@oh-story/dsh 0.1.3 → 0.1.5

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