@oh-story/dsh 0.1.3 → 0.1.5
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 +58 -12
- package/lib/client.js +2970 -537
- package/lib/client.js.map +4 -4
- package/lib/drama/manifest.json +218 -178
- package/lib/drama/skills/short-drama/SKILL.md +110 -117
- package/lib/drama/skills/short-drama/references/audience-reveal.md +14 -67
- package/lib/drama/skills/short-drama/references/contract-and-ownership.md +26 -77
- package/lib/drama/skills/short-drama/references/creator-documents.md +204 -0
- package/lib/drama/skills/short-drama/references/creator-workflow.md +73 -63
- package/lib/drama/skills/short-drama/references/knowhow-index.md +5 -0
- package/lib/drama/skills/short-drama/references/look-development.md +2 -3
- package/lib/drama/skills/short-drama/references/pickup-and-alternate.md +14 -48
- package/lib/drama/skills/short-drama/references/production-form-profiles.md +1 -1
- package/lib/drama/skills/short-drama/references/reference-roles.md +32 -135
- package/lib/drama/skills/short-drama/references/runtime-preflight.md +16 -19
- 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 +59 -170
- package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +18 -25
- package/lib/drama/skills/short-drama-assets/references/character-and-look.md +13 -15
- package/lib/drama/skills/short-drama-assets/references/continuity-delta.md +11 -14
- 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 +6 -12
- package/lib/drama/skills/short-drama-assets/references/occurrence-extraction.md +8 -11
- package/lib/drama/skills/short-drama-assets/references/prop-and-state.md +21 -2
- package/lib/drama/skills/short-drama-assets/references/stage-contract.md +12 -105
- package/lib/drama/skills/short-drama-assets/references/voice-direction.md +12 -32
- package/lib/drama/skills/short-drama-assets/scripts/asset_check.py +1 -1
- package/lib/drama/skills/short-drama-image-prompts/SKILL.md +45 -155
- package/lib/drama/skills/short-drama-image-prompts/references/character-and-look.md +12 -13
- package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +12 -13
- package/lib/drama/skills/short-drama-image-prompts/references/edit-and-revision.md +39 -51
- package/lib/drama/skills/short-drama-image-prompts/references/location-plate.md +3 -4
- package/lib/drama/skills/short-drama-image-prompts/references/look-and-state-variant.md +15 -14
- package/lib/drama/skills/short-drama-image-prompts/references/lookdev-frame.md +11 -15
- package/lib/drama/skills/short-drama-image-prompts/references/production-sheet-recipes.md +1 -1
- package/lib/drama/skills/short-drama-image-prompts/references/prop-plate.md +4 -7
- package/lib/drama/skills/short-drama-image-prompts/references/review-and-fixtures.md +23 -23
- package/lib/drama/skills/short-drama-image-prompts/references/stage-contract.md +8 -65
- 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 +67 -15
- 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/SKILL.md +61 -123
- package/lib/drama/skills/short-drama-review/references/anti-template-repair.md +10 -15
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +15 -16
- package/lib/drama/skills/short-drama-review/references/project-calibration.md +7 -9
- package/lib/drama/skills/short-drama-review/references/review-method.md +8 -9
- package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +9 -13
- package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +104 -4
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +27 -19
- package/lib/drama/skills/short-drama-review/references/stage-contract.md +4 -63
- package/lib/drama/skills/short-drama-review/scripts/review_check.py +1 -1
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +116 -238
- package/lib/drama/skills/short-drama-storyboard/assets/shot-template.jsonl +1 -1
- package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +9 -34
- package/lib/drama/skills/short-drama-storyboard/references/comic-keyframe-lexicon.md +156 -0
- package/lib/drama/skills/short-drama-storyboard/references/coverage-audition.md +5 -10
- package/lib/drama/skills/short-drama-storyboard/references/keyframe-craft.md +12 -7
- 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 +27 -62
- package/lib/drama/skills/short-drama-storyboard/references/scene-visual-plan.md +16 -25
- package/lib/drama/skills/short-drama-storyboard/references/screenplay-to-keyframe-example.md +21 -23
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +8 -10
- package/lib/drama/skills/short-drama-storyboard/references/shot-revision-identity.md +9 -13
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +16 -71
- package/lib/drama/skills/short-drama-storyboard/scripts/storyboard_check.py +1 -1
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +123 -230
- 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/camera-audio-continuity.md +10 -8
- package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +24 -6
- package/lib/drama/skills/short-drama-video-prompts/references/generability.md +2 -2
- 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 +35 -27
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +8 -11
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +13 -10
- package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +34 -63
- 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 +17 -73
- 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 +74 -268
- package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +7 -0
- package/lib/drama/skills/short-drama-write/references/scene-handoff-capsule.md +17 -43
- package/lib/drama/skills/short-drama-write/references/screenplay-format.md +15 -15
- package/lib/drama/skills/short-drama-write/references/script-craft.md +208 -21
- package/lib/drama/skills/short-drama-write/references/stage-contract.md +11 -70
- 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 +971 -46
- 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 +419 -309
- package/lib/oh-story/roles/character-designer.md +4 -4
- package/lib/oh-story/roles/consistency-checker.md +1 -1
- package/lib/oh-story/roles/narrative-writer.md +41 -42
- package/lib/oh-story/roles/story-architect.md +19 -23
- package/lib/oh-story/roles/story-explorer.md +8 -5
- 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 +21 -8
- 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-long-write/references/{genre-core-mechanics.md → long-genre-mechanics.md} +1 -1
- package/lib/oh-story/skills/story-long-write/references/{reversal-toolkit.md → 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 +5 -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 +17 -11
- 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 +85 -41
- 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-setup/references/agent-references/{genre-core-mechanics.md → 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-short-write/references/reversal-toolkit.md → story-setup/references/agent-references/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-setup/references/agent-references/{reversal-toolkit.md → 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 +9 -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 +5 -2
- 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/reference-tool.d.ts +6 -0
- package/lib/types/reference-tool.d.ts.map +1 -0
- package/lib/types/role-provider.d.ts.map +1 -1
- package/lib/types/role-tool.d.ts +3 -1
- 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 +57 -41
- package/lib/drama/skills/short-drama/references/lifecycle-commands.md +0 -130
- 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.",
|
|
@@ -39,14 +40,42 @@ var DSH_DRAMA_BRIDGE = [
|
|
|
39
40
|
"This Skill is a native contribution to the current DeepSeek Harness session.",
|
|
40
41
|
"DSH owns the workspace, model, preset, permissions, Session Log, tools, approvals, cancellation, resume, and Agent UI.",
|
|
41
42
|
"The \u77ED\u5267 tab is the creator workspace. Never start dashboard_server.py, another web server, Dashboard, Agent runtime, session transport, or model configuration.",
|
|
43
|
+
"Drama Skills v0.6 uses a creator-first contract: each episode keeps only the requested documents, up to five creator-facing sources at \u5267\u96C6/<EP>/\u5267\u672C.md, \u89C6\u89C9\u8BBE\u5B9A.md, \u5206\u955C.md, \u56FE\u7247\u63D0\u793A\u8BCD.md, and \u89C6\u9891\u63D0\u793A\u8BCD.md. Never precreate empty documents, backfill nominal stages, or start work the creator did not request. Persisted reviews use creator-readable Markdown under \u5BA1\u67E5/; an oral review writes nothing.",
|
|
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.",
|
|
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.",
|
|
42
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.",
|
|
43
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.",
|
|
44
49
|
"</short-drama-dsh-integration>"
|
|
45
50
|
].join("\n");
|
|
46
51
|
var DSH_DRAMA_OVERRIDES = {
|
|
47
|
-
"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.",
|
|
48
|
-
"short-drama-produce": "Use an upstream adapter only after the creator explicitly confirms the exact current job. DSH permissions and approval UI remain authoritative."
|
|
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.",
|
|
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."
|
|
49
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");
|
|
50
79
|
var DSH_NATIVE_SKILLS = {
|
|
51
80
|
story: `# story \u2014 DSH \u5C0F\u8BF4\u6D41\u7A0B\u5165\u53E3
|
|
52
81
|
|
|
@@ -58,6 +87,9 @@ var DSH_NATIVE_SKILLS = {
|
|
|
58
87
|
- \u5BFC\u5165\u5DF2\u6709\u4F5C\u54C1\uFF1Astory-import
|
|
59
88
|
- \u5BA1\u7A3F\u4E0E\u53BB AI \u5473\uFF1Astory-review\u3001story-deslop
|
|
60
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
|
|
61
93
|
|
|
62
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
|
|
63
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
|
|
@@ -71,7 +103,7 @@ Agent\u3001\u6A21\u578B\u3001\u6743\u9650\u3001Session Log \u548C UI \u5747\u753
|
|
|
71
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
|
|
72
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
|
|
73
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
|
|
74
|
-
\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
|
|
75
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
|
|
76
108
|
|
|
77
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
|
|
@@ -119,9 +151,19 @@ Agent\u3001\u6A21\u578B\u3001\u6743\u9650\u3001Session Log \u548C UI \u5747\u753
|
|
|
119
151
|
\u4E0D\u8981\u542F\u52A8 CDP\u3001\u72EC\u7ACB\u6D4F\u89C8\u5668\u6216\u5E76\u884C\u8FD0\u884C\u65F6\u3002`
|
|
120
152
|
};
|
|
121
153
|
function frontmatterValue(frontmatter, key) {
|
|
122
|
-
const
|
|
123
|
-
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();
|
|
124
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
|
+
}
|
|
125
167
|
if (raw.startsWith('"') && raw.endsWith('"')) {
|
|
126
168
|
try {
|
|
127
169
|
return JSON.parse(raw);
|
|
@@ -138,7 +180,12 @@ function parseBundledSkill(source) {
|
|
|
138
180
|
const description = frontmatterValue(match[1], "description");
|
|
139
181
|
if (!name2 || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name2)) throw new Error("Bundled skill has an invalid name.");
|
|
140
182
|
if (!description) throw new Error(`Bundled skill "${name2}" has no description.`);
|
|
141
|
-
return {
|
|
183
|
+
return {
|
|
184
|
+
name: name2,
|
|
185
|
+
description,
|
|
186
|
+
content: source.slice(match[0].length),
|
|
187
|
+
userInvocable: frontmatterValue(match[1], "user-invocable") !== "false"
|
|
188
|
+
};
|
|
142
189
|
}
|
|
143
190
|
function dshSkillContent(name2, content) {
|
|
144
191
|
const override = DSH_SKILL_OVERRIDES[name2];
|
|
@@ -156,6 +203,10 @@ function defaultDramaSkillRoot() {
|
|
|
156
203
|
const current = dirname(fileURLToPath(import.meta.url));
|
|
157
204
|
return basename(current) === "src" ? resolve(current, "../../knowledge/drama/skills") : resolve(current, "drama/skills");
|
|
158
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
|
+
}
|
|
159
210
|
function createBundledSkillProvider(providerName, skillRoot, content) {
|
|
160
211
|
const root = resolve(skillRoot);
|
|
161
212
|
return {
|
|
@@ -169,7 +220,7 @@ function createBundledSkillProvider(providerName, skillRoot, content) {
|
|
|
169
220
|
return {
|
|
170
221
|
name: parsed.name,
|
|
171
222
|
description: parsed.description,
|
|
172
|
-
invocation:
|
|
223
|
+
invocation: { modelInvocable: true, userInvocable: parsed.userInvocable },
|
|
173
224
|
provider: providerName,
|
|
174
225
|
source: "bundled",
|
|
175
226
|
resourceBase: { kind: "directory", path: join(root, directory) },
|
|
@@ -191,7 +242,7 @@ function createBundledSkillProvider(providerName, skillRoot, content) {
|
|
|
191
242
|
return {
|
|
192
243
|
name: parsed.name,
|
|
193
244
|
description: parsed.description,
|
|
194
|
-
invocation:
|
|
245
|
+
invocation: { modelInvocable: true, userInvocable: parsed.userInvocable },
|
|
195
246
|
provider: providerName,
|
|
196
247
|
source: "bundled",
|
|
197
248
|
resourceBase: { kind: "directory", path: join(root, parsed.name) },
|
|
@@ -214,18 +265,38 @@ ${content}`;
|
|
|
214
265
|
function createDramaSkillProvider(skillRoot = defaultDramaSkillRoot()) {
|
|
215
266
|
return createBundledSkillProvider(DRAMA_PROVIDER_NAME, skillRoot, dshDramaSkillContent);
|
|
216
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
|
+
}
|
|
217
288
|
|
|
218
289
|
// src/native-hooks.ts
|
|
219
290
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
220
291
|
var MUTATION_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "str_replace_editor"]);
|
|
221
292
|
function mutationPath(name2, args) {
|
|
222
293
|
if (!MUTATION_TOOLS.has(name2) || typeof args !== "object" || args === null || Array.isArray(args)) return void 0;
|
|
223
|
-
const
|
|
294
|
+
const record2 = args;
|
|
224
295
|
if (name2 === "str_replace_editor") {
|
|
225
|
-
if (!(/* @__PURE__ */ new Set(["create", "str_replace", "insert"])).has(String(
|
|
226
|
-
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;
|
|
227
298
|
}
|
|
228
|
-
return typeof
|
|
299
|
+
return typeof record2.file_path === "string" && record2.file_path.trim() !== "" ? record2.file_path : void 0;
|
|
229
300
|
}
|
|
230
301
|
function normalizedRelativePath(path) {
|
|
231
302
|
const segments = [];
|
|
@@ -320,11 +391,89 @@ function registerOhStoryHooks(context) {
|
|
|
320
391
|
}
|
|
321
392
|
|
|
322
393
|
// src/role-tool.ts
|
|
394
|
+
import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
|
|
395
|
+
|
|
396
|
+
// src/reference-tool.ts
|
|
397
|
+
import { readdir as readdir2, readFile as readFile2, realpath } from "node:fs/promises";
|
|
398
|
+
import { isAbsolute as isAbsolute2, join as join2, relative as relative2, resolve as resolve2 } from "node:path";
|
|
323
399
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
400
|
+
var OH_STORY_REFERENCE_TOOL_NAME = "oh_story_bundled_reference";
|
|
401
|
+
async function collectMarkdown(root, directory = root) {
|
|
402
|
+
const entries = await readdir2(directory, { withFileTypes: true });
|
|
403
|
+
const nested = await Promise.all(entries.map(async (entry) => {
|
|
404
|
+
const path = join2(directory, entry.name);
|
|
405
|
+
if (entry.isDirectory()) return collectMarkdown(root, path);
|
|
406
|
+
return entry.isFile() && entry.name.endsWith(".md") ? [path] : [];
|
|
407
|
+
}));
|
|
408
|
+
return nested.flat();
|
|
409
|
+
}
|
|
410
|
+
async function bundledReferences(storySetupRoot) {
|
|
411
|
+
const root = await realpath(resolve2(storySetupRoot));
|
|
412
|
+
const referenceRoot = await realpath(join2(root, "references", "agent-references"));
|
|
413
|
+
const referenceDirectory = relative2(root, referenceRoot);
|
|
414
|
+
if (referenceDirectory === "" || referenceDirectory.startsWith("..") || isAbsolute2(referenceDirectory)) {
|
|
415
|
+
throw new Error("Bundled Oh Story reference directory escaped its package root.");
|
|
416
|
+
}
|
|
417
|
+
const values = await Promise.all((await collectMarkdown(referenceRoot)).map(async (path) => {
|
|
418
|
+
const canonicalPath = await realpath(path);
|
|
419
|
+
const inside = relative2(referenceRoot, canonicalPath);
|
|
420
|
+
if (inside === "" || inside.startsWith("..") || isAbsolute2(inside)) {
|
|
421
|
+
throw new Error(`Bundled Oh Story reference escaped its package root: ${path}`);
|
|
422
|
+
}
|
|
423
|
+
return {
|
|
424
|
+
canonicalPath,
|
|
425
|
+
name: `story-setup/references/agent-references/${inside.replaceAll("\\", "/")}`
|
|
426
|
+
};
|
|
427
|
+
}));
|
|
428
|
+
return values.sort((left, right) => left.name.localeCompare(right.name));
|
|
429
|
+
}
|
|
430
|
+
async function createOhStoryReferenceTool(storySetupRoot = join2(defaultBundledSkillRoot(), "story-setup")) {
|
|
431
|
+
const references = await bundledReferences(storySetupRoot);
|
|
432
|
+
if (references.length === 0) throw new Error("No bundled Oh Story references were found.");
|
|
433
|
+
const paths = references.map((reference) => reference.name);
|
|
434
|
+
const byName = new Map(references.map((reference) => [reference.name, reference.canonicalPath]));
|
|
435
|
+
return defineTool({
|
|
436
|
+
name: OH_STORY_REFERENCE_TOOL_NAME,
|
|
437
|
+
description: "Read one exact, pinned Oh Story story-setup reference bundled with this plugin. This does not resolve project Skills or workspace files.",
|
|
438
|
+
parameters: {
|
|
439
|
+
reference: {
|
|
440
|
+
type: "string",
|
|
441
|
+
required: true,
|
|
442
|
+
enum: paths,
|
|
443
|
+
description: "The exact story-setup reference path named by the active bundled Role."
|
|
444
|
+
}
|
|
445
|
+
},
|
|
446
|
+
output: {
|
|
447
|
+
schema: {
|
|
448
|
+
type: "object",
|
|
449
|
+
additionalProperties: false,
|
|
450
|
+
properties: {
|
|
451
|
+
reference: { type: "string", required: true },
|
|
452
|
+
content: { type: "string", required: true }
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
render: (_args, value) => [{
|
|
456
|
+
type: "text",
|
|
457
|
+
text: `Bundled Oh Story reference: ${value.reference}
|
|
458
|
+
|
|
459
|
+
${value.content}`
|
|
460
|
+
}]
|
|
461
|
+
},
|
|
462
|
+
isConcurrencySafe: () => true,
|
|
463
|
+
async execute(args) {
|
|
464
|
+
const path = byName.get(args.reference);
|
|
465
|
+
if (path === void 0) throw new Error(`Oh Story reference is not bundled: ${args.reference}`);
|
|
466
|
+
return { reference: args.reference, content: await readFile2(path, "utf8") };
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
function bundledReferenceGuard(definition, tools) {
|
|
471
|
+
return (execution) => execution.name === OH_STORY_REFERENCE_TOOL_NAME && tools.get(OH_STORY_REFERENCE_TOOL_NAME, execution.agent) !== definition ? "The pinned Oh Story reference tool was shadowed in this Agent scope." : void 0;
|
|
472
|
+
}
|
|
324
473
|
|
|
325
474
|
// src/role-provider.ts
|
|
326
|
-
import { readFile as
|
|
327
|
-
import { basename as basename2, dirname as dirname2, join as
|
|
475
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
476
|
+
import { basename as basename2, dirname as dirname2, join as join3, resolve as resolve3 } from "node:path";
|
|
328
477
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
329
478
|
var OH_STORY_ROLE_NAMES = [
|
|
330
479
|
"chapter-extractor",
|
|
@@ -341,10 +490,10 @@ function roleBody(source) {
|
|
|
341
490
|
}
|
|
342
491
|
function defaultBundledRoleRoot() {
|
|
343
492
|
const current = dirname2(fileURLToPath2(import.meta.url));
|
|
344
|
-
return basename2(current) === "src" ?
|
|
493
|
+
return basename2(current) === "src" ? resolve3(current, "../../knowledge/oh-story/roles") : resolve3(current, "oh-story/roles");
|
|
345
494
|
}
|
|
346
495
|
async function loadBundledRole(name2, roleRoot = defaultBundledRoleRoot(), execution = "tool-free") {
|
|
347
|
-
const body = roleBody(await
|
|
496
|
+
const body = roleBody(await readFile3(join3(resolve3(roleRoot), `${name2}.md`), "utf8"));
|
|
348
497
|
if (body.length === 0) throw new Error(`Bundled role "${name2}" is empty.`);
|
|
349
498
|
const integration = execution === "tool-free" ? [
|
|
350
499
|
"You are running inside an Oh Story review-required DSH collaboration. The caller supplies every permitted input in the prompt.",
|
|
@@ -353,6 +502,8 @@ async function loadBundledRole(name2, roleRoot = defaultBundledRoleRoot(), execu
|
|
|
353
502
|
] : [
|
|
354
503
|
"You are running as a native oh-story-dsh specialist. The current DSH workspace and visible tool set are your complete authority boundary.",
|
|
355
504
|
"Never inspect or require legacy .claude/.opencode/.codex agent deployment files; this exact pinned Role is already active.",
|
|
505
|
+
"Bundled story-setup references are pinned plugin resources, not project files or project Skills. When the upstream Role marks one mandatory, call oh_story_bundled_reference with the exact story-setup/references/agent-references path, then use only the returned content.",
|
|
506
|
+
"If oh_story_bundled_reference or a required bundled reference is unavailable, report the missing reference to the caller. Never call the generic skill tool, fall back to a legacy platform path, or claim that an unread reference was used.",
|
|
356
507
|
"Use only the tools actually visible to you. Mutate files only when the caller explicitly requests it and your visible DSH tools permit it; otherwise return findings to the caller."
|
|
357
508
|
];
|
|
358
509
|
return [
|
|
@@ -367,10 +518,10 @@ async function loadBundledRole(name2, roleRoot = defaultBundledRoleRoot(), execu
|
|
|
367
518
|
var OH_STORY_ROLE_TOOL_NAME = "oh_story_role";
|
|
368
519
|
var roleTools = {
|
|
369
520
|
"chapter-extractor": ["read", "glob", "grep"],
|
|
370
|
-
"character-designer": ["read", "glob", "grep", "write", "edit"],
|
|
371
|
-
"consistency-checker": ["read", "glob", "grep"],
|
|
372
|
-
"narrative-writer": ["read", "glob", "grep", "write", "edit", "bash"],
|
|
373
|
-
"story-architect": ["read", "glob", "grep", "write", "edit"],
|
|
521
|
+
"character-designer": [OH_STORY_REFERENCE_TOOL_NAME, "read", "glob", "grep", "write", "edit"],
|
|
522
|
+
"consistency-checker": [OH_STORY_REFERENCE_TOOL_NAME, "read", "glob", "grep"],
|
|
523
|
+
"narrative-writer": [OH_STORY_REFERENCE_TOOL_NAME, "read", "glob", "grep", "write", "edit", "bash"],
|
|
524
|
+
"story-architect": [OH_STORY_REFERENCE_TOOL_NAME, "read", "glob", "grep", "write", "edit"],
|
|
374
525
|
"story-explorer": ["read", "glob", "grep"],
|
|
375
526
|
"story-researcher": ["read", "glob", "grep", "bash", "write", "web_search", "web_fetch"]
|
|
376
527
|
};
|
|
@@ -380,12 +531,12 @@ function roleToolFilter(role) {
|
|
|
380
531
|
function resultText(output) {
|
|
381
532
|
return output.map((block) => block.type === "text" ? block.text : JSON.stringify(block)).join("\n");
|
|
382
533
|
}
|
|
383
|
-
async function createOhStoryRoleTool() {
|
|
534
|
+
async function createOhStoryRoleTool(subagents) {
|
|
384
535
|
const personas = /* @__PURE__ */ new Map();
|
|
385
536
|
await Promise.all(OH_STORY_ROLE_NAMES.map(async (role) => {
|
|
386
537
|
personas.set(role, await loadBundledRole(role, void 0, "native-tools"));
|
|
387
538
|
}));
|
|
388
|
-
return
|
|
539
|
+
return defineTool2({
|
|
389
540
|
name: OH_STORY_ROLE_TOOL_NAME,
|
|
390
541
|
description: "Run one focused Oh Story specialist as a child of the current DSH Agent. The child inherits DSH model, workspace, permissions, lifecycle, and UI.",
|
|
391
542
|
parameters: {
|
|
@@ -419,7 +570,9 @@ async function createOhStoryRoleTool() {
|
|
|
419
570
|
const persona = personas.get(args.role);
|
|
420
571
|
if (persona === void 0) throw new Error(`Oh Story Role ${args.role} is not bundled.`);
|
|
421
572
|
const allowed = roleToolFilter(args.role).allow.filter((name2) => exec.agent?.ctx.tools.get(name2, exec.agent) !== void 0);
|
|
422
|
-
const
|
|
573
|
+
const runtime = subagents ?? exec.agent.ctx.get("subagents");
|
|
574
|
+
if (runtime === void 0) throw new Error("oh_story_role requires the DSH subagent runtime.");
|
|
575
|
+
const run = await runtime.start("spawn", {
|
|
423
576
|
label: `oh-story:${args.role}`,
|
|
424
577
|
prompt: [{ type: "text", text: args.prompt }],
|
|
425
578
|
parent: exec.agent,
|
|
@@ -445,7 +598,12 @@ async function createOhStoryRoleTool() {
|
|
|
445
598
|
});
|
|
446
599
|
}
|
|
447
600
|
async function registerOhStoryRoleTool(context) {
|
|
448
|
-
const definition = await
|
|
601
|
+
const [definition, referenceDefinition] = await Promise.all([
|
|
602
|
+
createOhStoryRoleTool(context.subagents),
|
|
603
|
+
createOhStoryReferenceTool()
|
|
604
|
+
]);
|
|
605
|
+
context.tools.register(referenceDefinition);
|
|
606
|
+
context.tools.guard(bundledReferenceGuard(referenceDefinition, context.tools));
|
|
449
607
|
let dispose;
|
|
450
608
|
const mount = () => {
|
|
451
609
|
dispose ??= context.tools.register(definition);
|
|
@@ -463,11 +621,290 @@ async function registerOhStoryRoleTool(context) {
|
|
|
463
621
|
if (context.subagents.getProvider("spawn") !== void 0) mount();
|
|
464
622
|
}
|
|
465
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
|
+
|
|
466
725
|
// src/workspace-route.ts
|
|
467
|
-
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";
|
|
468
732
|
import { FsError } from "@deepseek-ai/dsh-fs";
|
|
469
733
|
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
470
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
|
+
|
|
471
908
|
// src/workspace-request-trust.ts
|
|
472
909
|
function header(headers, name2) {
|
|
473
910
|
if (headers instanceof Headers) return headers.get(name2) ?? void 0;
|
|
@@ -517,14 +954,45 @@ function isTrustedWorkspaceRequest(request, trustedHosts) {
|
|
|
517
954
|
return false;
|
|
518
955
|
}
|
|
519
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
|
+
}
|
|
520
965
|
|
|
521
966
|
// src/workspace-route.ts
|
|
522
967
|
var STORY_DIRECTORIES = ["\u6B63\u6587", "\u5927\u7EB2", "\u8BBE\u5B9A", "\u8FFD\u8E2A", "\u5BF9\u6807", "\u53C2\u8003\u8D44\u6599"];
|
|
523
968
|
var DRAMA_DIRECTORIES = ["\u8F93\u5165", "\u9879\u76EE\u5F00\u53D1", "\u8BBE\u5B9A\u96C6", "\u5267\u96C6", "\u4EA4\u4ED8", "\u521B\u4F5C\u8005\u51B3\u7B56", "\u5BA1\u67E5"];
|
|
524
|
-
var
|
|
969
|
+
var GAME_DIRECTORY = "game-adaptations";
|
|
970
|
+
var CREATIVE_DIRECTORIES = [VIDEO_DIRECTORY, GAME_DIRECTORY, ...STORY_DIRECTORIES, ...DRAMA_DIRECTORIES];
|
|
525
971
|
var ROOT_FILES = /* @__PURE__ */ new Set(["short-drama.json"]);
|
|
526
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;
|
|
527
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;
|
|
528
996
|
var WorkspaceHttpError = class extends Error {
|
|
529
997
|
constructor(status, message) {
|
|
530
998
|
super(message);
|
|
@@ -532,6 +1000,53 @@ var WorkspaceHttpError = class extends Error {
|
|
|
532
1000
|
}
|
|
533
1001
|
status;
|
|
534
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
|
+
}
|
|
535
1050
|
function send(response, status, value) {
|
|
536
1051
|
const body = `${JSON.stringify(value)}
|
|
537
1052
|
`;
|
|
@@ -543,6 +1058,87 @@ function send(response, status, value) {
|
|
|
543
1058
|
});
|
|
544
1059
|
response.end(body);
|
|
545
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
|
+
}
|
|
546
1142
|
async function jsonBody(request, maxBytes) {
|
|
547
1143
|
const chunks = [];
|
|
548
1144
|
let size = 0;
|
|
@@ -560,11 +1156,18 @@ async function jsonBody(request, maxBytes) {
|
|
|
560
1156
|
throw new WorkspaceHttpError(400, "\u8BF7\u6C42\u5FC5\u987B\u662F JSON \u5BF9\u8C61\u3002");
|
|
561
1157
|
}
|
|
562
1158
|
}
|
|
563
|
-
function
|
|
564
|
-
|
|
565
|
-
|
|
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");
|
|
566
1169
|
}
|
|
567
|
-
if (
|
|
1170
|
+
if (!safeRelativePath(path)) {
|
|
568
1171
|
throw new WorkspaceHttpError(403, "\u6587\u4EF6\u8DEF\u5F84\u4E0D\u5728\u521B\u4F5C\u5DE5\u4F5C\u53F0\u4E2D\u3002");
|
|
569
1172
|
}
|
|
570
1173
|
const root = path.split("/", 1)[0];
|
|
@@ -572,9 +1175,11 @@ function assertCreativePath(path) {
|
|
|
572
1175
|
throw new WorkspaceHttpError(403, "\u6587\u4EF6\u8DEF\u5F84\u4E0D\u5728\u521B\u4F5C\u5DE5\u4F5C\u53F0\u4E2D\u3002");
|
|
573
1176
|
}
|
|
574
1177
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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");
|
|
578
1183
|
const lookup = context.typert.lookups.get("agent");
|
|
579
1184
|
if (lookup === void 0) throw new WorkspaceHttpError(503, "DSH Agent lookup \u5F53\u524D\u4E0D\u53EF\u7528\u3002");
|
|
580
1185
|
let agent;
|
|
@@ -594,8 +1199,13 @@ async function workspaceRealm(context, url) {
|
|
|
594
1199
|
if (fs === void 0 || sandboxPolicy === void 0) throw new WorkspaceHttpError(503, "DSH \u6587\u4EF6\u7CFB\u7EDF\u5F53\u524D\u4E0D\u53EF\u7528\u3002");
|
|
595
1200
|
return { agent, fs, sandboxPolicy, cwd, root: await fs.resolve(cwd) };
|
|
596
1201
|
}
|
|
597
|
-
async function
|
|
598
|
-
|
|
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);
|
|
599
1209
|
const target2 = await realm.fs.resolve(path, { cwd: realm.cwd });
|
|
600
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");
|
|
601
1211
|
return target2;
|
|
@@ -628,11 +1238,15 @@ async function listFiles(realm) {
|
|
|
628
1238
|
for (const entry of await realm.fs.listDir(directory)) {
|
|
629
1239
|
if (entry.name.startsWith(".") || !realm.fs.contains(realm.root, entry.target)) continue;
|
|
630
1240
|
const childPath = `${path}/${entry.name}`;
|
|
631
|
-
if (entry.type === "directory")
|
|
632
|
-
|
|
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()))) {
|
|
633
1244
|
const info = entry.version === void 0 || entry.size === void 0 ? await realm.fs.stat(entry.target) : void 0;
|
|
634
1245
|
const version = entry.version ?? info?.version;
|
|
635
|
-
|
|
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
|
+
}
|
|
636
1250
|
}
|
|
637
1251
|
if (files.length >= FILE_LIMIT) return;
|
|
638
1252
|
}
|
|
@@ -647,7 +1261,7 @@ async function listFiles(realm) {
|
|
|
647
1261
|
for (const path of ROOT_FILES) {
|
|
648
1262
|
const target2 = await creativeTarget(realm, path);
|
|
649
1263
|
const info = await realm.fs.stat(target2);
|
|
650
|
-
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" });
|
|
651
1265
|
}
|
|
652
1266
|
return files.sort((left, right) => left.path.localeCompare(right.path, "zh-Hans-CN"));
|
|
653
1267
|
}
|
|
@@ -660,6 +1274,276 @@ async function metadata(realm, files, path, maxBytes) {
|
|
|
660
1274
|
return { value: null, error: error instanceof SyntaxError ? `${path} \u4E0D\u662F\u6709\u6548\u7684 JSON\u3002` : `${path} \u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u3002` };
|
|
661
1275
|
}
|
|
662
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
|
+
}
|
|
663
1547
|
function mapFsError(error) {
|
|
664
1548
|
if (!(error instanceof FsError)) return void 0;
|
|
665
1549
|
switch (error.code) {
|
|
@@ -684,15 +1568,33 @@ function mapFsError(error) {
|
|
|
684
1568
|
}
|
|
685
1569
|
async function handle(context, request, response, options) {
|
|
686
1570
|
try {
|
|
687
|
-
if (!isTrustedWorkspaceRequest(request, options.trustedHosts ?? [])) throw new WorkspaceHttpError(403, "\u8BF7\u6C42\u6765\u6E90\u4E0D\u53D7\u4FE1\u4EFB\u3002");
|
|
688
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
|
+
}
|
|
689
1580
|
if (url.pathname === "/oh-story/workspace" && request.method === "GET") {
|
|
690
1581
|
const realm = await workspaceRealm(context, url);
|
|
691
1582
|
const files = await listFiles(realm);
|
|
692
1583
|
const tracking = await metadata(realm, files, "\u8FFD\u8E2A/_tracking-state.json", options.maxBytes);
|
|
693
1584
|
const shortDrama = await metadata(realm, files, "short-drama.json", options.maxBytes);
|
|
694
1585
|
const metadataErrors = [tracking.error, shortDrama.error].filter((value) => value !== void 0);
|
|
695
|
-
|
|
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());
|
|
696
1598
|
return;
|
|
697
1599
|
}
|
|
698
1600
|
if (url.pathname === "/oh-story/file" && request.method === "GET") {
|
|
@@ -703,6 +1605,17 @@ async function handle(context, request, response, options) {
|
|
|
703
1605
|
send(response, 200, { path, ...file });
|
|
704
1606
|
return;
|
|
705
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
|
+
}
|
|
706
1619
|
if (url.pathname === "/oh-story/file" && request.method === "PUT") {
|
|
707
1620
|
const realm = await workspaceRealm(context, url);
|
|
708
1621
|
const path = url.searchParams.get("path");
|
|
@@ -748,27 +1661,39 @@ async function apply(context, config = {}) {
|
|
|
748
1661
|
for (const entry of trustedHosts) assertTrustedWorkspaceAuthority(entry);
|
|
749
1662
|
context.skills.registerProvider(() => createOhStorySkillProvider());
|
|
750
1663
|
context.skills.registerProvider(() => createDramaSkillProvider());
|
|
1664
|
+
context.skills.registerProvider(() => createNovelToGameSkillProvider());
|
|
1665
|
+
context.skills.registerProvider(() => createVideoRecapSkillProvider());
|
|
751
1666
|
registerOhStoryHooks(context);
|
|
1667
|
+
registerOhStoryProductionTool(context);
|
|
752
1668
|
await registerOhStoryRoleTool(context);
|
|
753
1669
|
registerWorkspaceRoute(context, { maxBytes: config.editorMaxBytes ?? 2097152, trustedHosts });
|
|
754
1670
|
}
|
|
755
1671
|
var index_default = { name, inject, Config, apply };
|
|
756
1672
|
export {
|
|
757
1673
|
Config,
|
|
1674
|
+
OH_STORY_PRODUCTION_TOOL_NAME,
|
|
1675
|
+
OH_STORY_REFERENCE_TOOL_NAME,
|
|
758
1676
|
OH_STORY_ROLE_NAMES,
|
|
759
1677
|
OH_STORY_ROLE_TOOL_NAME,
|
|
760
1678
|
apply,
|
|
1679
|
+
bundledReferenceGuard,
|
|
761
1680
|
createDramaSkillProvider,
|
|
1681
|
+
createNovelToGameSkillProvider,
|
|
1682
|
+
createOhStoryProductionTool,
|
|
1683
|
+
createOhStoryReferenceTool,
|
|
762
1684
|
createOhStoryRoleTool,
|
|
763
1685
|
createOhStorySkillProvider,
|
|
1686
|
+
createVideoRecapSkillProvider,
|
|
764
1687
|
index_default as default,
|
|
765
1688
|
inject,
|
|
766
1689
|
loadBundledRole,
|
|
767
1690
|
name,
|
|
768
1691
|
parseBundledSkill,
|
|
769
1692
|
registerOhStoryHooks,
|
|
1693
|
+
registerOhStoryProductionTool,
|
|
770
1694
|
registerOhStoryRoleTool,
|
|
771
1695
|
registerWorkspaceRoute,
|
|
772
|
-
roleToolFilter
|
|
1696
|
+
roleToolFilter,
|
|
1697
|
+
validateProductionIntent
|
|
773
1698
|
};
|
|
774
1699
|
//# sourceMappingURL=index.js.map
|