@oh-story/dsh 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -12
- package/lib/client.js +2707 -268
- package/lib/client.js.map +4 -4
- package/lib/drama/manifest.json +136 -101
- package/lib/drama/skills/short-drama/SKILL.md +61 -1
- package/lib/drama/skills/short-drama/references/contract-and-ownership.md +7 -4
- package/lib/drama/skills/short-drama/references/creator-documents.md +133 -8
- package/lib/drama/skills/short-drama/references/creator-workflow.md +38 -2
- package/lib/drama/skills/short-drama/references/knowhow-index.md +3 -0
- package/lib/drama/skills/short-drama/references/reference-roles.md +10 -7
- package/lib/drama/skills/short-drama/references/runtime-preflight.md +3 -0
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +1002 -0
- package/lib/drama/skills/short-drama/scripts/project_tool.py +485 -5
- package/lib/drama/skills/short-drama-assets/SKILL.md +13 -3
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +116 -0
- package/lib/drama/skills/short-drama-assets/references/identity-vs-variant.md +2 -1
- package/lib/drama/skills/short-drama-assets/references/prop-and-state.md +19 -0
- package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-assets/scripts/asset_check.py +1 -1
- package/lib/drama/skills/short-drama-image-prompts/SKILL.md +6 -2
- package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +4 -3
- package/lib/drama/skills/short-drama-image-prompts/references/lookdev-frame.md +1 -1
- package/lib/drama/skills/short-drama-image-prompts/references/review-and-fixtures.md +1 -1
- package/lib/drama/skills/short-drama-image-prompts/references/stage-contract.md +3 -1
- package/lib/drama/skills/short-drama-image-prompts/scripts/image_prompt_check.py +8 -8
- package/lib/drama/skills/short-drama-image-prompts/scripts/selftest.py +4 -2
- package/lib/drama/skills/short-drama-produce/SKILL.md +46 -10
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +65 -8
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +85 -0
- package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +27 -10
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +362 -3
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +627 -40
- package/lib/drama/skills/short-drama-produce/scripts/selftest.py +5 -4
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +12 -0
- package/lib/drama/skills/short-drama-review/scripts/review_check.py +1 -1
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +76 -7
- package/lib/drama/skills/short-drama-storyboard/assets/shot-template.jsonl +1 -1
- package/lib/drama/skills/short-drama-storyboard/references/keyframe-craft.md +6 -2
- package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +19 -4
- package/lib/drama/skills/short-drama-storyboard/references/review-and-fixtures.md +5 -2
- package/lib/drama/skills/short-drama-storyboard/references/screenplay-to-keyframe-example.md +16 -0
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +8 -3
- package/lib/drama/skills/short-drama-storyboard/scripts/storyboard_check.py +1 -1
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +81 -13
- package/lib/drama/skills/short-drama-video-prompts/assets/motion-spec.jsonl.md +6 -1
- package/lib/drama/skills/short-drama-video-prompts/assets/video-prompts.md +17 -1
- package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +21 -1
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +154 -0
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +24 -6
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +11 -7
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +41 -0
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +66 -0
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +5 -2
- package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +115 -0
- package/lib/drama/skills/short-drama-video-prompts/scripts/container_check.py +1 -1
- package/lib/drama/skills/short-drama-video-prompts/scripts/music_spec_check.py +1 -1
- package/lib/drama/skills/short-drama-write/SKILL.md +3 -2
- package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +1 -1
- package/lib/drama/skills/short-drama-write/scripts/screenplay_index.py +2 -2
- package/lib/drama/skills/short-drama-write/scripts/voice_sheet_check.py +1 -1
- package/lib/index.js +865 -33
- package/lib/index.js.map +4 -4
- package/lib/novel-to-game/LICENSE +21 -0
- package/lib/novel-to-game/README.md +180 -0
- package/lib/novel-to-game/README_ZH.md +178 -0
- package/lib/novel-to-game/VERSION +1 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +217 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/.vercelignore +3 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +130 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/compound_new_guofeng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/fate_coda.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/meng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pan.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pinger.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/xuee.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/yue.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_afterglow_five.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_alliance.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day10.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day5.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/title_new_guofeng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_pinger.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_xuee.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_meng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pan.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pinger.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/meng.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pan.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pinger.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/xuee.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/yue.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/explicit.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/prelude.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/meng/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pan/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pinger/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/xuee/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/yue/night.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/scene/gate_collector.webp +0 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +1105 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +2539 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +18 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +147 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +332 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +7472 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +13077 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +3028 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +32 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +10 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/app/vercel.json +23 -0
- package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +161 -0
- package/lib/novel-to-game/examples/jin-ping-mei/example.json +12 -0
- package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +31 -0
- package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +99 -0
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +41 -0
- package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +129 -0
- package/lib/novel-to-game/manifest.json +508 -0
- package/lib/novel-to-game/skills/game-art-direction/SKILL.md +73 -0
- package/lib/novel-to-game/skills/game-art-direction/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +119 -0
- package/lib/novel-to-game/skills/game-build/SKILL.md +84 -0
- package/lib/novel-to-game/skills/game-build/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +109 -0
- package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +56 -0
- package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +81 -0
- package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +37 -0
- package/lib/novel-to-game/skills/game-concept/SKILL.md +72 -0
- package/lib/novel-to-game/skills/game-concept/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-concept/references/concept-method.md +94 -0
- package/lib/novel-to-game/skills/game-qa/SKILL.md +50 -0
- package/lib/novel-to-game/skills/game-qa/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-qa/references/qa-contract.md +66 -0
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +45 -0
- package/lib/novel-to-game/skills/game-world-design/SKILL.md +63 -0
- package/lib/novel-to-game/skills/game-world-design/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +59 -0
- package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +53 -0
- package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +84 -0
- package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +56 -0
- package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +124 -0
- package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +77 -0
- package/lib/novel-to-game/skills/novel-game-analyze/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +65 -0
- package/lib/novel-to-game/skills/novel-to-game/SKILL.md +71 -0
- package/lib/novel-to-game/skills/novel-to-game/agents/openai.yaml +4 -0
- package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +42 -0
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +51 -0
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +65 -0
- package/lib/oh-story/manifest.json +415 -305
- package/lib/oh-story/roles/consistency-checker.md +1 -1
- package/lib/oh-story/roles/narrative-writer.md +32 -33
- package/lib/oh-story/roles/story-architect.md +16 -26
- package/lib/oh-story/skills/browser-cdp/SKILL.md +1 -1
- package/lib/oh-story/skills/story/SKILL.md +16 -9
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-cover/SKILL.md +1 -1
- package/lib/oh-story/skills/story-deslop/SKILL.md +10 -5
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +2 -1
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +64 -1
- package/lib/oh-story/skills/story-import/SKILL.md +19 -10
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +9 -10
- package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +2 -2
- package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +1 -1
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +14 -6
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +251 -0
- package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +98 -9
- package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +246 -0
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +4 -4
- package/lib/oh-story/skills/story-long-scan/SKILL.md +1 -1
- package/lib/oh-story/skills/story-long-write/SKILL.md +35 -21
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +2 -1
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +7 -3
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story-long-write/references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +6 -6
- package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/genre-readers.md +1 -1
- package/lib/oh-story/skills/{story-setup/references/agent-references/hooks-chapter.md → story-long-write/references/long-chapter-hooks.md} +5 -5
- package/lib/oh-story/skills/story-long-write/references/{quality-checklist.md → long-chapter-quality.md} +2 -1
- package/lib/oh-story/skills/story-long-write/references/long-format.md +42 -0
- package/lib/oh-story/skills/{story-setup/references/agent-references/genre-catalog.md → story-long-write/references/long-genre-catalog.md} +11 -97
- package/lib/oh-story/skills/{story-setup/references/agent-references/genre-core-mechanics.md → story-long-write/references/long-genre-mechanics.md} +1 -1
- package/lib/oh-story/skills/{story-short-write/references/reversal-toolkit.md → story-long-write/references/long-reversal.md} +18 -32
- package/lib/oh-story/skills/story-long-write/references/{hooks-suspense.md → long-suspense.md} +6 -6
- package/lib/oh-story/skills/story-long-write/references/opening-design.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +14 -6
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +32 -27
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +4 -4
- package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +14 -14
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +54 -54
- package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +64 -1
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +251 -0
- package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +303 -0
- package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +98 -9
- package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +246 -0
- package/lib/oh-story/skills/story-review/SKILL.md +26 -17
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +2 -1
- package/lib/oh-story/skills/story-review/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story-review/references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-review/references/{quality-checklist.md → review-quality.md} +22 -21
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +14 -6
- package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +64 -1
- package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +98 -9
- package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +246 -0
- package/lib/oh-story/skills/story-setup/SKILL.md +74 -33
- package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +72 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/agent-reference-profiles.md +55 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +2 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +9 -10
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-readers.md +1 -1
- package/lib/oh-story/skills/{story-short-analyze/references/hooks-chapter.md → story-setup/references/agent-references/long-chapter-hooks.md} +5 -5
- package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +209 -0
- package/lib/oh-story/skills/{story-long-write/references/genre-catalog.md → story-setup/references/agent-references/long-genre-catalog.md} +11 -97
- package/lib/oh-story/skills/{story-long-write/references/genre-core-mechanics.md → story-setup/references/agent-references/long-genre-mechanics.md} +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +40 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/{reversal-toolkit.md → long-reversal.md} +18 -32
- package/lib/oh-story/skills/{story-short-write/references/hooks-suspense.md → story-setup/references/agent-references/long-suspense.md} +6 -6
- package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/{emotional-methods.md → short-emotional-methods.md} +4 -4
- package/lib/oh-story/skills/{story-long-write/references/genre-writing-formulas.md → story-setup/references/agent-references/short-genre-formulas.md} +32 -44
- package/lib/oh-story/skills/story-setup/references/agent-references/short-quality.md +39 -0
- package/lib/oh-story/skills/{story-long-write/references/reversal-toolkit.md → story-setup/references/agent-references/short-reversal.md} +29 -23
- package/lib/oh-story/skills/story-setup/references/agent-references/{hooks-suspense.md → short-suspense.md} +31 -31
- package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +55 -55
- package/lib/oh-story/skills/story-short-analyze/SKILL.md +28 -28
- package/lib/oh-story/skills/story-short-analyze/references/{character-basics.md → analysis-character-basics.md} +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/{character-design-methods.md → analysis-character-design.md} +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/{character-relations.md → analysis-character-relations.md} +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/{hooks-paragraph.md → analysis-paragraph-hooks.md} +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/{genre-readers.md → analysis-reader-profile.md} +2 -2
- package/lib/oh-story/skills/story-short-analyze/references/analysis-report-style.md +45 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-genres.md +65 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-hooks.md +77 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-mechanics.md +82 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-patterns.md +50 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-short-suspense.md +98 -0
- package/lib/oh-story/skills/story-short-analyze/references/{genre-writing-techniques.md → analysis-writing-techniques.md} +21 -19
- package/lib/oh-story/skills/story-short-analyze/references/deconstruction-examples.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +21 -28
- package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +17 -17
- package/lib/oh-story/skills/story-short-analyze/references/{quality-checklist.md → source-story-quality.md} +28 -49
- package/lib/oh-story/skills/story-short-analyze/references/zhihu-style.md +2 -2
- package/lib/oh-story/skills/story-short-scan/SKILL.md +1 -1
- package/lib/oh-story/skills/story-short-write/SKILL.md +78 -55
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +163 -0
- package/lib/oh-story/skills/story-short-write/references/banned-words.md +6 -4
- package/lib/oh-story/skills/story-short-write/references/cross-book-recall.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +6 -6
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +7 -7
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/262/231/351/233/225/350/204/221/346/264/236.md +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//350/277/275/345/246/273/347/201/253/350/221/254/345/234/272.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +32 -44
- package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +20 -18
- package/lib/oh-story/skills/story-short-write/references/output-contract.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/short-craft.md +18 -18
- package/lib/oh-story/skills/story-short-write/references/short-deslop.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/short-format.md +38 -8
- package/lib/oh-story/skills/story-short-write/references/{quality-checklist.md → short-prose-quality.md} +22 -21
- package/lib/oh-story/skills/story-short-write/references/short-reversal.md +355 -0
- package/lib/oh-story/skills/{story-short-analyze/references/hooks-suspense.md → story-short-write/references/short-suspense.md} +31 -31
- package/lib/oh-story/skills/story-short-write/references/submission-craft.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +31 -22
- package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +871 -0
- package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +64 -1
- package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +212 -0
- package/lib/oh-story/skills/story-short-write/scripts/check-phase2-contract.js +363 -0
- package/lib/types/client/drama-production-view.d.ts +34 -0
- package/lib/types/client/drama-production-view.d.ts.map +1 -0
- package/lib/types/client/drama-production.d.ts +92 -0
- package/lib/types/client/drama-production.d.ts.map +1 -0
- package/lib/types/client/file-activity.d.ts +4 -2
- package/lib/types/client/file-activity.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/production-intents.d.ts +9 -0
- package/lib/types/client/production-intents.d.ts.map +1 -0
- package/lib/types/client/production-prompts.d.ts +9 -0
- package/lib/types/client/production-prompts.d.ts.map +1 -0
- package/lib/types/client/production-runtime.d.ts +58 -0
- package/lib/types/client/production-runtime.d.ts.map +1 -0
- package/lib/types/client/video-studio.d.ts +41 -0
- package/lib/types/client/video-studio.d.ts.map +1 -0
- package/lib/types/client/workbench-ui.d.ts +5 -0
- package/lib/types/client/workbench-ui.d.ts.map +1 -0
- package/lib/types/game-verification.d.ts +16 -0
- package/lib/types/game-verification.d.ts.map +1 -0
- package/lib/types/index.d.ts +3 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/production-intent.d.ts +21 -0
- package/lib/types/production-intent.d.ts.map +1 -0
- package/lib/types/production-tool.d.ts +5 -0
- package/lib/types/production-tool.d.ts.map +1 -0
- package/lib/types/role-tool.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts +7 -0
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/types/video-project.d.ts +42 -0
- package/lib/types/video-project.d.ts.map +1 -0
- package/lib/types/workspace-request-trust.d.ts +6 -0
- package/lib/types/workspace-request-trust.d.ts.map +1 -1
- package/lib/types/workspace-route.d.ts +20 -0
- package/lib/types/workspace-route.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +282 -0
- package/lib/video-recap/LICENSE +21 -0
- package/lib/video-recap/README.en.md +257 -0
- package/lib/video-recap/README.md +255 -0
- package/lib/video-recap/manifest.json +684 -0
- package/lib/video-recap/skills/video-assemble/SKILL.md +97 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/LICENSE.duo-video +21 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +15 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_draft_meta_info.json +69 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +84 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +98 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +204 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_material_video.json +83 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_meta_material_value.json +23 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_project_info.json +133 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_segment.json +70 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_text_styles.json +30 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_audio.json +41 -0
- package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_text.json +109 -0
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +88 -0
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +345 -0
- package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +34 -0
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +305 -0
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +94 -0
- package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +205 -0
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +555 -0
- package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +261 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +767 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +111 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +24 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +109 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_templates.py +37 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +191 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +75 -0
- package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +394 -0
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +239 -0
- package/lib/video-recap/skills/video-assemble/scripts/media.py +243 -0
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +477 -0
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +46 -0
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +486 -0
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +376 -0
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +88 -0
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +248 -0
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +89 -0
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +512 -0
- package/lib/video-recap/skills/video-cut/SKILL.md +90 -0
- package/lib/video-recap/skills/video-cut/scripts/cut.py +67 -0
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +320 -0
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +545 -0
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +348 -0
- package/lib/video-recap/skills/video-cut/scripts/lib.py +77 -0
- package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +341 -0
- package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +249 -0
- package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +745 -0
- package/lib/video-recap/skills/video-recap/SKILL.md +216 -0
- package/lib/video-recap/skills/video-recap/references/config-playbook.md +61 -0
- package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +225 -0
- package/lib/video-recap/skills/video-recap/references/data-schema.md +534 -0
- package/lib/video-recap/skills/video-recap/references/research-guide.md +74 -0
- package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +23 -0
- package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +251 -0
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +483 -0
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +737 -0
- package/lib/video-recap/skills/video-recap/scripts/lib.py +242 -0
- package/lib/video-recap/skills/video-recap/scripts/materials.py +457 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +95 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +18 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_contract.py +9 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +371 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +201 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +227 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +362 -0
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +129 -0
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +338 -0
- package/lib/video-recap/skills/video-recap/scripts/recap.py +9 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +140 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +550 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_review.py +77 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +654 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +335 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +217 -0
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +795 -0
- package/lib/video-recap/skills/video-script/SKILL.md +248 -0
- package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +225 -0
- package/lib/video-recap/skills/video-script/references/research-guide.md +75 -0
- package/lib/video-recap/skills/video-script/scripts/agent_brief.py +399 -0
- package/lib/video-recap/skills/video-script/scripts/agent_text.py +410 -0
- package/lib/video-recap/skills/video-script/scripts/brief_context.py +403 -0
- package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +332 -0
- package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +442 -0
- package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +237 -0
- package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +492 -0
- package/lib/video-recap/skills/video-script/scripts/lib.py +327 -0
- package/lib/video-recap/skills/video-script/scripts/narration.py +12 -0
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +678 -0
- package/lib/video-recap/skills/video-script/scripts/review.py +42 -0
- package/lib/video-recap/skills/video-script/scripts/review_grounding.py +262 -0
- package/lib/video-recap/skills/video-script/scripts/review_response.py +761 -0
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +200 -0
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +204 -0
- package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +199 -0
- package/lib/video-recap/skills/video-script/scripts/validate.py +164 -0
- package/lib/video-recap/skills/video-understanding/SKILL.md +75 -0
- package/lib/video-recap/skills/video-understanding/references/data-schema.md +299 -0
- package/lib/video-recap/skills/video-understanding/references/prompt-templates.md +37 -0
- package/lib/video-recap/skills/video-understanding/references/research-guide.md +74 -0
- package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +399 -0
- package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +410 -0
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +260 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief.py +12 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +403 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +332 -0
- package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +442 -0
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +647 -0
- package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +237 -0
- package/lib/video-recap/skills/video-understanding/scripts/detect.py +513 -0
- package/lib/video-recap/skills/video-understanding/scripts/extract.py +62 -0
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +470 -0
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +678 -0
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +204 -0
- package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +507 -0
- package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +199 -0
- package/lib/video-recap/skills/video-understanding/scripts/understand.py +9 -0
- package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +216 -0
- package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +327 -0
- package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +367 -0
- package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +162 -0
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +705 -0
- package/lib/video-recap/skills/video-voiceover/SKILL.md +81 -0
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +637 -0
- package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +109 -0
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +421 -0
- package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +113 -0
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +782 -0
- package/package.json +55 -40
- package/lib/oh-story/skills/story-deslop/scripts/check-outline-copy.js +0 -241
- package/lib/oh-story/skills/story-long-write/references/format-and-structure.md +0 -169
- package/lib/oh-story/skills/story-setup/references/agent-references/genre-writing-formulas.md +0 -444
- package/lib/oh-story/skills/story-setup/references/agent-references/quality-checklist.md +0 -247
- package/lib/oh-story/skills/story-short-analyze/references/anti-ai-writing.md +0 -582
- package/lib/oh-story/skills/story-short-analyze/references/banned-words.md +0 -147
- package/lib/oh-story/skills/story-short-analyze/references/genre-catalog.md +0 -490
- package/lib/oh-story/skills/story-short-analyze/references/genre-core-mechanics.md +0 -425
- package/lib/oh-story/skills/story-short-analyze/references/genre-writing-formulas.md +0 -453
- /package/lib/oh-story/skills/{story-long-write/references/hooks-chapter.md → story-setup/references/agent-references/short-chapter-hooks.md} +0 -0
- /package/lib/oh-story/skills/{story-long-write/references/hooks-paragraph.md → story-setup/references/agent-references/short-paragraph-hooks.md} +0 -0
package/lib/index.js
CHANGED
|
@@ -10,24 +10,25 @@ import {
|
|
|
10
10
|
} from "@deepseek-ai/dsh-skill";
|
|
11
11
|
var STORY_PROVIDER_NAME = "oh-story";
|
|
12
12
|
var DRAMA_PROVIDER_NAME = "short-drama";
|
|
13
|
-
var
|
|
13
|
+
var GAME_PROVIDER_NAME = "novel-to-game";
|
|
14
|
+
var VIDEO_PROVIDER_NAME = "video-recap";
|
|
14
15
|
var DSH_SKILL_BRIDGE = [
|
|
15
16
|
"<oh-story-dsh-integration>",
|
|
16
17
|
"This Skill is a native contribution to the current DeepSeek Harness session.",
|
|
17
18
|
"DSH owns the workspace, model, preset, permissions, Session Log, tools, subagents, cancellation, resume, and Agent UI.",
|
|
18
19
|
"Never start another Agent runtime, session transport, Dashboard, SSE stream, polling loop, or model configuration.",
|
|
19
20
|
"All seven upstream Oh Story specialist Roles are bundled. Invoke one with oh_story_role and a self-contained prompt.",
|
|
20
|
-
"Never inspect .claude/agents, .codex/agents, .opencode/agents, or .story-deployed to decide whether a Role is available.",
|
|
21
|
+
"Never inspect .claude/agents, .codex/agents, .opencode/agents, .agents/agents, .zcode, or .story-deployed to decide whether a Role is available, and never call invoke_subagent with a TypeName.",
|
|
21
22
|
"Use only DSH-visible tools. DSH sandbox and permission policy remain authoritative.",
|
|
22
23
|
"</oh-story-dsh-integration>"
|
|
23
24
|
].join("\n");
|
|
24
25
|
var DSH_SKILL_OVERRIDES = {
|
|
25
26
|
story: "The \u5C0F\u8BF4 workspace is an official DSH conversation view. Never start or open a second web application.",
|
|
26
|
-
"story-setup": "Initialize or validate novel project data only. DSH already supplies Skills, Roles, hooks, tools, permissions, sessions, and UI; never deploy Claude/OpenCode/Codex/ZCode/OpenClaw/Reasonix files or a .story-deployed marker.",
|
|
27
|
+
"story-setup": "Initialize or validate novel project data only. DSH already supplies Skills, Roles, hooks, tools, permissions, sessions, and UI; never deploy Claude/OpenCode/Codex/Antigravity/ZCode/OpenClaw/Reasonix files or a .story-deployed marker.",
|
|
27
28
|
"story-long-analyze": "Use oh_story_role for chapter extraction or specialist analysis. Never inspect platform agent directories or require a deployed external Agent definition.",
|
|
28
29
|
"story-long-write": "All named Roles are provided through oh_story_role. Do not check platform agent files. Keep the upstream writing, Tracking, lint, outline, revision, and quality workflows.",
|
|
29
30
|
"story-review": "All named reviewer Roles are provided through oh_story_role. Do not check platform agent files; full/lean review may use the bundled Roles directly.",
|
|
30
|
-
"story-import": "All named Roles are provided through oh_story_role. Do not require story-setup to deploy them.",
|
|
31
|
+
"story-import": "All named Roles are provided through oh_story_role. Do not require story-setup to deploy them, and never inspect platform agent directories.",
|
|
31
32
|
"story-deslop": "Use the bundled narrative-writer Role through oh_story_role when specialist review is useful. Never inspect platform agent directories.",
|
|
32
33
|
"story-short-analyze": "Use oh_story_role for specialist analysis. Never inspect platform agent directories or require external Agent deployment.",
|
|
33
34
|
"story-short-write": "All named Roles are provided through oh_story_role. Do not inspect platform agent files; preserve the upstream short-fiction workflow and quality gates.",
|
|
@@ -43,13 +44,38 @@ var DSH_DRAMA_BRIDGE = [
|
|
|
43
44
|
"For a v0.6 project, never create a parallel JSON/JSONL lifecycle truth, indexes, fingerprints, coverage tables, or QA records merely because legacy maintenance scripts and templates remain bundled.",
|
|
44
45
|
"Never upgrade a v0.5 structured project in place or mix both contracts in one project root. Keep legacy production/audit artifacts read-only and pinned to v0.5; migrate only into a new creator-first root with manual per-episode creator confirmation.",
|
|
45
46
|
"Use only tools visible in the current DSH preset and preserve the upstream project ownership, freshness, review, and explicit production-confirmation contracts.",
|
|
47
|
+
"Use oh_story_production only for semantic production-view intents (open/focus, explicit shot order, or tracking a job that this Agent is actually executing). Cosmetic canvas layout remains creator-controlled. The tool changes only the Session projection: it does not edit creator documents, generate media, or authorize production.",
|
|
46
48
|
"Production credentials remain outside project files. Never treat a prior acceptance, preview, continuation request, or budget discussion as confirmation for a paid production run.",
|
|
47
49
|
"</short-drama-dsh-integration>"
|
|
48
50
|
].join("\n");
|
|
49
51
|
var DSH_DRAMA_OVERRIDES = {
|
|
50
52
|
"short-drama": "A dashboard request means focus or use the native \u77ED\u5267 tab in this DSH Session. Do not run the bundled standalone Dashboard script. New projects follow only the v0.6 creator-first contract and create only documents required by the current request.",
|
|
51
|
-
"short-drama-produce": "Use an upstream adapter only after the creator explicitly confirms the exact current job. Its source must be the current creator-first Markdown and its output belongs under \u5267\u96C6/<EP>/\u5236\u4F5C\u6210\u679C/. DSH permissions and approval UI remain authoritative."
|
|
53
|
+
"short-drama-produce": "Use an upstream adapter only after the creator explicitly confirms the exact current job. Its source must be the current creator-first Markdown and its output belongs under \u5267\u96C6/<EP>/\u5236\u4F5C\u6210\u679C/. DSH permissions and approval UI remain authoritative. When oh_story_production is visible, register the previewed job with track_job after the confirmation is valid and before run \u2014 never at prepare time \u2014 passing the same job ID, target, modality and count the creator just approved.",
|
|
54
|
+
"short-drama-assets": "Keep one stable `- ID\uFF1AVISUAL-*` line on every \u4EBA\u7269/\u9020\u578B/\u5730\u70B9/\u9053\u5177/\u72B6\u6001 entry in \u89C6\u89C9\u8BBE\u5B9A.md, and never change an existing ID when editing its heading or text. The \u751F\u4EA7 view identifies canvas nodes by that ID; entries without one still render, but the workbench reports their node identity as unstable."
|
|
52
55
|
};
|
|
56
|
+
var DSH_GAME_BRIDGE = [
|
|
57
|
+
"<novel-to-game-dsh-integration>",
|
|
58
|
+
"This Skill is a native contribution to the current DeepSeek Harness session.",
|
|
59
|
+
"DSH owns the workspace, model, preset, permissions, Session Log, tools, approvals, cancellation, resume, Todo, and Chat UI.",
|
|
60
|
+
"The \u6E38\u620F tab is the playable Game Studio. Never start a second Agent runtime, dashboard, session transport, or model configuration.",
|
|
61
|
+
"Keep the complete upstream seven-Skill pipeline and write adaptation artifacts under game-adaptations/<project>/ exactly as the upstream contracts specify.",
|
|
62
|
+
"For a web target, keep the authoritative playable entry at build/app/index.html so Game Studio can preview it. Do not silently replace a requested non-web runtime with a web build.",
|
|
63
|
+
"Use only DSH-visible tools and approvals. qa/verification.json remains the sole machine QA truth and must cover launch, render, input, coreLoop, outcome, and restart with real execution evidence.",
|
|
64
|
+
"The bundled Jin Ping Mei project is a read-only example, not a template to copy mechanically and not proof that another adaptation passed QA.",
|
|
65
|
+
"</novel-to-game-dsh-integration>"
|
|
66
|
+
].join("\n");
|
|
67
|
+
var DSH_VIDEO_BRIDGE = [
|
|
68
|
+
"<video-recap-dsh-integration>",
|
|
69
|
+
"This Skill is a native contribution to the current DeepSeek Harness session.",
|
|
70
|
+
"DSH owns the workspace, model, preset, permissions, Session Log, tools, approvals, cancellation, resume, Todo, Chat, and the \u89C6\u9891 preview Studio.",
|
|
71
|
+
"Never start a second Agent runtime, dashboard, session transport, web server, polling loop, or model configuration.",
|
|
72
|
+
"Keep the complete upstream six-Skill pipeline. Put each project under video-recaps/<project>/, copy or import source media under sources/, and use work/ as the upstream work_dir so the Studio can discover authoritative manifests and outputs.",
|
|
73
|
+
"The Video Studio is a preview and artifact surface, not a nonlinear editor. Do not invent a second project-state format, timeline truth, or render queue; recap_run_manifest.json, recap_phase.json, timeline.json, assembly_manifest.json, and the upstream artifacts remain authoritative.",
|
|
74
|
+
"MIMO_API_KEY, FISH_API_KEY, and voice credentials stay in the host environment. Never write secrets into project files, tool arguments shown to the browser, or chat output.",
|
|
75
|
+
"Use only DSH-visible tools and approvals. Run Python and ffmpeg through the current DSH execution world, preserve cancellation, and do not install or upgrade system dependencies without explicit user approval.",
|
|
76
|
+
"When a new edited or final video is ready, tell the user that Video Studio can load it; never interrupt playback by replacing the currently loaded video silently.",
|
|
77
|
+
"</video-recap-dsh-integration>"
|
|
78
|
+
].join("\n");
|
|
53
79
|
var DSH_NATIVE_SKILLS = {
|
|
54
80
|
story: `# story \u2014 DSH \u5C0F\u8BF4\u6D41\u7A0B\u5165\u53E3
|
|
55
81
|
|
|
@@ -61,6 +87,9 @@ var DSH_NATIVE_SKILLS = {
|
|
|
61
87
|
- \u5BFC\u5165\u5DF2\u6709\u4F5C\u54C1\uFF1Astory-import
|
|
62
88
|
- \u5BA1\u7A3F\u4E0E\u53BB AI \u5473\uFF1Astory-review\u3001story-deslop
|
|
63
89
|
- \u5C01\u9762\uFF1Astory-cover
|
|
90
|
+
- \u7BA1\u7406\u4F5C\u8005\u4E60\u60EF\uFF08\u8BB0\u4F4F/\u67E5\u770B/\u786E\u8BA4/\u66FF\u6362/\u5FD8\u6389\u5199\u4F5C\u504F\u597D\uFF09\uFF1A\u52A0\u8F7D\u672C skill \u7684
|
|
91
|
+
references/author-memory.md\uFF0C\u53EA\u7528\u672C skill \u7684 scripts/author_memory_commit.py \u7BA1\u7406
|
|
92
|
+
\u5DE5\u4F5C\u533A\u7EA7 .story/\u4F5C\u8005\u8BB0\u5FC6/\uFF1B\u5DE5\u5177\u672A\u8FD4\u56DE Author Memory Receipt \u524D\u4E0D\u5F97\u58F0\u79F0\u5DF2\u8BB0\u4F4F\u3002
|
|
64
93
|
|
|
65
94
|
\u610F\u56FE\u660E\u786E\u65F6\u76F4\u63A5\u8FDB\u5165\u5BF9\u5E94 Skill\uFF1B\u4E0D\u660E\u786E\u65F6\u53EA\u95EE\u4E00\u4E2A\u4F1A\u6539\u53D8\u6D41\u7A0B\u7684\u95EE\u9898\u3002\u9879\u76EE\u6587\u4EF6\u3001
|
|
66
95
|
Agent\u3001\u6A21\u578B\u3001\u6743\u9650\u3001Session Log \u548C UI \u5747\u7531\u5F53\u524D DSH \u4F1A\u8BDD\u7BA1\u7406\u3002\u5C0F\u8BF4\u6587\u4EF6\u901A\u8FC7\u201C\u5C0F\u8BF4\u201D
|
|
@@ -74,7 +103,7 @@ Agent\u3001\u6A21\u578B\u3001\u6743\u9650\u3001Session Log \u548C UI \u5747\u753
|
|
|
74
103
|
3. \u957F\u7BC7\u6309\u9700\u5EFA\u7ACB \u6B63\u6587/\u3001\u8BBE\u5B9A/\u3001\u5927\u7EB2/\u3001\u8FFD\u8E2A/\uFF0C\u5E76\u51C6\u5907\u8FFD\u8E2A/_tracking-state.json\uFF1B
|
|
75
104
|
\u7B2C\u4E00\u7AE0\u6B63\u6587\u843D\u76D8\u524D\u5FC5\u987B\u6709\u5BF9\u5E94\u7EC6\u7EB2\u3002\u77ED\u7BC7\u4FDD\u6301\u8F7B\u91CF\u7ED3\u6784\uFF0C\u4E0D\u5F3A\u52A0\u957F\u7BC7 Tracking\u3002
|
|
76
105
|
4. \u9700\u8981\u67B6\u6784\u3001\u89D2\u8272\u6216\u7814\u7A76\u5DE5\u4F5C\u65F6\uFF0C\u901A\u8FC7 oh_story_role \u8C03\u7528\u5DF2\u6253\u5305 Role\uFF1B\u4E0D\u8981\u68C0\u67E5\u6216
|
|
77
|
-
\u751F\u6210 .claude\u3001.codex\u3001.opencode\u3001.zcode\u3001AGENTS.md \u6216 .story-deployed\u3002
|
|
106
|
+
\u751F\u6210 .claude\u3001.codex\u3001.opencode\u3001.agents\u3001.zcode\u3001AGENTS.md \u6216 .story-deployed\u3002
|
|
78
107
|
5. \u590D\u8FF0\u521B\u5EFA\u3001\u4FDD\u7559\u548C\u5F85\u7528\u6237\u786E\u8BA4\u7684\u6587\u4EF6\u3002\u4E0D\u8981\u914D\u7F6E\u6A21\u578B\u3001\u6743\u9650\u3001Hooks \u6216 Session\u3002
|
|
79
108
|
|
|
80
109
|
\u9898\u6750\u3001\u89D2\u8272\u3001\u8282\u594F\u3001\u51B2\u7A81\u3001\u5F00\u7BC7\u548C\u5199\u4F5C\u65B9\u6CD5\u8D44\u6599\u4F4D\u4E8E references/agent-references/\uFF1B
|
|
@@ -122,9 +151,19 @@ Agent\u3001\u6A21\u578B\u3001\u6743\u9650\u3001Session Log \u548C UI \u5747\u753
|
|
|
122
151
|
\u4E0D\u8981\u542F\u52A8 CDP\u3001\u72EC\u7ACB\u6D4F\u89C8\u5668\u6216\u5E76\u884C\u8FD0\u884C\u65F6\u3002`
|
|
123
152
|
};
|
|
124
153
|
function frontmatterValue(frontmatter, key) {
|
|
125
|
-
const
|
|
126
|
-
const
|
|
154
|
+
const lines = frontmatter.split(/\r?\n/u);
|
|
155
|
+
const index = lines.findIndex((line) => new RegExp(`^${key}:\\s*`, "u").test(line));
|
|
156
|
+
if (index < 0) return void 0;
|
|
157
|
+
const raw = lines[index]?.replace(new RegExp(`^${key}:\\s*`, "u"), "").trim();
|
|
127
158
|
if (raw === void 0) return void 0;
|
|
159
|
+
if (raw === ">" || raw === "|" || raw === ">-" || raw === "|-") {
|
|
160
|
+
const values = [];
|
|
161
|
+
for (const line of lines.slice(index + 1)) {
|
|
162
|
+
if (/^\S/u.test(line)) break;
|
|
163
|
+
values.push(line.trim());
|
|
164
|
+
}
|
|
165
|
+
return (raw.startsWith(">") ? values.join(" ") : values.join("\n")).trim();
|
|
166
|
+
}
|
|
128
167
|
if (raw.startsWith('"') && raw.endsWith('"')) {
|
|
129
168
|
try {
|
|
130
169
|
return JSON.parse(raw);
|
|
@@ -141,7 +180,12 @@ function parseBundledSkill(source) {
|
|
|
141
180
|
const description = frontmatterValue(match[1], "description");
|
|
142
181
|
if (!name2 || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name2)) throw new Error("Bundled skill has an invalid name.");
|
|
143
182
|
if (!description) throw new Error(`Bundled skill "${name2}" has no description.`);
|
|
144
|
-
return {
|
|
183
|
+
return {
|
|
184
|
+
name: name2,
|
|
185
|
+
description,
|
|
186
|
+
content: source.slice(match[0].length),
|
|
187
|
+
userInvocable: frontmatterValue(match[1], "user-invocable") !== "false"
|
|
188
|
+
};
|
|
145
189
|
}
|
|
146
190
|
function dshSkillContent(name2, content) {
|
|
147
191
|
const override = DSH_SKILL_OVERRIDES[name2];
|
|
@@ -159,6 +203,10 @@ function defaultDramaSkillRoot() {
|
|
|
159
203
|
const current = dirname(fileURLToPath(import.meta.url));
|
|
160
204
|
return basename(current) === "src" ? resolve(current, "../../knowledge/drama/skills") : resolve(current, "drama/skills");
|
|
161
205
|
}
|
|
206
|
+
function defaultNovelToGameSkillRoot() {
|
|
207
|
+
const current = dirname(fileURLToPath(import.meta.url));
|
|
208
|
+
return basename(current) === "src" ? resolve(current, "../../knowledge/novel-to-game/skills") : resolve(current, "novel-to-game/skills");
|
|
209
|
+
}
|
|
162
210
|
function createBundledSkillProvider(providerName, skillRoot, content) {
|
|
163
211
|
const root = resolve(skillRoot);
|
|
164
212
|
return {
|
|
@@ -172,7 +220,7 @@ function createBundledSkillProvider(providerName, skillRoot, content) {
|
|
|
172
220
|
return {
|
|
173
221
|
name: parsed.name,
|
|
174
222
|
description: parsed.description,
|
|
175
|
-
invocation:
|
|
223
|
+
invocation: { modelInvocable: true, userInvocable: parsed.userInvocable },
|
|
176
224
|
provider: providerName,
|
|
177
225
|
source: "bundled",
|
|
178
226
|
resourceBase: { kind: "directory", path: join(root, directory) },
|
|
@@ -194,7 +242,7 @@ function createBundledSkillProvider(providerName, skillRoot, content) {
|
|
|
194
242
|
return {
|
|
195
243
|
name: parsed.name,
|
|
196
244
|
description: parsed.description,
|
|
197
|
-
invocation:
|
|
245
|
+
invocation: { modelInvocable: true, userInvocable: parsed.userInvocable },
|
|
198
246
|
provider: providerName,
|
|
199
247
|
source: "bundled",
|
|
200
248
|
resourceBase: { kind: "directory", path: join(root, parsed.name) },
|
|
@@ -217,18 +265,38 @@ ${content}`;
|
|
|
217
265
|
function createDramaSkillProvider(skillRoot = defaultDramaSkillRoot()) {
|
|
218
266
|
return createBundledSkillProvider(DRAMA_PROVIDER_NAME, skillRoot, dshDramaSkillContent);
|
|
219
267
|
}
|
|
268
|
+
function dshNovelToGameSkillContent(_name, content) {
|
|
269
|
+
return `${DSH_GAME_BRIDGE}
|
|
270
|
+
|
|
271
|
+
${content}`;
|
|
272
|
+
}
|
|
273
|
+
function createNovelToGameSkillProvider(skillRoot = defaultNovelToGameSkillRoot()) {
|
|
274
|
+
return createBundledSkillProvider(GAME_PROVIDER_NAME, skillRoot, dshNovelToGameSkillContent);
|
|
275
|
+
}
|
|
276
|
+
function defaultVideoRecapSkillRoot() {
|
|
277
|
+
const current = dirname(fileURLToPath(import.meta.url));
|
|
278
|
+
return basename(current) === "src" ? resolve(current, "../../knowledge/video-recap/skills") : resolve(current, "video-recap/skills");
|
|
279
|
+
}
|
|
280
|
+
function dshVideoRecapSkillContent(_name, content) {
|
|
281
|
+
return `${DSH_VIDEO_BRIDGE}
|
|
282
|
+
|
|
283
|
+
${content}`;
|
|
284
|
+
}
|
|
285
|
+
function createVideoRecapSkillProvider(skillRoot = defaultVideoRecapSkillRoot()) {
|
|
286
|
+
return createBundledSkillProvider(VIDEO_PROVIDER_NAME, skillRoot, dshVideoRecapSkillContent);
|
|
287
|
+
}
|
|
220
288
|
|
|
221
289
|
// src/native-hooks.ts
|
|
222
290
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
223
291
|
var MUTATION_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "str_replace_editor"]);
|
|
224
292
|
function mutationPath(name2, args) {
|
|
225
293
|
if (!MUTATION_TOOLS.has(name2) || typeof args !== "object" || args === null || Array.isArray(args)) return void 0;
|
|
226
|
-
const
|
|
294
|
+
const record2 = args;
|
|
227
295
|
if (name2 === "str_replace_editor") {
|
|
228
|
-
if (!(/* @__PURE__ */ new Set(["create", "str_replace", "insert"])).has(String(
|
|
229
|
-
return typeof
|
|
296
|
+
if (!(/* @__PURE__ */ new Set(["create", "str_replace", "insert"])).has(String(record2.command))) return void 0;
|
|
297
|
+
return typeof record2.path === "string" && record2.path.trim() !== "" ? record2.path : void 0;
|
|
230
298
|
}
|
|
231
|
-
return typeof
|
|
299
|
+
return typeof record2.file_path === "string" && record2.file_path.trim() !== "" ? record2.file_path : void 0;
|
|
232
300
|
}
|
|
233
301
|
function normalizedRelativePath(path) {
|
|
234
302
|
const segments = [];
|
|
@@ -553,11 +621,290 @@ async function registerOhStoryRoleTool(context) {
|
|
|
553
621
|
if (context.subagents.getProvider("spawn") !== void 0) mount();
|
|
554
622
|
}
|
|
555
623
|
|
|
624
|
+
// src/production-tool.ts
|
|
625
|
+
import { defineTool as defineTool3 } from "@deepseek-ai/dsh-tools";
|
|
626
|
+
|
|
627
|
+
// src/production-intent.ts
|
|
628
|
+
var OH_STORY_PRODUCTION_TOOL_NAME = "oh_story_production";
|
|
629
|
+
var PRODUCTION_INTENT_ACTIONS = [
|
|
630
|
+
"open_section",
|
|
631
|
+
"focus_target",
|
|
632
|
+
"set_sequence",
|
|
633
|
+
"track_job"
|
|
634
|
+
];
|
|
635
|
+
var PRODUCTION_INTENT_SECTIONS = ["shots", "assets", "tasks", "sequence", "canvas"];
|
|
636
|
+
var PRODUCTION_INTENT_JOB_KINDS = ["image", "video", "composition"];
|
|
637
|
+
function requiredText(value, field) {
|
|
638
|
+
const normalized = value?.trim();
|
|
639
|
+
if (!normalized) throw new Error(`oh_story_production ${field} is required for this action.`);
|
|
640
|
+
if (normalized.length > 512) throw new Error(`oh_story_production ${field} is too long.`);
|
|
641
|
+
return normalized;
|
|
642
|
+
}
|
|
643
|
+
function validateProductionIntent(args) {
|
|
644
|
+
const episode = args.episode.trim().replaceAll("\\", "/").replace(/\/$/u, "");
|
|
645
|
+
if (!/^剧集\/EP\d{3,}$/u.test(episode)) {
|
|
646
|
+
throw new Error("oh_story_production episode must use the creator path form \u5267\u96C6/EP001.");
|
|
647
|
+
}
|
|
648
|
+
if (args.action === "open_section") {
|
|
649
|
+
if (args.section === void 0) throw new Error("oh_story_production section is required for open_section.");
|
|
650
|
+
return { action: args.action, episode, section: args.section };
|
|
651
|
+
}
|
|
652
|
+
if (args.action === "focus_target") {
|
|
653
|
+
return { action: args.action, episode, targetId: requiredText(args.targetId, "targetId"), section: args.section };
|
|
654
|
+
}
|
|
655
|
+
if (args.action === "set_sequence") {
|
|
656
|
+
const shotIds = args.shotIds?.map((value) => value.trim()).filter((value) => value !== "") ?? [];
|
|
657
|
+
if (shotIds.length === 0) throw new Error("oh_story_production shotIds must contain at least one shot for set_sequence.");
|
|
658
|
+
if (shotIds.length > 500 || new Set(shotIds).size !== shotIds.length || shotIds.some((value) => !/^SHOT-[A-Z0-9-]+$/u.test(value))) {
|
|
659
|
+
throw new Error("oh_story_production shotIds must be unique canonical SHOT-* identifiers.");
|
|
660
|
+
}
|
|
661
|
+
return { action: args.action, episode, shotIds };
|
|
662
|
+
}
|
|
663
|
+
const expectedOutputs = args.expectedOutputs;
|
|
664
|
+
if (expectedOutputs !== void 0 && (!Number.isInteger(expectedOutputs) || expectedOutputs < 1 || expectedOutputs > 500)) {
|
|
665
|
+
throw new Error("oh_story_production expectedOutputs must be an integer between 1 and 500.");
|
|
666
|
+
}
|
|
667
|
+
if (args.jobKind === void 0) throw new Error("oh_story_production jobKind is required for track_job.");
|
|
668
|
+
const prompt = args.prompt?.trim();
|
|
669
|
+
return {
|
|
670
|
+
action: args.action,
|
|
671
|
+
episode,
|
|
672
|
+
jobId: requiredText(args.jobId, "jobId"),
|
|
673
|
+
targetId: requiredText(args.targetId, "targetId"),
|
|
674
|
+
jobKind: args.jobKind,
|
|
675
|
+
expectedOutputs,
|
|
676
|
+
prompt: prompt === "" ? void 0 : prompt
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// src/production-tool.ts
|
|
681
|
+
function intentMessage(intent) {
|
|
682
|
+
if (intent.action === "track_job") return `\u5DF2\u628A ${intent.targetId ?? "\u751F\u4EA7\u5BF9\u8C61"} \u7684 ${intent.jobKind ?? "\u5A92\u4F53"} \u4EFB\u52A1\u6295\u5F71\u5230 ${intent.episode} \u7684\u4EFB\u52A1\u677F\u3002`;
|
|
683
|
+
if (intent.action === "set_sequence") return `\u5DF2\u628A ${String(intent.shotIds?.length ?? 0)} \u4E2A\u955C\u5934\u7684\u987A\u5E8F\u53D1\u9001\u5230 ${intent.episode} \u6210\u7247\u89C6\u56FE\u3002`;
|
|
684
|
+
if (intent.action === "focus_target") return `\u5DF2\u8BF7\u6C42 ${intent.episode} \u751F\u4EA7\u89C6\u56FE\u805A\u7126 ${intent.targetId ?? "\u76EE\u6807"}\u3002`;
|
|
685
|
+
return `\u5DF2\u628A ${intent.action} \u754C\u9762\u610F\u56FE\u53D1\u9001\u5230 ${intent.episode} \u751F\u4EA7\u5DE5\u4F5C\u53F0\u3002`;
|
|
686
|
+
}
|
|
687
|
+
function createOhStoryProductionTool() {
|
|
688
|
+
return defineTool3({
|
|
689
|
+
name: OH_STORY_PRODUCTION_TOOL_NAME,
|
|
690
|
+
description: "Operate the native oh-story short-drama production projection in the current DSH Session. It can open or focus semantic production targets, set an explicit shot order, or track a job the Agent is actually executing. It never controls cosmetic canvas layout, generates media, changes creator documents, or counts as creator confirmation for paid production.",
|
|
691
|
+
parameters: {
|
|
692
|
+
action: { type: "string", required: true, enum: PRODUCTION_INTENT_ACTIONS, description: "The exact production UI/task projection operation." },
|
|
693
|
+
episode: { type: "string", required: true, description: "Creator-first episode directory, for example \u5267\u96C6/EP001." },
|
|
694
|
+
section: { type: "string", enum: PRODUCTION_INTENT_SECTIONS },
|
|
695
|
+
targetId: { type: "string" },
|
|
696
|
+
shotIds: { type: "array", items: { type: "string" } },
|
|
697
|
+
jobId: { type: "string", description: "Stable ID that must also appear in produced output filenames." },
|
|
698
|
+
jobKind: { type: "string", enum: PRODUCTION_INTENT_JOB_KINDS },
|
|
699
|
+
expectedOutputs: { type: "integer", description: "Number of media files this job will produce. Repeat the count the creator approved when re-registering a batch; omit it to keep the count the workbench already recorded." },
|
|
700
|
+
prompt: { type: "string", description: "Exact prompt/specification for a tracked job; not a production authorization." }
|
|
701
|
+
},
|
|
702
|
+
output: {
|
|
703
|
+
schema: {
|
|
704
|
+
type: "object",
|
|
705
|
+
additionalProperties: false,
|
|
706
|
+
properties: {
|
|
707
|
+
action: { type: "string", required: true, enum: PRODUCTION_INTENT_ACTIONS },
|
|
708
|
+
episode: { type: "string", required: true },
|
|
709
|
+
message: { type: "string", required: true }
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
render: (_args, value) => [{ type: "text", text: value.message }]
|
|
713
|
+
},
|
|
714
|
+
isConcurrencySafe: () => true,
|
|
715
|
+
execute(args) {
|
|
716
|
+
const intent = validateProductionIntent(args);
|
|
717
|
+
return Promise.resolve({ action: intent.action, episode: intent.episode, message: intentMessage(intent) });
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
function registerOhStoryProductionTool(context) {
|
|
722
|
+
context.tools.register(createOhStoryProductionTool());
|
|
723
|
+
}
|
|
724
|
+
|
|
556
725
|
// src/workspace-route.ts
|
|
557
|
-
import {
|
|
726
|
+
import { createHash } from "node:crypto";
|
|
727
|
+
import { createReadStream } from "node:fs";
|
|
728
|
+
import { readFile as readNodeFile, realpath as nodeRealpath, stat as nodeStat } from "node:fs/promises";
|
|
729
|
+
import { extname as extname2, isAbsolute as isAbsolute3, relative as relative3, resolve as resolve4 } from "node:path";
|
|
730
|
+
import { execFile } from "node:child_process";
|
|
731
|
+
import { promisify } from "node:util";
|
|
558
732
|
import { FsError } from "@deepseek-ai/dsh-fs";
|
|
559
733
|
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
560
734
|
|
|
735
|
+
// src/game-verification.ts
|
|
736
|
+
var WorkspaceVerificationTracker = class {
|
|
737
|
+
#observations = /* @__PURE__ */ new Map();
|
|
738
|
+
observe(key, verificationRevision, previewVersion) {
|
|
739
|
+
const previous = this.#observations.get(key);
|
|
740
|
+
if (previous === void 0) {
|
|
741
|
+
this.#remember(key, { verificationRevision, previewVersion, bound: false });
|
|
742
|
+
return { binding: "UNBOUND" };
|
|
743
|
+
}
|
|
744
|
+
if (verificationRevision !== previous.verificationRevision) {
|
|
745
|
+
const bound = verificationRevision !== void 0;
|
|
746
|
+
this.#remember(key, { verificationRevision, previewVersion, bound });
|
|
747
|
+
return bound ? { binding: "CURRENT", verifiedPreviewVersion: previewVersion } : { binding: "UNBOUND" };
|
|
748
|
+
}
|
|
749
|
+
if (!previous.bound) return { binding: "UNBOUND" };
|
|
750
|
+
return previewVersion === previous.previewVersion ? { binding: "CURRENT", verifiedPreviewVersion: previous.previewVersion } : { binding: "STALE", verifiedPreviewVersion: previous.previewVersion };
|
|
751
|
+
}
|
|
752
|
+
#remember(key, observation) {
|
|
753
|
+
this.#observations.set(key, observation);
|
|
754
|
+
if (this.#observations.size <= 500) return;
|
|
755
|
+
const oldest = this.#observations.keys().next();
|
|
756
|
+
if (!oldest.done) this.#observations.delete(oldest.value);
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
// src/video-project.ts
|
|
761
|
+
import { basename as basename3, extname } from "node:path";
|
|
762
|
+
var VIDEO_DIRECTORY = "video-recaps";
|
|
763
|
+
var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".mkv", ".webm"]);
|
|
764
|
+
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set(["frames", "tts_segments", "asr_chunks", "chunks", "cache", "tmp", ".subtitle_measure"]);
|
|
765
|
+
var VISIBLE_TEXT_FILES = /* @__PURE__ */ new Set([
|
|
766
|
+
"project.json",
|
|
767
|
+
"recap_run_manifest.json",
|
|
768
|
+
"recap_phase.json",
|
|
769
|
+
"agent_narration_brief.md",
|
|
770
|
+
"recap_story_plan.json",
|
|
771
|
+
"visual_audio_board.json",
|
|
772
|
+
"clip_plan.json",
|
|
773
|
+
"clip_plan_validated.json",
|
|
774
|
+
"narration.json",
|
|
775
|
+
"narration_review.md",
|
|
776
|
+
"tts_meta.json",
|
|
777
|
+
"timeline.json",
|
|
778
|
+
"assembly_manifest.json",
|
|
779
|
+
"assembly_qc.json",
|
|
780
|
+
"final_qc.json",
|
|
781
|
+
"final_qc.md",
|
|
782
|
+
"delivery_qc.json",
|
|
783
|
+
"subtitles.srt",
|
|
784
|
+
"subtitles.ass"
|
|
785
|
+
]);
|
|
786
|
+
var ARTIFACT_META = {
|
|
787
|
+
"recap_run_manifest.json": { label: "\u8FD0\u884C\u6E05\u5355", kind: "manifest" },
|
|
788
|
+
"recap_phase.json": { label: "\u9636\u6BB5\u8D26\u672C", kind: "manifest" },
|
|
789
|
+
"agent_narration_brief.md": { label: "\u89E3\u8BF4 Brief", kind: "plan" },
|
|
790
|
+
"recap_story_plan.json": { label: "\u6545\u4E8B\u65B9\u6848", kind: "plan" },
|
|
791
|
+
"visual_audio_board.json": { label: "\u97F3\u753B\u65B9\u6848", kind: "plan" },
|
|
792
|
+
"clip_plan.json": { label: "\u526A\u8F91\u8BA1\u5212", kind: "plan" },
|
|
793
|
+
"clip_plan_validated.json": { label: "\u5DF2\u6821\u9A8C\u526A\u8F91\u8BA1\u5212", kind: "plan" },
|
|
794
|
+
"narration.json": { label: "\u89E3\u8BF4\u8BCD", kind: "script" },
|
|
795
|
+
"narration_review.md": { label: "\u89E3\u8BF4\u590D\u6838", kind: "quality" },
|
|
796
|
+
"timeline.json": { label: "\u6210\u7247\u65F6\u95F4\u7EBF", kind: "plan" },
|
|
797
|
+
"assembly_manifest.json": { label: "\u5408\u6210\u6E05\u5355", kind: "manifest" },
|
|
798
|
+
"assembly_qc.json": { label: "\u5408\u6210\u8D28\u68C0", kind: "quality" },
|
|
799
|
+
"final_qc.json": { label: "\u6700\u7EC8\u8D28\u68C0", kind: "quality" },
|
|
800
|
+
"final_qc.md": { label: "\u6700\u7EC8\u8D28\u68C0\u62A5\u544A", kind: "quality" },
|
|
801
|
+
"delivery_qc.json": { label: "\u4EA4\u4ED8\u8D28\u68C0", kind: "quality" },
|
|
802
|
+
"subtitles.srt": { label: "SRT \u5B57\u5E55", kind: "subtitle" },
|
|
803
|
+
"subtitles.ass": { label: "ASS \u5B57\u5E55", kind: "subtitle" }
|
|
804
|
+
};
|
|
805
|
+
function videoProjectRoot(path) {
|
|
806
|
+
const parts = path.split("/");
|
|
807
|
+
const name2 = parts[1];
|
|
808
|
+
if (parts[0] !== VIDEO_DIRECTORY || name2 === void 0 || name2 === "" || name2 === "." || name2 === ".." || name2.startsWith(".") || name2.includes("\\") || name2.length > 128) return void 0;
|
|
809
|
+
return `${VIDEO_DIRECTORY}/${name2}`;
|
|
810
|
+
}
|
|
811
|
+
function skipVideoDirectory(path) {
|
|
812
|
+
const parts = path.split("/");
|
|
813
|
+
return parts[0] === VIDEO_DIRECTORY && parts.length >= 3 && SKIPPED_DIRECTORIES.has(parts.at(-1) ?? "");
|
|
814
|
+
}
|
|
815
|
+
function visibleVideoPath(path) {
|
|
816
|
+
const root = videoProjectRoot(path);
|
|
817
|
+
if (root === void 0 || path === root) return false;
|
|
818
|
+
const name2 = basename3(path);
|
|
819
|
+
const extension = extname(name2).toLocaleLowerCase();
|
|
820
|
+
if (VISIBLE_TEXT_FILES.has(name2)) return true;
|
|
821
|
+
if (!VIDEO_EXTENSIONS.has(extension)) return false;
|
|
822
|
+
return path.startsWith(`${root}/sources/`) || path.startsWith(`${root}/outputs/`) || name2 === "edited_source.mp4" || /^recap_.+\.(?:mp4|mov|webm)$/iu.test(name2);
|
|
823
|
+
}
|
|
824
|
+
function record(value) {
|
|
825
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
826
|
+
}
|
|
827
|
+
function text(value) {
|
|
828
|
+
return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
|
|
829
|
+
}
|
|
830
|
+
function referencedMedia(files, value) {
|
|
831
|
+
const path = text(value)?.replaceAll("\\", "/");
|
|
832
|
+
if (path === void 0) return void 0;
|
|
833
|
+
const direct = files.find((file) => file.path === path || path.endsWith(`/${file.path}`));
|
|
834
|
+
if (direct !== void 0) return direct;
|
|
835
|
+
const name2 = path.split("/").at(-1);
|
|
836
|
+
const matching = files.filter((file) => file.path.split("/").at(-1) === name2);
|
|
837
|
+
return matching.length === 1 ? matching[0] : void 0;
|
|
838
|
+
}
|
|
839
|
+
function previewAsset(file, role, label) {
|
|
840
|
+
return file.kind === "media" && file.mimeType?.startsWith("video/") === true ? { role, label, path: file.path, bytes: file.bytes, version: file.version, mimeType: file.mimeType } : void 0;
|
|
841
|
+
}
|
|
842
|
+
function summarizeVideoProject(root, files, metadata2 = {}) {
|
|
843
|
+
const id = root.slice(`${VIDEO_DIRECTORY}/`.length);
|
|
844
|
+
const projectFiles = files.filter((file) => file.path.startsWith(`${root}/`));
|
|
845
|
+
const project = record(metadata2.project);
|
|
846
|
+
const manifest = record(metadata2.runManifest);
|
|
847
|
+
const settings = record(manifest?.settings);
|
|
848
|
+
const assembly = record(metadata2.assembly);
|
|
849
|
+
const source = referencedMedia(projectFiles, manifest?.source_video) ?? projectFiles.find((file) => file.kind === "media" && file.path.startsWith(`${root}/sources/`) && file.mimeType?.startsWith("video/") === true);
|
|
850
|
+
const edited = projectFiles.find((file) => file.path.split("/").at(-1) === "edited_source.mp4");
|
|
851
|
+
const final = referencedMedia(projectFiles, assembly?.final_output) ?? projectFiles.filter((file) => file.kind === "media" && (file.path.startsWith(`${root}/outputs/`) || /^recap_.+\.(?:mp4|mov|webm)$/iu.test(file.path.split("/").at(-1) ?? ""))).at(-1);
|
|
852
|
+
const previews = [
|
|
853
|
+
source === void 0 ? void 0 : previewAsset(source, "source", "\u539F\u7247"),
|
|
854
|
+
edited === void 0 ? void 0 : previewAsset(edited, "edited", "\u526A\u540E\u7247"),
|
|
855
|
+
final === void 0 ? void 0 : previewAsset(final, "final", "\u6700\u7EC8\u6210\u7247")
|
|
856
|
+
].filter((value) => value !== void 0);
|
|
857
|
+
const names = new Set(projectFiles.map((file) => file.path.split("/").at(-1)));
|
|
858
|
+
const cutMode = settings?.edit_mode === "cut" || names.has("clip_plan.json") || edited !== void 0;
|
|
859
|
+
let state = "not-started";
|
|
860
|
+
let stage = "source";
|
|
861
|
+
let stageLabel = source === void 0 ? "\u7B49\u5F85\u5BFC\u5165\u89C6\u9891" : "\u53EF\u4EE5\u5F00\u59CB";
|
|
862
|
+
let nextArtifact;
|
|
863
|
+
if (final !== void 0) {
|
|
864
|
+
state = "ready";
|
|
865
|
+
stage = "complete";
|
|
866
|
+
stageLabel = "\u6210\u7247\u5DF2\u5C31\u7EEA";
|
|
867
|
+
} else if (names.has("tts_meta.json")) {
|
|
868
|
+
state = "working";
|
|
869
|
+
stage = "assemble";
|
|
870
|
+
stageLabel = "\u6B63\u5728\u5408\u6210";
|
|
871
|
+
} else if (names.has("narration.json")) {
|
|
872
|
+
state = "working";
|
|
873
|
+
stage = "voiceover";
|
|
874
|
+
stageLabel = "\u914D\u97F3\u4E0E\u5408\u6210";
|
|
875
|
+
} else if (cutMode && edited !== void 0) {
|
|
876
|
+
state = "waiting";
|
|
877
|
+
stage = "narration";
|
|
878
|
+
stageLabel = "\u7B49\u5F85\u89E3\u8BF4\u8BCD";
|
|
879
|
+
nextArtifact = "narration.json";
|
|
880
|
+
} else if (cutMode && names.has("clip_plan.json")) {
|
|
881
|
+
state = "working";
|
|
882
|
+
stage = "cut";
|
|
883
|
+
stageLabel = "\u6B63\u5728\u526A\u8F91";
|
|
884
|
+
} else if (manifest !== void 0) {
|
|
885
|
+
state = "waiting";
|
|
886
|
+
stage = cutMode ? "clip-plan" : "narration";
|
|
887
|
+
stageLabel = cutMode ? "\u7B49\u5F85\u526A\u8F91\u8BA1\u5212" : "\u7B49\u5F85\u89E3\u8BF4\u8BCD";
|
|
888
|
+
nextArtifact = cutMode ? "clip_plan.json" : "narration.json";
|
|
889
|
+
}
|
|
890
|
+
const artifacts = projectFiles.flatMap((file) => {
|
|
891
|
+
const name2 = file.path.split("/").at(-1) ?? "";
|
|
892
|
+
const meta = ARTIFACT_META[name2];
|
|
893
|
+
return meta === void 0 ? [] : [{ ...meta, path: file.path, version: file.version }];
|
|
894
|
+
}).sort((left, right) => left.path.localeCompare(right.path, "zh-Hans-CN"));
|
|
895
|
+
return {
|
|
896
|
+
id,
|
|
897
|
+
root,
|
|
898
|
+
title: text(project?.title) ?? id,
|
|
899
|
+
state,
|
|
900
|
+
stage,
|
|
901
|
+
stageLabel,
|
|
902
|
+
nextArtifact,
|
|
903
|
+
previews,
|
|
904
|
+
artifacts
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
|
|
561
908
|
// src/workspace-request-trust.ts
|
|
562
909
|
function header(headers, name2) {
|
|
563
910
|
if (headers instanceof Headers) return headers.get(name2) ?? void 0;
|
|
@@ -607,14 +954,45 @@ function isTrustedWorkspaceRequest(request, trustedHosts) {
|
|
|
607
954
|
return false;
|
|
608
955
|
}
|
|
609
956
|
}
|
|
957
|
+
function isTrustedPreviewNavigation(request, trustedHosts) {
|
|
958
|
+
if (isTrustedWorkspaceRequest(request, trustedHosts)) return true;
|
|
959
|
+
const authority = header(request.headers, "host");
|
|
960
|
+
if (authority === void 0) return false;
|
|
961
|
+
const host = parseAuthority(authority);
|
|
962
|
+
if (host === void 0 || !isLoopbackHostname(host.hostname) && !isTrustedAuthority(host, trustedHosts)) return false;
|
|
963
|
+
return header(request.headers, "origin") === void 0 && header(request.headers, "sec-fetch-site") === "cross-site" && header(request.headers, "sec-fetch-mode") === "navigate" && ["document", "iframe"].includes(header(request.headers, "sec-fetch-dest") ?? "");
|
|
964
|
+
}
|
|
610
965
|
|
|
611
966
|
// src/workspace-route.ts
|
|
612
967
|
var STORY_DIRECTORIES = ["\u6B63\u6587", "\u5927\u7EB2", "\u8BBE\u5B9A", "\u8FFD\u8E2A", "\u5BF9\u6807", "\u53C2\u8003\u8D44\u6599"];
|
|
613
968
|
var DRAMA_DIRECTORIES = ["\u8F93\u5165", "\u9879\u76EE\u5F00\u53D1", "\u8BBE\u5B9A\u96C6", "\u5267\u96C6", "\u4EA4\u4ED8", "\u521B\u4F5C\u8005\u51B3\u7B56", "\u5BA1\u67E5"];
|
|
614
|
-
var
|
|
969
|
+
var GAME_DIRECTORY = "game-adaptations";
|
|
970
|
+
var CREATIVE_DIRECTORIES = [VIDEO_DIRECTORY, GAME_DIRECTORY, ...STORY_DIRECTORIES, ...DRAMA_DIRECTORIES];
|
|
615
971
|
var ROOT_FILES = /* @__PURE__ */ new Set(["short-drama.json"]);
|
|
616
972
|
var EDITABLE_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".txt", ".json", ".jsonl"]);
|
|
973
|
+
var GAME_EDITABLE_EXTENSIONS = /* @__PURE__ */ new Set([...EDITABLE_EXTENSIONS, ".html", ".css", ".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx"]);
|
|
974
|
+
var VIDEO_EDITABLE_EXTENSIONS = /* @__PURE__ */ new Set([...EDITABLE_EXTENSIONS, ".srt", ".ass"]);
|
|
975
|
+
var MEDIA_TYPES = /* @__PURE__ */ new Map([
|
|
976
|
+
[".png", "image/png"],
|
|
977
|
+
[".jpg", "image/jpeg"],
|
|
978
|
+
[".jpeg", "image/jpeg"],
|
|
979
|
+
[".webp", "image/webp"],
|
|
980
|
+
[".gif", "image/gif"],
|
|
981
|
+
[".mp4", "video/mp4"],
|
|
982
|
+
[".webm", "video/webm"],
|
|
983
|
+
[".mov", "video/quicktime"],
|
|
984
|
+
[".mkv", "video/x-matroska"],
|
|
985
|
+
[".mp3", "audio/mpeg"],
|
|
986
|
+
[".wav", "audio/wav"],
|
|
987
|
+
[".m4a", "audio/mp4"]
|
|
988
|
+
]);
|
|
989
|
+
var MEDIA_MAX_BYTES = 256 * 1024 * 1024;
|
|
617
990
|
var FILE_LIMIT = 1e3;
|
|
991
|
+
var PREVIEW_FILE_LIMIT = 32 * 1024 * 1024;
|
|
992
|
+
var BUNDLED_GAME_EXAMPLE = "jin-ping-mei";
|
|
993
|
+
var workspaceVerificationTracker = new WorkspaceVerificationTracker();
|
|
994
|
+
var execFileAsync = promisify(execFile);
|
|
995
|
+
var videoPreflightCache;
|
|
618
996
|
var WorkspaceHttpError = class extends Error {
|
|
619
997
|
constructor(status, message) {
|
|
620
998
|
super(message);
|
|
@@ -622,6 +1000,53 @@ var WorkspaceHttpError = class extends Error {
|
|
|
622
1000
|
}
|
|
623
1001
|
status;
|
|
624
1002
|
};
|
|
1003
|
+
async function commandOutput(command, args) {
|
|
1004
|
+
try {
|
|
1005
|
+
const { stdout, stderr } = await execFileAsync(command, [...args], { encoding: "utf8", timeout: 5e3, maxBuffer: 4 * 1024 * 1024 });
|
|
1006
|
+
return `${stdout}${stderr}`.trim();
|
|
1007
|
+
} catch {
|
|
1008
|
+
return void 0;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
function pythonVersion(value) {
|
|
1012
|
+
const match = /Python\s+(\d+)\.(\d+)(?:\.(\d+))?/u.exec(value ?? "");
|
|
1013
|
+
if (match === null) return { ok: false };
|
|
1014
|
+
const major = Number(match[1]);
|
|
1015
|
+
const minor = Number(match[2]);
|
|
1016
|
+
return { ok: major > 3 || major === 3 && minor >= 10, version: match[0].replace(/^Python\s+/u, "") };
|
|
1017
|
+
}
|
|
1018
|
+
function configured(...names) {
|
|
1019
|
+
return names.some((name2) => (process.env[name2] ?? "") !== "");
|
|
1020
|
+
}
|
|
1021
|
+
async function videoPreflight() {
|
|
1022
|
+
if (videoPreflightCache !== void 0 && videoPreflightCache.expires > Date.now()) return videoPreflightCache.value;
|
|
1023
|
+
let python = { ok: false };
|
|
1024
|
+
for (const command of ["python3", "python"]) {
|
|
1025
|
+
const parsed = pythonVersion(await commandOutput(command, ["--version"]));
|
|
1026
|
+
if (parsed.version !== void 0) {
|
|
1027
|
+
python = parsed;
|
|
1028
|
+
break;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
const [ffmpegOutput, ffmpegFilters, ffprobeOutput] = await Promise.all([
|
|
1032
|
+
commandOutput("ffmpeg", ["-version"]),
|
|
1033
|
+
commandOutput("ffmpeg", ["-hide_banner", "-filters"]),
|
|
1034
|
+
commandOutput("ffprobe", ["-version"])
|
|
1035
|
+
]);
|
|
1036
|
+
const value = {
|
|
1037
|
+
python,
|
|
1038
|
+
ffmpeg: { ok: ffmpegOutput !== void 0, subtitles: /\bsubtitles\b/u.test(ffmpegFilters ?? "") },
|
|
1039
|
+
ffprobe: { ok: ffprobeOutput !== void 0 },
|
|
1040
|
+
credentials: {
|
|
1041
|
+
// Upstream falls back to the shared MIMO_API_KEY only when a per-service key is unset.
|
|
1042
|
+
mimo: configured("MIMO_API_KEY", "MIMO_VIDEO_API_KEY", "MIMO_TTS_API_KEY", "MIMO_ASR_API_KEY"),
|
|
1043
|
+
fish: configured("FISH_API_KEY"),
|
|
1044
|
+
ttsProvider: process.env.TTS_PROVIDER ?? "mimo"
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
videoPreflightCache = { expires: Date.now() + 3e4, value };
|
|
1048
|
+
return value;
|
|
1049
|
+
}
|
|
625
1050
|
function send(response, status, value) {
|
|
626
1051
|
const body = `${JSON.stringify(value)}
|
|
627
1052
|
`;
|
|
@@ -633,6 +1058,87 @@ function send(response, status, value) {
|
|
|
633
1058
|
});
|
|
634
1059
|
response.end(body);
|
|
635
1060
|
}
|
|
1061
|
+
function parseByteRange(value, size) {
|
|
1062
|
+
const match = value === void 0 ? null : /^bytes=(\d*)-(\d*)$/u.exec(value.trim());
|
|
1063
|
+
if (match === null) return void 0;
|
|
1064
|
+
const left = match[1] ?? "";
|
|
1065
|
+
const right = match[2] ?? "";
|
|
1066
|
+
if (left === "") {
|
|
1067
|
+
const suffix = Number(right);
|
|
1068
|
+
if (right === "" || !Number.isSafeInteger(suffix)) return void 0;
|
|
1069
|
+
return suffix === 0 || size <= 0 ? null : { start: Math.max(0, size - suffix), end: size - 1 };
|
|
1070
|
+
}
|
|
1071
|
+
const start = Number(left);
|
|
1072
|
+
const requestedEnd = right === "" ? Number.MAX_SAFE_INTEGER : Number(right);
|
|
1073
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(requestedEnd) || requestedEnd < start) return void 0;
|
|
1074
|
+
return start >= size ? null : { start, end: Math.min(requestedEnd, size - 1) };
|
|
1075
|
+
}
|
|
1076
|
+
function mediaHeaders(mimeType, size, range) {
|
|
1077
|
+
const start = range?.start ?? 0;
|
|
1078
|
+
const end = range?.end ?? size - 1;
|
|
1079
|
+
return {
|
|
1080
|
+
"content-type": mimeType,
|
|
1081
|
+
"content-length": Math.max(0, end - start + 1),
|
|
1082
|
+
"cache-control": "private, max-age=60",
|
|
1083
|
+
"accept-ranges": "bytes",
|
|
1084
|
+
...range === void 0 ? {} : { "content-range": `bytes ${String(start)}-${String(end)}/${String(size)}` },
|
|
1085
|
+
"x-content-type-options": "nosniff"
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
function sendMediaBytes(request, response, bytes, mimeType) {
|
|
1089
|
+
const range = parseByteRange(typeof request.headers.range === "string" ? request.headers.range : void 0, bytes.byteLength);
|
|
1090
|
+
if (range === null) {
|
|
1091
|
+
response.writeHead(416, { "content-range": `bytes */${String(bytes.byteLength)}`, "cache-control": "no-store" });
|
|
1092
|
+
response.end();
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
const start = range?.start ?? 0;
|
|
1096
|
+
const end = range?.end ?? bytes.byteLength - 1;
|
|
1097
|
+
const body = bytes.subarray(start, end + 1);
|
|
1098
|
+
response.writeHead(range === void 0 ? 200 : 206, mediaHeaders(mimeType, bytes.byteLength, range));
|
|
1099
|
+
response.end(request.method === "HEAD" ? void 0 : Buffer.from(body));
|
|
1100
|
+
}
|
|
1101
|
+
async function hostWorkspaceFile(realm, target2, expectedSize) {
|
|
1102
|
+
const [root, path] = await Promise.all([
|
|
1103
|
+
nodeRealpath(realm.fs.processPath(realm.root)),
|
|
1104
|
+
nodeRealpath(realm.fs.processPath(target2))
|
|
1105
|
+
]);
|
|
1106
|
+
const inside = relative3(root, path);
|
|
1107
|
+
if (inside === "" || inside.startsWith("..") || isAbsolute3(inside)) return void 0;
|
|
1108
|
+
const local = await nodeStat(path);
|
|
1109
|
+
return local.isFile() && local.size === expectedSize ? path : void 0;
|
|
1110
|
+
}
|
|
1111
|
+
async function sendWorkspaceMedia(request, response, realm, target2, info, mimeType) {
|
|
1112
|
+
const expectedSize = info.size;
|
|
1113
|
+
if (expectedSize !== void 0) {
|
|
1114
|
+
const processPath = await hostWorkspaceFile(realm, target2, expectedSize).catch(() => void 0);
|
|
1115
|
+
if (processPath !== void 0) {
|
|
1116
|
+
const range = parseByteRange(typeof request.headers.range === "string" ? request.headers.range : void 0, expectedSize);
|
|
1117
|
+
if (range === null) {
|
|
1118
|
+
response.writeHead(416, { "content-range": `bytes */${String(expectedSize)}`, "cache-control": "no-store" });
|
|
1119
|
+
response.end();
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
response.writeHead(range === void 0 ? 200 : 206, mediaHeaders(mimeType, expectedSize, range));
|
|
1123
|
+
if (request.method === "HEAD") {
|
|
1124
|
+
response.end();
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
const stream = createReadStream(processPath, range === void 0 ? void 0 : { start: range.start, end: range.end });
|
|
1128
|
+
request.once("close", () => {
|
|
1129
|
+
if (!response.writableEnded) stream.destroy();
|
|
1130
|
+
});
|
|
1131
|
+
stream.on("error", () => {
|
|
1132
|
+
if (!response.headersSent) response.destroy();
|
|
1133
|
+
else response.end();
|
|
1134
|
+
});
|
|
1135
|
+
stream.pipe(response);
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
if (expectedSize > MEDIA_MAX_BYTES) throw new WorkspaceHttpError(413, "\u8FDC\u7A0B\u5A92\u4F53\u8D85\u8FC7\u5DE5\u4F5C\u53F0\u56DE\u9000\u9884\u89C8\u5927\u5C0F\u9650\u5236\u3002");
|
|
1139
|
+
}
|
|
1140
|
+
sendMediaBytes(request, response, await realm.fs.readBytes(target2, void 0, MEDIA_MAX_BYTES), mimeType);
|
|
1141
|
+
}
|
|
636
1142
|
async function jsonBody(request, maxBytes) {
|
|
637
1143
|
const chunks = [];
|
|
638
1144
|
let size = 0;
|
|
@@ -650,11 +1156,18 @@ async function jsonBody(request, maxBytes) {
|
|
|
650
1156
|
throw new WorkspaceHttpError(400, "\u8BF7\u6C42\u5FC5\u987B\u662F JSON \u5BF9\u8C61\u3002");
|
|
651
1157
|
}
|
|
652
1158
|
}
|
|
653
|
-
function
|
|
654
|
-
|
|
655
|
-
|
|
1159
|
+
function safeRelativePath(path) {
|
|
1160
|
+
return path !== "" && !path.startsWith("/") && !path.includes("\\") && !path.split("/").some((segment) => segment === "" || segment === "." || segment === "..");
|
|
1161
|
+
}
|
|
1162
|
+
function editablePath(path) {
|
|
1163
|
+
const extensions = path.startsWith(`${GAME_DIRECTORY}/`) ? GAME_EDITABLE_EXTENSIONS : path.startsWith(`${VIDEO_DIRECTORY}/`) ? VIDEO_EDITABLE_EXTENSIONS : EDITABLE_EXTENSIONS;
|
|
1164
|
+
return extensions.has(extname2(path).toLocaleLowerCase());
|
|
1165
|
+
}
|
|
1166
|
+
function assertCreativePath(path, kind) {
|
|
1167
|
+
if (kind === "text" ? !editablePath(path) : !MEDIA_TYPES.has(extname2(path).toLocaleLowerCase())) {
|
|
1168
|
+
throw new WorkspaceHttpError(415, kind === "text" ? "\u5DE5\u4F5C\u53F0\u4E0D\u652F\u6301\u7F16\u8F91\u8BE5\u6587\u4EF6\u7C7B\u578B\u3002" : "\u76EE\u6807\u4E0D\u662F\u53D7\u652F\u6301\u7684\u77ED\u5267\u5A92\u4F53\u6587\u4EF6\u3002");
|
|
656
1169
|
}
|
|
657
|
-
if (
|
|
1170
|
+
if (!safeRelativePath(path)) {
|
|
658
1171
|
throw new WorkspaceHttpError(403, "\u6587\u4EF6\u8DEF\u5F84\u4E0D\u5728\u521B\u4F5C\u5DE5\u4F5C\u53F0\u4E2D\u3002");
|
|
659
1172
|
}
|
|
660
1173
|
const root = path.split("/", 1)[0];
|
|
@@ -662,9 +1175,11 @@ function assertCreativePath(path) {
|
|
|
662
1175
|
throw new WorkspaceHttpError(403, "\u6587\u4EF6\u8DEF\u5F84\u4E0D\u5728\u521B\u4F5C\u5DE5\u4F5C\u53F0\u4E2D\u3002");
|
|
663
1176
|
}
|
|
664
1177
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
1178
|
+
function mediaMimeTypeForPath(path) {
|
|
1179
|
+
return MEDIA_TYPES.get(extname2(path).toLocaleLowerCase());
|
|
1180
|
+
}
|
|
1181
|
+
async function workspaceRealmForSession(context, rawId) {
|
|
1182
|
+
if (rawId === "") throw new WorkspaceHttpError(400, "\u7F3A\u5C11 DSH sessionId\u3002");
|
|
668
1183
|
const lookup = context.typert.lookups.get("agent");
|
|
669
1184
|
if (lookup === void 0) throw new WorkspaceHttpError(503, "DSH Agent lookup \u5F53\u524D\u4E0D\u53EF\u7528\u3002");
|
|
670
1185
|
let agent;
|
|
@@ -684,8 +1199,13 @@ async function workspaceRealm(context, url) {
|
|
|
684
1199
|
if (fs === void 0 || sandboxPolicy === void 0) throw new WorkspaceHttpError(503, "DSH \u6587\u4EF6\u7CFB\u7EDF\u5F53\u524D\u4E0D\u53EF\u7528\u3002");
|
|
685
1200
|
return { agent, fs, sandboxPolicy, cwd, root: await fs.resolve(cwd) };
|
|
686
1201
|
}
|
|
687
|
-
async function
|
|
688
|
-
|
|
1202
|
+
async function workspaceRealm(context, url) {
|
|
1203
|
+
const rawId = url.searchParams.get("sessionId");
|
|
1204
|
+
if (rawId === null) throw new WorkspaceHttpError(400, "\u7F3A\u5C11 DSH sessionId\u3002");
|
|
1205
|
+
return workspaceRealmForSession(context, rawId);
|
|
1206
|
+
}
|
|
1207
|
+
async function creativeTarget(realm, path, kind = "text") {
|
|
1208
|
+
assertCreativePath(path, kind);
|
|
689
1209
|
const target2 = await realm.fs.resolve(path, { cwd: realm.cwd });
|
|
690
1210
|
if (!realm.fs.contains(realm.root, target2)) throw new WorkspaceHttpError(403, "\u6587\u4EF6\u8DEF\u5F84\u79BB\u5F00\u4E86 DSH \u5DE5\u4F5C\u76EE\u5F55\u3002");
|
|
691
1211
|
return target2;
|
|
@@ -718,11 +1238,15 @@ async function listFiles(realm) {
|
|
|
718
1238
|
for (const entry of await realm.fs.listDir(directory)) {
|
|
719
1239
|
if (entry.name.startsWith(".") || !realm.fs.contains(realm.root, entry.target)) continue;
|
|
720
1240
|
const childPath = `${path}/${entry.name}`;
|
|
721
|
-
if (entry.type === "directory")
|
|
722
|
-
|
|
1241
|
+
if (entry.type === "directory") {
|
|
1242
|
+
if (!skipVideoDirectory(childPath)) await walk(childPath, entry.target);
|
|
1243
|
+
} else if (entry.type === "file" && (editablePath(childPath) || MEDIA_TYPES.has(extname2(entry.name).toLocaleLowerCase()))) {
|
|
723
1244
|
const info = entry.version === void 0 || entry.size === void 0 ? await realm.fs.stat(entry.target) : void 0;
|
|
724
1245
|
const version = entry.version ?? info?.version;
|
|
725
|
-
|
|
1246
|
+
const mimeType = MEDIA_TYPES.get(extname2(entry.name).toLocaleLowerCase());
|
|
1247
|
+
if (version !== void 0 && (!childPath.startsWith(`${VIDEO_DIRECTORY}/`) || visibleVideoPath(childPath))) {
|
|
1248
|
+
files.push({ path: childPath, bytes: entry.size ?? info?.size ?? 0, version, kind: mimeType === void 0 ? "text" : "media", mimeType });
|
|
1249
|
+
}
|
|
726
1250
|
}
|
|
727
1251
|
if (files.length >= FILE_LIMIT) return;
|
|
728
1252
|
}
|
|
@@ -737,7 +1261,7 @@ async function listFiles(realm) {
|
|
|
737
1261
|
for (const path of ROOT_FILES) {
|
|
738
1262
|
const target2 = await creativeTarget(realm, path);
|
|
739
1263
|
const info = await realm.fs.stat(target2);
|
|
740
|
-
if (info?.type === "file") files.push({ path, bytes: info.size ?? 0, version: info.version });
|
|
1264
|
+
if (info?.type === "file") files.push({ path, bytes: info.size ?? 0, version: info.version, kind: "text" });
|
|
741
1265
|
}
|
|
742
1266
|
return files.sort((left, right) => left.path.localeCompare(right.path, "zh-Hans-CN"));
|
|
743
1267
|
}
|
|
@@ -750,6 +1274,276 @@ async function metadata(realm, files, path, maxBytes) {
|
|
|
750
1274
|
return { value: null, error: error instanceof SyntaxError ? `${path} \u4E0D\u662F\u6709\u6548\u7684 JSON\u3002` : `${path} \u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u3002` };
|
|
751
1275
|
}
|
|
752
1276
|
}
|
|
1277
|
+
function token(value) {
|
|
1278
|
+
return Buffer.from(value, "utf8").toString("base64url");
|
|
1279
|
+
}
|
|
1280
|
+
function untoken(value) {
|
|
1281
|
+
try {
|
|
1282
|
+
return Buffer.from(value, "base64url").toString("utf8");
|
|
1283
|
+
} catch {
|
|
1284
|
+
throw new WorkspaceHttpError(400, "\u6E38\u620F\u9884\u89C8\u6807\u8BC6\u65E0\u6548\u3002");
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
function gameRoot(path) {
|
|
1288
|
+
const parts = path.split("/");
|
|
1289
|
+
const name2 = parts[1];
|
|
1290
|
+
return parts.length === 2 && parts[0] === GAME_DIRECTORY && name2 !== void 0 && name2 !== "" && name2 !== "." && name2 !== ".." && name2.length <= 128 && !name2.startsWith(".") && !name2.includes("\\");
|
|
1291
|
+
}
|
|
1292
|
+
function normalizedVerification(value, binding, verifiedPreviewVersion) {
|
|
1293
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1294
|
+
return { status: "NOT_RUN", checks: {}, limitations: [], binding };
|
|
1295
|
+
}
|
|
1296
|
+
const record2 = value;
|
|
1297
|
+
const status = record2.status === "PASS" || record2.status === "FAIL" ? record2.status : "NOT_RUN";
|
|
1298
|
+
const rawChecks = typeof record2.checks === "object" && record2.checks !== null && !Array.isArray(record2.checks) ? record2.checks : {};
|
|
1299
|
+
const checks = {};
|
|
1300
|
+
for (const name2 of ["launch", "render", "input", "coreLoop", "outcome", "restart"]) {
|
|
1301
|
+
const check = rawChecks[name2];
|
|
1302
|
+
checks[name2] = check === "PASS" || check === "FAIL" ? check : "NOT_RUN";
|
|
1303
|
+
}
|
|
1304
|
+
const completeRun = typeof record2.completeRun === "object" && record2.completeRun !== null && !Array.isArray(record2.completeRun) ? record2.completeRun : {};
|
|
1305
|
+
const limitations = Array.isArray(record2.limitations) ? record2.limitations.flatMap((entry) => {
|
|
1306
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return [];
|
|
1307
|
+
const item = entry;
|
|
1308
|
+
return typeof item.scope === "string" && typeof item.reason === "string" ? [{ scope: item.scope, reason: item.reason }] : [];
|
|
1309
|
+
}) : [];
|
|
1310
|
+
return {
|
|
1311
|
+
status,
|
|
1312
|
+
checks,
|
|
1313
|
+
runId: typeof completeRun.id === "string" ? completeRun.id : void 0,
|
|
1314
|
+
limitations,
|
|
1315
|
+
binding,
|
|
1316
|
+
verifiedPreviewVersion
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
async function workspaceText(realm, path, maxBytes) {
|
|
1320
|
+
const target2 = await realm.fs.resolve(path, { cwd: realm.cwd });
|
|
1321
|
+
if (!realm.fs.contains(realm.root, target2) || (await realm.fs.stat(target2))?.type !== "file") return void 0;
|
|
1322
|
+
return (await readVersionedFile(realm.fs, target2, maxBytes)).content;
|
|
1323
|
+
}
|
|
1324
|
+
async function previewDigest(realm, projectRoot) {
|
|
1325
|
+
const appPath = `${projectRoot}/build/app`;
|
|
1326
|
+
const app = await realm.fs.resolve(appPath, { cwd: realm.cwd });
|
|
1327
|
+
if (!realm.fs.contains(realm.root, app) || (await realm.fs.stat(app))?.type !== "directory") return { ready: false, version: "missing" };
|
|
1328
|
+
const entries = [];
|
|
1329
|
+
let ready = false;
|
|
1330
|
+
const visit = async (directory, path) => {
|
|
1331
|
+
for (const entry of await realm.fs.listDir(directory)) {
|
|
1332
|
+
if (entry.name.startsWith(".") || !realm.fs.contains(app, entry.target)) continue;
|
|
1333
|
+
const childPath = path === "" ? entry.name : `${path}/${entry.name}`;
|
|
1334
|
+
if (entry.type === "directory") await visit(entry.target, childPath);
|
|
1335
|
+
else if (entry.type === "file") {
|
|
1336
|
+
const info = entry.version === void 0 ? await realm.fs.stat(entry.target) : void 0;
|
|
1337
|
+
const version = entry.version ?? info?.version;
|
|
1338
|
+
if (version !== void 0) entries.push(`${childPath}\0${version}`);
|
|
1339
|
+
if (childPath === "index.html") ready = true;
|
|
1340
|
+
}
|
|
1341
|
+
if (entries.length >= 5e3) return;
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
await visit(app, "");
|
|
1345
|
+
return { ready, version: createHash("sha256").update(entries.sort().join("\n")).digest("hex").slice(0, 16) };
|
|
1346
|
+
}
|
|
1347
|
+
function headingTitle(content, fallback) {
|
|
1348
|
+
const heading = content?.split(/\r?\n/u).find((line) => /^#\s+/u.test(line));
|
|
1349
|
+
return heading?.replace(/^#\s+/u, "").replace(/^PRODUCT_BRIEF\s*[·・:]?\s*/iu, "").trim() || fallback;
|
|
1350
|
+
}
|
|
1351
|
+
async function workspaceGameProjects(realm, files, sessionId, maxBytes) {
|
|
1352
|
+
const roots = [...new Set(files.flatMap((file) => {
|
|
1353
|
+
const parts = file.path.split("/");
|
|
1354
|
+
return parts[0] === GAME_DIRECTORY && parts[1] !== void 0 ? [`${GAME_DIRECTORY}/${parts[1]}`] : [];
|
|
1355
|
+
}))].filter(gameRoot).sort();
|
|
1356
|
+
return Promise.all(roots.map(async (root) => {
|
|
1357
|
+
const id = root.slice(`${GAME_DIRECTORY}/`.length);
|
|
1358
|
+
const qaPath = `${root}/qa/verification.json`;
|
|
1359
|
+
const qaFile = files.find((file) => file.path === qaPath);
|
|
1360
|
+
const [brief, qa, preview] = await Promise.all([
|
|
1361
|
+
workspaceText(realm, `${root}/PRODUCT_BRIEF.md`, maxBytes).catch(() => void 0),
|
|
1362
|
+
workspaceText(realm, qaPath, maxBytes).catch(() => void 0),
|
|
1363
|
+
previewDigest(realm, root).catch(() => ({ ready: false, version: "unavailable" }))
|
|
1364
|
+
]);
|
|
1365
|
+
let verification;
|
|
1366
|
+
try {
|
|
1367
|
+
verification = qa === void 0 ? void 0 : JSON.parse(qa);
|
|
1368
|
+
} catch {
|
|
1369
|
+
verification = void 0;
|
|
1370
|
+
}
|
|
1371
|
+
const freshness = workspaceVerificationTracker.observe(`${sessionId}\0${root}`, qaFile?.version, preview.version);
|
|
1372
|
+
return {
|
|
1373
|
+
id: `workspace:${id}`,
|
|
1374
|
+
root,
|
|
1375
|
+
title: headingTitle(brief, id),
|
|
1376
|
+
source: "workspace",
|
|
1377
|
+
previewReady: preview.ready,
|
|
1378
|
+
previewUrl: preview.ready ? `/oh-story/game-preview/workspace/${token(sessionId)}/${token(root)}/index.html` : void 0,
|
|
1379
|
+
previewVersion: preview.version,
|
|
1380
|
+
verification: normalizedVerification(verification, freshness.binding, freshness.verifiedPreviewVersion)
|
|
1381
|
+
};
|
|
1382
|
+
}));
|
|
1383
|
+
}
|
|
1384
|
+
async function workspaceVideoProjects(realm, files, maxBytes) {
|
|
1385
|
+
const roots = [...new Set(files.flatMap((file) => {
|
|
1386
|
+
const root = videoProjectRoot(file.path);
|
|
1387
|
+
return root === void 0 ? [] : [root];
|
|
1388
|
+
}))].sort((left, right) => left.localeCompare(right, "zh-Hans-CN"));
|
|
1389
|
+
return Promise.all(roots.map(async (root) => {
|
|
1390
|
+
const findPath = (name2) => files.find((file) => file.path.startsWith(`${root}/`) && file.path.split("/").at(-1) === name2)?.path;
|
|
1391
|
+
const readJson = async (name2) => {
|
|
1392
|
+
const path = findPath(name2);
|
|
1393
|
+
if (path === void 0) return void 0;
|
|
1394
|
+
const content = await workspaceText(realm, path, maxBytes).catch(() => void 0);
|
|
1395
|
+
if (content === void 0) return void 0;
|
|
1396
|
+
try {
|
|
1397
|
+
return JSON.parse(content);
|
|
1398
|
+
} catch {
|
|
1399
|
+
return void 0;
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
const [project, runManifest, assembly] = await Promise.all([
|
|
1403
|
+
readJson("project.json"),
|
|
1404
|
+
readJson("recap_run_manifest.json"),
|
|
1405
|
+
readJson("assembly_manifest.json")
|
|
1406
|
+
]);
|
|
1407
|
+
return summarizeVideoProject(root, files, { project, runManifest, assembly });
|
|
1408
|
+
}));
|
|
1409
|
+
}
|
|
1410
|
+
function bundledExampleRoot() {
|
|
1411
|
+
return resolve4(defaultNovelToGameSkillRoot(), `../examples/${BUNDLED_GAME_EXAMPLE}`);
|
|
1412
|
+
}
|
|
1413
|
+
async function bundledGameExample() {
|
|
1414
|
+
const root = bundledExampleRoot();
|
|
1415
|
+
const [example, verification, manifest] = await Promise.all([
|
|
1416
|
+
readNodeFile(resolve4(root, "example.json"), "utf8"),
|
|
1417
|
+
readNodeFile(resolve4(root, "qa/verification.json"), "utf8"),
|
|
1418
|
+
readNodeFile(resolve4(defaultNovelToGameSkillRoot(), "../manifest.json"), "utf8")
|
|
1419
|
+
]);
|
|
1420
|
+
const exampleJson = JSON.parse(example);
|
|
1421
|
+
const manifestJson = JSON.parse(manifest);
|
|
1422
|
+
const previewVersion = typeof manifestJson.upstream?.commit === "string" ? manifestJson.upstream.commit.slice(0, 16) : "bundled";
|
|
1423
|
+
return {
|
|
1424
|
+
id: `example:${BUNDLED_GAME_EXAMPLE}`,
|
|
1425
|
+
root: `examples/${BUNDLED_GAME_EXAMPLE}`,
|
|
1426
|
+
title: typeof exampleJson.title === "string" ? exampleJson.title : "\u91D1\u74F6\u6885 \xB7 \u98CE\u6708\u603B\u8D26",
|
|
1427
|
+
source: "example",
|
|
1428
|
+
previewReady: true,
|
|
1429
|
+
previewUrl: `/oh-story/game-preview/example/${BUNDLED_GAME_EXAMPLE}/index.html`,
|
|
1430
|
+
previewVersion,
|
|
1431
|
+
verification: normalizedVerification(JSON.parse(verification), "PINNED", previewVersion)
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
function previewContentType(path) {
|
|
1435
|
+
switch (extname2(path).toLocaleLowerCase()) {
|
|
1436
|
+
case ".html":
|
|
1437
|
+
return "text/html; charset=utf-8";
|
|
1438
|
+
case ".css":
|
|
1439
|
+
return "text/css; charset=utf-8";
|
|
1440
|
+
case ".js":
|
|
1441
|
+
case ".mjs":
|
|
1442
|
+
return "text/javascript; charset=utf-8";
|
|
1443
|
+
case ".json":
|
|
1444
|
+
return "application/json; charset=utf-8";
|
|
1445
|
+
case ".svg":
|
|
1446
|
+
return "image/svg+xml";
|
|
1447
|
+
case ".png":
|
|
1448
|
+
return "image/png";
|
|
1449
|
+
case ".jpg":
|
|
1450
|
+
case ".jpeg":
|
|
1451
|
+
return "image/jpeg";
|
|
1452
|
+
case ".webp":
|
|
1453
|
+
return "image/webp";
|
|
1454
|
+
case ".gif":
|
|
1455
|
+
return "image/gif";
|
|
1456
|
+
case ".woff":
|
|
1457
|
+
return "font/woff";
|
|
1458
|
+
case ".woff2":
|
|
1459
|
+
return "font/woff2";
|
|
1460
|
+
case ".wasm":
|
|
1461
|
+
return "application/wasm";
|
|
1462
|
+
case ".mp3":
|
|
1463
|
+
return "audio/mpeg";
|
|
1464
|
+
case ".ogg":
|
|
1465
|
+
return "audio/ogg";
|
|
1466
|
+
default:
|
|
1467
|
+
return "application/octet-stream";
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
function previewAssetSources(request) {
|
|
1471
|
+
const authority = request.headers.host;
|
|
1472
|
+
if (authority === void 0) return "'none'";
|
|
1473
|
+
const prefix = `${authority}/oh-story/game-preview/`;
|
|
1474
|
+
return `http://${prefix} https://${prefix}`;
|
|
1475
|
+
}
|
|
1476
|
+
function previewContentSecurityPolicy(assets) {
|
|
1477
|
+
return [
|
|
1478
|
+
"sandbox allow-scripts allow-forms allow-modals allow-downloads allow-same-origin",
|
|
1479
|
+
"default-src 'none'",
|
|
1480
|
+
`script-src 'unsafe-inline' 'wasm-unsafe-eval' blob: ${assets}`,
|
|
1481
|
+
`style-src 'unsafe-inline' ${assets}`,
|
|
1482
|
+
`img-src data: blob: ${assets}`,
|
|
1483
|
+
`media-src data: blob: ${assets}`,
|
|
1484
|
+
`font-src data: ${assets}`,
|
|
1485
|
+
`connect-src ${assets}`,
|
|
1486
|
+
`worker-src blob: ${assets}`,
|
|
1487
|
+
`manifest-src ${assets}`,
|
|
1488
|
+
"object-src 'none'",
|
|
1489
|
+
"base-uri 'none'",
|
|
1490
|
+
"form-action 'none'",
|
|
1491
|
+
"frame-ancestors 'self' http://127.0.0.1:* http://localhost:*"
|
|
1492
|
+
].join("; ");
|
|
1493
|
+
}
|
|
1494
|
+
function sendPreview(request, response, path, bytes) {
|
|
1495
|
+
const assets = previewAssetSources(request);
|
|
1496
|
+
response.writeHead(200, {
|
|
1497
|
+
"content-type": previewContentType(path),
|
|
1498
|
+
"content-length": bytes.byteLength,
|
|
1499
|
+
"cache-control": "no-store",
|
|
1500
|
+
"x-content-type-options": "nosniff",
|
|
1501
|
+
"cross-origin-resource-policy": "cross-origin",
|
|
1502
|
+
"access-control-allow-origin": "*",
|
|
1503
|
+
"content-security-policy": previewContentSecurityPolicy(assets)
|
|
1504
|
+
});
|
|
1505
|
+
response.end(bytes);
|
|
1506
|
+
}
|
|
1507
|
+
async function previewBytes(context, pathname) {
|
|
1508
|
+
let segments;
|
|
1509
|
+
try {
|
|
1510
|
+
segments = pathname.split("/").slice(3).map((segment) => decodeURIComponent(segment));
|
|
1511
|
+
} catch {
|
|
1512
|
+
throw new WorkspaceHttpError(400, "\u6E38\u620F\u9884\u89C8\u5730\u5740\u65E0\u6548\u3002");
|
|
1513
|
+
}
|
|
1514
|
+
const kind = segments.shift();
|
|
1515
|
+
if (kind === "example") {
|
|
1516
|
+
const id = segments.shift();
|
|
1517
|
+
const path = segments.join("/") || "index.html";
|
|
1518
|
+
if (id !== BUNDLED_GAME_EXAMPLE || !safeRelativePath(path)) throw new WorkspaceHttpError(404, "\u6E38\u620F\u793A\u4F8B\u4E0D\u5B58\u5728\u3002");
|
|
1519
|
+
const appRoot = resolve4(bundledExampleRoot(), "build/app");
|
|
1520
|
+
const target2 = resolve4(appRoot, path);
|
|
1521
|
+
const escaped = relative3(appRoot, target2);
|
|
1522
|
+
if (escaped.startsWith("..") || isAbsolute3(escaped)) throw new WorkspaceHttpError(403, "\u9884\u89C8\u8D44\u6E90\u79BB\u5F00\u4E86\u6E38\u620F\u76EE\u5F55\u3002");
|
|
1523
|
+
const info = await nodeStat(target2).catch(() => void 0);
|
|
1524
|
+
if (!info?.isFile()) throw new WorkspaceHttpError(404, "\u9884\u89C8\u8D44\u6E90\u4E0D\u5B58\u5728\u3002");
|
|
1525
|
+
if (info.size > PREVIEW_FILE_LIMIT) throw new WorkspaceHttpError(413, "\u9884\u89C8\u8D44\u6E90\u8FC7\u5927\u3002");
|
|
1526
|
+
return { path, bytes: await readNodeFile(target2) };
|
|
1527
|
+
}
|
|
1528
|
+
if (kind === "workspace") {
|
|
1529
|
+
const session = segments.shift();
|
|
1530
|
+
const project = segments.shift();
|
|
1531
|
+
const path = segments.join("/") || "index.html";
|
|
1532
|
+
if (session === void 0 || project === void 0 || !safeRelativePath(path)) throw new WorkspaceHttpError(400, "\u6E38\u620F\u9884\u89C8\u5730\u5740\u65E0\u6548\u3002");
|
|
1533
|
+
const realm = await workspaceRealmForSession(context, untoken(session));
|
|
1534
|
+
const root = untoken(project);
|
|
1535
|
+
if (!gameRoot(root)) throw new WorkspaceHttpError(403, "\u6E38\u620F\u9879\u76EE\u8DEF\u5F84\u65E0\u6548\u3002");
|
|
1536
|
+
const appRoot = await realm.fs.resolve(`${root}/build/app`, { cwd: realm.cwd });
|
|
1537
|
+
const target2 = await realm.fs.resolve(`${root}/build/app/${path}`, { cwd: realm.cwd });
|
|
1538
|
+
if (!realm.fs.contains(realm.root, appRoot) || !realm.fs.contains(appRoot, target2)) {
|
|
1539
|
+
throw new WorkspaceHttpError(403, "\u9884\u89C8\u8D44\u6E90\u79BB\u5F00\u4E86\u6E38\u620F\u76EE\u5F55\u3002");
|
|
1540
|
+
}
|
|
1541
|
+
const info = requireRegularFile(await realm.fs.stat(target2));
|
|
1542
|
+
if (info.size !== void 0 && info.size > PREVIEW_FILE_LIMIT) throw new WorkspaceHttpError(413, "\u9884\u89C8\u8D44\u6E90\u8FC7\u5927\u3002");
|
|
1543
|
+
return { path, bytes: await realm.fs.readBytes(target2, void 0, PREVIEW_FILE_LIMIT) };
|
|
1544
|
+
}
|
|
1545
|
+
throw new WorkspaceHttpError(404, "\u6E38\u620F\u9884\u89C8\u4E0D\u5B58\u5728\u3002");
|
|
1546
|
+
}
|
|
753
1547
|
function mapFsError(error) {
|
|
754
1548
|
if (!(error instanceof FsError)) return void 0;
|
|
755
1549
|
switch (error.code) {
|
|
@@ -774,15 +1568,33 @@ function mapFsError(error) {
|
|
|
774
1568
|
}
|
|
775
1569
|
async function handle(context, request, response, options) {
|
|
776
1570
|
try {
|
|
777
|
-
if (!isTrustedWorkspaceRequest(request, options.trustedHosts ?? [])) throw new WorkspaceHttpError(403, "\u8BF7\u6C42\u6765\u6E90\u4E0D\u53D7\u4FE1\u4EFB\u3002");
|
|
778
1571
|
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
1572
|
+
const gamePreview = url.pathname.startsWith("/oh-story/game-preview/");
|
|
1573
|
+
const trusted = gamePreview ? isTrustedPreviewNavigation(request, options.trustedHosts ?? []) : isTrustedWorkspaceRequest(request, options.trustedHosts ?? []);
|
|
1574
|
+
if (!trusted) throw new WorkspaceHttpError(403, "\u8BF7\u6C42\u6765\u6E90\u4E0D\u53D7\u4FE1\u4EFB\u3002");
|
|
1575
|
+
if (gamePreview && request.method === "GET") {
|
|
1576
|
+
const preview = await previewBytes(context, url.pathname);
|
|
1577
|
+
sendPreview(request, response, preview.path, preview.bytes);
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
779
1580
|
if (url.pathname === "/oh-story/workspace" && request.method === "GET") {
|
|
780
1581
|
const realm = await workspaceRealm(context, url);
|
|
781
1582
|
const files = await listFiles(realm);
|
|
782
1583
|
const tracking = await metadata(realm, files, "\u8FFD\u8E2A/_tracking-state.json", options.maxBytes);
|
|
783
1584
|
const shortDrama = await metadata(realm, files, "short-drama.json", options.maxBytes);
|
|
784
1585
|
const metadataErrors = [tracking.error, shortDrama.error].filter((value) => value !== void 0);
|
|
785
|
-
|
|
1586
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
1587
|
+
if (sessionId === null) throw new WorkspaceHttpError(400, "\u7F3A\u5C11 DSH sessionId\u3002");
|
|
1588
|
+
const games = [
|
|
1589
|
+
...await workspaceGameProjects(realm, files, sessionId, options.maxBytes),
|
|
1590
|
+
await bundledGameExample()
|
|
1591
|
+
];
|
|
1592
|
+
const videos = await workspaceVideoProjects(realm, files, options.maxBytes);
|
|
1593
|
+
send(response, 200, { cwd: realm.cwd, files, games, videos, tracking: tracking.value, shortDrama: shortDrama.value, metadataErrors, mode: "dsh-session" });
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
if (url.pathname === "/oh-story/video-preflight" && request.method === "GET") {
|
|
1597
|
+
send(response, 200, await videoPreflight());
|
|
786
1598
|
return;
|
|
787
1599
|
}
|
|
788
1600
|
if (url.pathname === "/oh-story/file" && request.method === "GET") {
|
|
@@ -793,6 +1605,17 @@ async function handle(context, request, response, options) {
|
|
|
793
1605
|
send(response, 200, { path, ...file });
|
|
794
1606
|
return;
|
|
795
1607
|
}
|
|
1608
|
+
if (url.pathname === "/oh-story/media" && (request.method === "GET" || request.method === "HEAD")) {
|
|
1609
|
+
const realm = await workspaceRealm(context, url);
|
|
1610
|
+
const path = url.searchParams.get("path");
|
|
1611
|
+
if (path === null) throw new WorkspaceHttpError(400, "\u7F3A\u5C11\u5A92\u4F53\u6587\u4EF6\u8DEF\u5F84\u3002");
|
|
1612
|
+
const mimeType = mediaMimeTypeForPath(path);
|
|
1613
|
+
if (mimeType === void 0) throw new WorkspaceHttpError(415, "\u76EE\u6807\u4E0D\u662F\u53D7\u652F\u6301\u7684\u77ED\u5267\u5A92\u4F53\u6587\u4EF6\u3002");
|
|
1614
|
+
const target2 = await creativeTarget(realm, path, "media");
|
|
1615
|
+
const info = requireRegularFile(await realm.fs.stat(target2));
|
|
1616
|
+
await sendWorkspaceMedia(request, response, realm, target2, info, mimeType);
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
796
1619
|
if (url.pathname === "/oh-story/file" && request.method === "PUT") {
|
|
797
1620
|
const realm = await workspaceRealm(context, url);
|
|
798
1621
|
const path = url.searchParams.get("path");
|
|
@@ -838,30 +1661,39 @@ async function apply(context, config = {}) {
|
|
|
838
1661
|
for (const entry of trustedHosts) assertTrustedWorkspaceAuthority(entry);
|
|
839
1662
|
context.skills.registerProvider(() => createOhStorySkillProvider());
|
|
840
1663
|
context.skills.registerProvider(() => createDramaSkillProvider());
|
|
1664
|
+
context.skills.registerProvider(() => createNovelToGameSkillProvider());
|
|
1665
|
+
context.skills.registerProvider(() => createVideoRecapSkillProvider());
|
|
841
1666
|
registerOhStoryHooks(context);
|
|
1667
|
+
registerOhStoryProductionTool(context);
|
|
842
1668
|
await registerOhStoryRoleTool(context);
|
|
843
1669
|
registerWorkspaceRoute(context, { maxBytes: config.editorMaxBytes ?? 2097152, trustedHosts });
|
|
844
1670
|
}
|
|
845
1671
|
var index_default = { name, inject, Config, apply };
|
|
846
1672
|
export {
|
|
847
1673
|
Config,
|
|
1674
|
+
OH_STORY_PRODUCTION_TOOL_NAME,
|
|
848
1675
|
OH_STORY_REFERENCE_TOOL_NAME,
|
|
849
1676
|
OH_STORY_ROLE_NAMES,
|
|
850
1677
|
OH_STORY_ROLE_TOOL_NAME,
|
|
851
1678
|
apply,
|
|
852
1679
|
bundledReferenceGuard,
|
|
853
1680
|
createDramaSkillProvider,
|
|
1681
|
+
createNovelToGameSkillProvider,
|
|
1682
|
+
createOhStoryProductionTool,
|
|
854
1683
|
createOhStoryReferenceTool,
|
|
855
1684
|
createOhStoryRoleTool,
|
|
856
1685
|
createOhStorySkillProvider,
|
|
1686
|
+
createVideoRecapSkillProvider,
|
|
857
1687
|
index_default as default,
|
|
858
1688
|
inject,
|
|
859
1689
|
loadBundledRole,
|
|
860
1690
|
name,
|
|
861
1691
|
parseBundledSkill,
|
|
862
1692
|
registerOhStoryHooks,
|
|
1693
|
+
registerOhStoryProductionTool,
|
|
863
1694
|
registerOhStoryRoleTool,
|
|
864
1695
|
registerWorkspaceRoute,
|
|
865
|
-
roleToolFilter
|
|
1696
|
+
roleToolFilter,
|
|
1697
|
+
validateProductionIntent
|
|
866
1698
|
};
|
|
867
1699
|
//# sourceMappingURL=index.js.map
|