@oh-story/dsh 0.1.4 → 0.1.6

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