@oh-story/dsh 0.1.8 → 0.1.10

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 (360) hide show
  1. package/README.md +14 -14
  2. package/lib/client.js +237 -84
  3. package/lib/client.js.map +3 -3
  4. package/lib/drama/manifest.json +183 -77
  5. package/lib/drama/skills/short-drama/SKILL.md +5 -1
  6. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +140 -9
  7. package/lib/drama/skills/short-drama/scripts/project_tool.py +11 -0
  8. package/lib/drama/skills/short-drama-assets/references/character-and-look.md +28 -0
  9. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +8 -2
  10. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +28 -0
  11. package/lib/drama/skills/short-drama-edit/SKILL.md +146 -0
  12. package/lib/drama/skills/short-drama-edit/agents/openai.yaml +4 -0
  13. package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +60 -0
  14. package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +15 -0
  15. package/lib/drama/skills/short-drama-edit/assets/remotion/remotion.config.ts +6 -0
  16. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +26 -0
  17. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +88 -0
  18. package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +46 -0
  19. package/lib/drama/skills/short-drama-edit/assets/remotion/src/index.ts +4 -0
  20. package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +32 -0
  21. package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +13 -0
  22. package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +43 -0
  23. package/lib/drama/skills/short-drama-edit/references/cut-craft.md +38 -0
  24. package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +29 -0
  25. package/lib/drama/skills/short-drama-edit/references/generated-footage.md +54 -0
  26. package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +60 -0
  27. package/lib/drama/skills/short-drama-edit/references/stage-contract.md +51 -0
  28. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1156 -0
  29. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +520 -0
  30. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +2 -0
  31. package/lib/drama/skills/short-drama-image-prompts/assets/image-prompts.md +5 -0
  32. package/lib/drama/skills/short-drama-image-prompts/assets/lookdev-prompts.md +3 -0
  33. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +11 -0
  34. package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +2 -2
  35. package/lib/drama/skills/short-drama-novel-analyze/references/chapter-extraction.md +4 -1
  36. package/lib/drama/skills/short-drama-produce/SKILL.md +29 -4
  37. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +22 -0
  38. package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +13 -12
  39. package/lib/drama/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
  40. package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +7 -0
  41. package/lib/drama/skills/short-drama-produce/scripts/fixture_adapter.py +18 -1
  42. package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +181 -2
  43. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +199 -3
  44. package/lib/drama/skills/short-drama-review/SKILL.md +2 -0
  45. package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +7 -0
  46. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +10 -0
  47. package/lib/drama/skills/short-drama-storyboard/SKILL.md +12 -1
  48. package/lib/drama/skills/short-drama-storyboard/assets/keyframe-prompts.md +4 -0
  49. package/lib/drama/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
  50. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +78 -2
  51. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +2 -0
  52. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +12 -5
  53. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +17 -0
  54. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +76 -7
  55. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +5 -2
  56. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +21 -0
  57. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +6 -17
  58. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +12 -3
  59. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +61 -5
  60. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +3 -3
  61. package/lib/drama/skills/short-drama-write/SKILL.md +11 -2
  62. package/lib/drama/skills/short-drama-write/references/screenplay-format.md +6 -0
  63. package/lib/drama/skills/short-drama-write/references/stage-contract.md +1 -0
  64. package/lib/index.js +290 -90
  65. package/lib/index.js.map +4 -4
  66. package/lib/novel-to-game/README.md +299 -59
  67. package/lib/novel-to-game/README_ZH.md +299 -56
  68. package/lib/novel-to-game/VERSION +1 -1
  69. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +3 -1
  70. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +11 -3
  71. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +397 -8
  72. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +0 -31
  73. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +71 -73
  74. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +78 -89
  75. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +37 -14
  76. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +5 -5
  77. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -1
  78. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +11 -16
  79. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +11 -11
  80. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +23 -0
  81. package/lib/novel-to-game/manifest.json +77 -87
  82. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +9 -35
  83. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +27 -106
  84. package/lib/novel-to-game/skills/game-build/SKILL.md +17 -35
  85. package/lib/novel-to-game/skills/game-build/references/blender-asset-workflow.md +24 -0
  86. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +45 -82
  87. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +14 -45
  88. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +30 -65
  89. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +13 -27
  90. package/lib/novel-to-game/skills/game-concept/SKILL.md +16 -34
  91. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +28 -75
  92. package/lib/novel-to-game/skills/game-qa/SKILL.md +9 -20
  93. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +10 -6
  94. package/lib/novel-to-game/skills/game-world-design/SKILL.md +19 -32
  95. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +23 -72
  96. package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +14 -48
  97. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +36 -103
  98. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +9 -18
  99. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +4 -4
  100. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +10 -27
  101. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +5 -6
  102. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +6 -11
  103. package/lib/oh-story/manifest.json +419 -254
  104. package/lib/oh-story/roles/chapter-extractor.md +177 -237
  105. package/lib/oh-story/roles/character-designer.md +1 -1
  106. package/lib/oh-story/roles/consistency-checker.md +4 -4
  107. package/lib/oh-story/roles/narrative-writer.md +56 -243
  108. package/lib/oh-story/roles/story-architect.md +6 -6
  109. package/lib/oh-story/roles/story-explorer.md +11 -11
  110. package/lib/oh-story/skills/story/SKILL.md +13 -7
  111. package/lib/oh-story/skills/story/VERSION +1 -1
  112. package/lib/oh-story/skills/story/references/author-memory.md +61 -29
  113. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +934 -170
  114. package/lib/oh-story/skills/story-cover/SKILL.md +10 -1
  115. package/lib/oh-story/skills/story-deslop/SKILL.md +54 -229
  116. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +29 -56
  117. package/lib/oh-story/skills/story-deslop/references/author-memory.md +61 -29
  118. package/lib/oh-story/skills/story-deslop/references/banned-words.md +3 -1
  119. package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +154 -0
  120. package/lib/oh-story/skills/story-deslop/references/style-resolution.md +27 -0
  121. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +934 -170
  122. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +6 -1
  123. package/lib/oh-story/skills/story-deslop/scripts/check-degeneration.js +39 -20
  124. package/lib/oh-story/skills/story-deslop/scripts/normalize-punctuation.js +10 -5
  125. package/lib/oh-story/skills/story-deslop/scripts/style-whitelist.js +39 -0
  126. package/lib/oh-story/skills/story-import/SKILL.md +78 -82
  127. package/lib/oh-story/skills/story-import/references/character-state-reverse.md +1 -1
  128. package/lib/oh-story/skills/story-import/references/format-and-structure.md +2 -2
  129. package/lib/oh-story/skills/story-import/references/state-tracking.md +4 -69
  130. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +10 -8
  131. package/lib/oh-story/skills/story-import/references/tracking-initialization.md +31 -0
  132. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +9 -34
  133. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +197 -15
  134. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +210 -2
  135. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +3 -0
  136. package/lib/oh-story/skills/story-long-analyze/SKILL.md +91 -258
  137. package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +245 -0
  138. package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +2 -2
  139. package/lib/oh-story/skills/story-long-analyze/references/inspiration-library.md +105 -0
  140. package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +113 -105
  141. package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +169 -125
  142. package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +172 -64
  143. package/lib/oh-story/skills/story-long-analyze/references/semantic-acceptance-fixtures.md +84 -0
  144. package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +42 -132
  145. package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +5 -5
  146. package/lib/oh-story/skills/story-long-analyze/scripts/build_chapter_index.py +466 -0
  147. package/lib/oh-story/skills/story-long-analyze/scripts/inspect_existing_assets.py +702 -0
  148. package/lib/oh-story/skills/story-long-analyze/scripts/inspiration_index.py +864 -0
  149. package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +1106 -0
  150. package/lib/oh-story/skills/story-long-analyze/scripts/render_relation_chart.py +425 -0
  151. package/lib/oh-story/skills/story-long-scan/SKILL.md +4 -1
  152. package/lib/oh-story/skills/story-long-write/SKILL.md +52 -216
  153. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +29 -56
  154. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +72 -12
  155. package/lib/oh-story/skills/story-long-write/references/author-memory.md +61 -29
  156. package/lib/oh-story/skills/story-long-write/references/banned-words.md +3 -1
  157. package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +20 -0
  158. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +26 -0
  159. package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +10 -4
  160. package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +2 -2
  161. package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +6 -15
  162. package/lib/oh-story/skills/story-long-write/references/long-format.md +2 -0
  163. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +2 -0
  164. package/lib/oh-story/skills/story-long-write/references/project-files.md +110 -0
  165. package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +12 -4
  166. package/lib/oh-story/skills/story-long-write/references/reference-index.md +86 -0
  167. package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -1
  168. package/lib/oh-story/skills/story-long-write/references/style-craft.md +9 -29
  169. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  170. package/lib/oh-story/skills/story-long-write/references/style-resolution.md +27 -0
  171. package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +31 -0
  172. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +9 -34
  173. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +88 -118
  174. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +55 -119
  175. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +15 -11
  176. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +73 -54
  177. package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +22 -0
  178. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +63 -4
  179. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +934 -170
  180. package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +531 -0
  181. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +6 -1
  182. package/lib/oh-story/skills/story-long-write/scripts/check-degeneration.js +39 -20
  183. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +197 -15
  184. package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +90 -23
  185. package/lib/oh-story/skills/story-long-write/scripts/normalize-punctuation.js +10 -5
  186. package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +285 -0
  187. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +58 -0
  188. package/lib/oh-story/skills/story-long-write/scripts/style-whitelist.js +39 -0
  189. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +210 -2
  190. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +3 -0
  191. package/lib/oh-story/skills/story-review/SKILL.md +80 -87
  192. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +29 -56
  193. package/lib/oh-story/skills/story-review/references/author-memory.md +61 -29
  194. package/lib/oh-story/skills/story-review/references/banned-words.md +3 -1
  195. package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +10 -4
  196. package/lib/oh-story/skills/story-review/references/review-quality.md +6 -12
  197. package/lib/oh-story/skills/story-review/references/style-resolution.md +27 -0
  198. package/lib/oh-story/skills/story-review/references/tracking-initialization.md +31 -0
  199. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +9 -34
  200. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +934 -170
  201. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +6 -1
  202. package/lib/oh-story/skills/story-review/scripts/check-degeneration.js +39 -20
  203. package/lib/oh-story/skills/story-review/scripts/normalize-punctuation.js +10 -5
  204. package/lib/oh-story/skills/story-review/scripts/style-whitelist.js +39 -0
  205. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +210 -2
  206. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +3 -0
  207. package/lib/oh-story/skills/story-setup/SKILL.md +58 -43
  208. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +29 -56
  209. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +3 -1
  210. package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +154 -0
  211. package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +10 -4
  212. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +2 -2
  213. package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +2 -2
  214. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +1 -1
  215. package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +1 -1
  216. package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -1
  217. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  218. package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +27 -0
  219. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +63 -4
  220. package/lib/oh-story/skills/story-setup/references/diagnostics.md +38 -0
  221. package/lib/oh-story/skills/story-short-analyze/SKILL.md +22 -3
  222. package/lib/oh-story/skills/story-short-analyze/references/analysis-writing-techniques.md +1 -1
  223. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +5 -1
  224. package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +2 -2
  225. package/lib/oh-story/skills/story-short-scan/SKILL.md +5 -1
  226. package/lib/oh-story/skills/story-short-write/SKILL.md +15 -324
  227. package/lib/oh-story/skills/story-short-write/references/author-memory.md +61 -29
  228. package/lib/oh-story/skills/story-short-write/references/banned-words.md +3 -1
  229. package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +60 -0
  230. package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +10 -4
  231. package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +2 -2
  232. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +1 -1
  233. 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
  234. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +3 -3
  235. 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
  236. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +5 -5
  237. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +4 -4
  238. package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +5 -5
  239. 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
  240. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +1 -1
  241. package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +1 -1
  242. package/lib/oh-story/skills/story-short-write/references/reference-index.md +55 -0
  243. package/lib/oh-story/skills/story-short-write/references/short-craft.md +12 -14
  244. package/lib/oh-story/skills/story-short-write/references/short-deslop.md +10 -10
  245. package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -1
  246. package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -2
  247. package/lib/oh-story/skills/story-short-write/references/style-resolution.md +27 -0
  248. package/lib/oh-story/skills/story-short-write/references/workflow-design.md +78 -0
  249. package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +126 -0
  250. package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +32 -0
  251. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +5 -5
  252. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +934 -170
  253. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +6 -1
  254. package/lib/oh-story/skills/story-short-write/scripts/check-degeneration.js +39 -20
  255. package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +34 -5
  256. package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +90 -23
  257. package/lib/oh-story/skills/story-short-write/scripts/normalize-punctuation.js +10 -5
  258. package/lib/oh-story/skills/story-short-write/scripts/style-whitelist.js +39 -0
  259. package/lib/types/client/drama-production-view.d.ts.map +1 -1
  260. package/lib/types/client/file-activity.d.ts +19 -1
  261. package/lib/types/client/file-activity.d.ts.map +1 -1
  262. package/lib/types/client/index.d.ts.map +1 -1
  263. package/lib/types/client/production-prompts.d.ts +7 -0
  264. package/lib/types/client/production-prompts.d.ts.map +1 -1
  265. package/lib/types/client/production-runtime.d.ts +33 -0
  266. package/lib/types/client/production-runtime.d.ts.map +1 -1
  267. package/lib/types/drama-adapters.d.ts +5 -3
  268. package/lib/types/drama-adapters.d.ts.map +1 -1
  269. package/lib/types/native-hooks.d.ts +52 -0
  270. package/lib/types/native-hooks.d.ts.map +1 -1
  271. package/lib/types/role-identity.d.ts +17 -0
  272. package/lib/types/role-identity.d.ts.map +1 -0
  273. package/lib/types/role-tool.d.ts.map +1 -1
  274. package/lib/types/skill-provider.d.ts.map +1 -1
  275. package/lib/video-recap/CHANGELOG.md +31 -1
  276. package/lib/video-recap/manifest.json +162 -162
  277. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +4 -9
  278. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +47 -60
  279. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +77 -115
  280. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +41 -40
  281. package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +22 -32
  282. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +102 -192
  283. package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +14 -35
  284. package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +14 -12
  285. package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +8 -9
  286. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +3 -14
  287. package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +23 -12
  288. package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +1 -4
  289. package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +24 -25
  290. package/lib/video-recap/skills/video-assemble/scripts/lib.py +31 -85
  291. package/lib/video-recap/skills/video-assemble/scripts/media.py +100 -144
  292. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +101 -195
  293. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +10 -21
  294. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +90 -169
  295. package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +69 -122
  296. package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +10 -17
  297. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +33 -48
  298. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +14 -20
  299. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +135 -213
  300. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +49 -57
  301. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +38 -74
  302. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +46 -95
  303. package/lib/video-recap/skills/video-cut/scripts/lib.py +23 -25
  304. package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +70 -173
  305. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +28 -53
  306. package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +162 -322
  307. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +6 -7
  308. package/lib/video-recap/skills/video-recap/scripts/doctor.py +104 -124
  309. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +255 -412
  310. package/lib/video-recap/skills/video-recap/scripts/lib.py +30 -52
  311. package/lib/video-recap/skills/video-recap/scripts/materials.py +97 -120
  312. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +7 -12
  313. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +46 -97
  314. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +18 -36
  315. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +29 -47
  316. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +43 -76
  317. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +2 -1
  318. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +2 -1
  319. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +2 -2
  320. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +129 -145
  321. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +27 -20
  322. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +20 -31
  323. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +31 -106
  324. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +43 -113
  325. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +196 -377
  326. package/lib/video-recap/skills/video-script/scripts/agent_brief.py +1 -1
  327. package/lib/video-recap/skills/video-script/scripts/agent_text.py +63 -172
  328. package/lib/video-recap/skills/video-script/scripts/brief_context.py +80 -135
  329. package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +125 -167
  330. package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +93 -208
  331. package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +20 -46
  332. package/lib/video-recap/skills/video-script/scripts/lib.py +30 -26
  333. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +145 -140
  334. package/lib/video-recap/skills/video-script/scripts/review_response.py +12 -42
  335. package/lib/video-recap/skills/video-script/scripts/review_runner.py +0 -6
  336. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +39 -100
  337. package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +33 -124
  338. package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +1 -1
  339. package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +63 -172
  340. package/lib/video-recap/skills/video-understanding/scripts/asr.py +1 -2
  341. package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +80 -135
  342. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +125 -167
  343. package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +93 -208
  344. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +1 -1
  345. package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +20 -46
  346. package/lib/video-recap/skills/video-understanding/scripts/lib.py +31 -26
  347. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +145 -140
  348. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +39 -100
  349. package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +33 -124
  350. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +1 -4
  351. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +7 -13
  352. package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +12 -30
  353. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +98 -131
  354. package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +17 -28
  355. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +151 -240
  356. package/package.json +45 -45
  357. package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +0 -59
  358. package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +0 -53
  359. package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -42
  360. package/lib/oh-story/skills/story-long-write/references/state-tracking.md +0 -108
@@ -16,7 +16,8 @@ license: MIT
16
16
  取出本次提示词,建立一个有边界的运行 job。creator-first job 的 `source` 必须指向拥有这条提示词的
17
17
  当前 Markdown,`source_entry` 必须点名该文档允许的二级标题:`图片提示词.md` 用 `IMG-*`,
18
18
  `视频提示词.md` 用 `MOTION-*`,`分镜.md` 用 `SHOT-*`(modality 为 `image`,正文取该镜的
19
- `### 冻结关键帧提示词`)。分镜这一路是本套件里唯一能把某一镜的起始画面渲染成文件的入口,但不是创作者获得起始帧的唯一办法——
19
+ `### 冻结关键帧提示词`)。此选择器只提取首帧;尾帧需先由图片提示词阶段建立独立 `IMG-*` 状态图条目,
20
+ 再从 `图片提示词.md` 准备 image job。创作者也可以提供已有起始帧——
20
21
  在自己的工具里出图再放进项目同样成立,图片始终不进项目时走分镜的 `PLAN-...`;
21
22
  产出落在 `剧集/<EP>/制作成果/images/` 后,分镜 owner 才能把它绑成 `用途:起始帧` 的 `REF-...`。存在真实参考图时,
22
23
  还必须逐张填写 `reference_bindings` 的槽位、顺序、路径、中文名、用途以及允许/禁止控制范围;
@@ -75,11 +76,13 @@ python3 <本技能目录>/scripts/production_tool.py prepare <project> --job <jo
75
76
  python3 <本技能目录>/scripts/production_tool.py confirm <project> --job-id <id> --confirmation "CONFIRM <id> <code>"
76
77
  python3 <本技能目录>/scripts/production_tool.py run <project> --job-id <id> --adapter-config <outside-project-config.json>
77
78
  python3 <本技能目录>/scripts/production_tool.py status <project> --job-id <id>
79
+ python3 <本技能目录>/scripts/production_tool.py collect <project> --job-id <id> --adapter-config <outside-project-config.json>
78
80
  python3 <本技能目录>/scripts/production_tool.py audit <project>
79
81
  ```
80
82
 
81
83
  `prepare` 只验证并预览,不生产。`confirm` 只保存与当前 job 指纹绑定的一次性确认。
82
- `run` 才启动 adapter。`audit` 只对账本地任务历史、失败后恢复、重复内容尝试和当前输出字节,
84
+ `run` 才启动 adapter。`collect` 只取回**已经提交过、已经计费**的那次任务的结果,
85
+ 不重新提交、也不需要新的确认——见下面「中断不等于要重跑」。`audit` 只对账本地任务历史、失败后恢复、重复内容尝试和当前输出字节,
83
86
  不会调用供应商,也不把技术成功、文件存在或哈希一致写成媒体质量结论。同一 job 存在未决
84
87
  `running` attempt 时禁止重新 prepare、confirm 或 run;先等待完成或排查遗留 attempt。
85
88
 
@@ -97,8 +100,8 @@ python3 <本技能目录>/scripts/production_tool.py audit <project>
97
100
  - **tts**:从 `剧本.md` 读取原句与表演要求,声音参考由用户或现有媒体明确提供。不得在生产 job
98
101
  中改词,也不为 TTS 新建第六份创作文档。
99
102
  - **music**:读取 `视频提示词.md` 中创作者已确认的时间线音乐章节;主题曲使用已确认歌词,纯配乐
100
- 不携带歌词。供应商不能精确承诺时长时,生成源音轨后仍由剪辑按文档里的混音意图完成落点、循环、
101
- 淡入淡出和对白 ducking。
103
+ 不携带歌词。供应商不能精确承诺时长时,生成源音轨后仍由 `$short-drama-edit` 按文档里的混音意图完成落点、
104
+ 循环、淡入淡出和对白 ducking。
102
105
 
103
106
  一个 job 不混合 modality。大批量工作拆成创作者能看清数量和成本边界的小 job;不为方便把整季
104
107
  隐式塞进一次确认。
@@ -124,6 +127,9 @@ adapter 配置必须在项目外,只包含 argv 命令和超时;凭据由 ad
124
127
  固定高保真引用并校验尺寸、格式与透明背景限制。
125
128
  - [MiniMax Music](references/providers/minimax-music.md):使用 `music-3.0` 与 hex 结果,区分主题曲
126
129
  和纯配乐,不伪造时长请求字段。
130
+ - [MiniMax 语音](references/providers/minimax-speech.md):`tts` 模态;模型与 `voice_id` 都必须
131
+ 由账号与文档显式给出,不内置音色清单。只从预置音色合成,不做声音克隆——克隆是授权问题,
132
+ 创作者授权的参考录音按普通创作者输入走 `输入/`。
127
133
  - [MiniMax H3 视频](references/providers/minimax-h3-video.md):模型 ID、分辨率集合与时长区间必须
128
134
  由账号显式配置;提示词进 `content` 的 text 项,参考图按显式 role 绑定,本地参考在没有可信上传时
129
135
  fail closed。该模型与画面同一次生成声音,写法影响见视频提示词技能的目标模型能力档案。
@@ -133,6 +139,23 @@ adapter 配置必须在项目外,只包含 argv 命令和超时;凭据由 ad
133
139
 
134
140
  仓库自带 `fixture_adapter.py` 只用于离线测试,不代表真实生成质量或默认生产 adapter。
135
141
 
142
+ ## 中断不等于要重跑
143
+
144
+ 视频任务在**提交那一刻**就已经计费,不是在拿到结果时。提交之后的一切——轮询几分钟、下载——
145
+ 都可能被进程被杀、断网、机器休眠打断。内置 adapter 因此在拿到供应商任务 ID 的第一时间就把它
146
+ 写进 `handle_path`(早于第一次轮询),这个路径不随本次尝试一起删除。
147
+
148
+ 于是中断之后有三条确定的动作,不必再花一次钱:
149
+
150
+ 1. `audit` 会把带着任务 ID 的未完成尝试报成 `orphaned_provider_job`,
151
+ `action` 是 `collect_before_retry`;
152
+ 2. `collect` 用那个 ID 取回结果并把这次尝试标成成功;
153
+ 3. 只有在 `collect` 也确认那边确实失败之后,才走重新确认与重投的老路。
154
+
155
+ **不要在 `audit` 报出 `orphaned_provider_job` 时直接重投**——那是在为同一个镜头付第二次钱。
156
+ `collect` 不走确认闸门是有意的:闸门防的是意外花钱,而 collect 不花钱;
157
+ 如果它也要求重新确认,那么中断之后最省事的路径就变成再付一次,正好是闸门要防的事。
158
+
136
159
  ## 结果与复核
137
160
 
138
161
  成功后回报实际输出路径、媒体类型和运行状态;不要把“adapter 返回成功”写成质量结论。
@@ -145,6 +168,8 @@ adapter 配置必须在项目外,只包含 argv 命令和超时;凭据由 ad
145
168
  构图与角色一致、画面本身合规的图;音频被拒重录那句台词。改动写进新的 job 重新 prepare,让创
146
169
  作者在预览里看到改的是哪一项再确认;原样重投的那次确认不产生修复,只产生一笔费用。重复内容
147
170
  缺陷回到对应 prompt/spec owner。
171
+ 生产结束后不自动进入剪辑。素材要装配成成片时由用户点名 `$short-drama-edit`;它只取舍已有帧,
172
+ 不回头改本阶段的 job,也不生成新素材。
148
173
  如需质量复核,报告可把已有结果另行交给 `$short-drama-review`;不要在生产调用中自动启动复核。
149
174
  Dashboard 只负责展示这些文件和运行摘要,不提供 adapter 设置或生产按钮。
150
175
 
@@ -99,6 +99,12 @@ Keep this file outside the project:
99
99
  `command` is an argv array, never a shell string. Timeout is 1–3600 seconds. Do not put credentials in this file;
100
100
  let the adapter read its environment or operating-system credential store.
101
101
 
102
+ ## Capability sources
103
+
104
+ Use the provider's current API reference for roles, limits and supported combinations.
105
+ A relay can expose a different subset, reject inputs itself or transform requests.
106
+ Record the tested execution path and distinguish its observations from the native API contract.
107
+
102
108
  ## Adapter stdin
103
109
 
104
110
  The adapter receives one UTF-8 JSON document as raw stdin bytes. Read the
@@ -113,6 +119,22 @@ on the machine locale. The document contains the confirmed job plus:
113
119
  production tool. Every adapter output source must be a direct regular-file
114
120
  child of this directory. The whole directory is deleted after success or
115
121
  failure.
122
+ - `handle_path`: absolute path where the adapter records the provider's task id
123
+ **as soon as it has one, before it starts polling** — write
124
+ `{"provider_job_id": "<id>"}`, atomically. Unlike `output_root` this path
125
+ survives the attempt.
126
+ - `collect_provider_job_id`: present only on a collect call. When it is set the
127
+ adapter must **not submit anything**; it polls and downloads that existing
128
+ task and returns its outputs as usual.
129
+
130
+ ## Recovering a submitted task
131
+
132
+ Record the provider task ID before polling so an interrupted task can be collected without resubmission.
133
+ A local handle-write failure is non-fatal after submission. The tool copies the handle into the attempt
134
+ record on success or failure; `audit` reports unfinished tasks as `orphaned_provider_job` with
135
+ `action: collect_before_retry`.
136
+
137
+ `production_tool.py collect` retrieves the existing task without new submission or confirmation.
116
138
 
117
139
  It may translate provider-neutral parameters into its chosen SDK/API. Optional
118
140
  provider adapters under `scripts/` document and implement known translations;
@@ -31,6 +31,12 @@ required and are only accepted when the runtime profile above permits their valu
31
31
  optional for reference-conditioned jobs and **required** for text-to-video, where `adaptive` is
32
32
  refused because there is no reference frame to adapt to.
33
33
 
34
+ The adapter validates and forwards `duration`; it does not estimate speech length or retime dialogue.
35
+ Before preparing a speaking shot, compare the accepted shot duration, the prompt's spoken timeline,
36
+ and the job's `duration`. Resolve insufficient speaking time in the storyboard and regenerate the
37
+ dependent prompt before preparing the job. Prompt timestamps alone do not change the request duration.
38
+ The official video API has no separate speech-rate parameter.
39
+
34
40
  The prompt is compiled into one `text` item of the multimodal `content` array and is refused above
35
41
  7000 characters. Each declared reference becomes one further `content` item carrying an explicit
36
42
  `role`: `first_frame`, `last_frame`, `reference_image`, `reference_video`, or `reference_audio`.
@@ -68,18 +74,13 @@ structured: base/first-frame/first-last-frame tasks use `integrated_multimodal_d
68
74
  form. Chinese dialogue remains exact inside `<d>[Chinese] ...</d>`. Full details live in the
69
75
  video-prompt skill's `references/minimax-h3.md`.
70
76
 
71
- Audio generation is a **capability**, not a style:
72
- what changes upstream is only which axes of the target-model profile a project declares — see the
73
- video-prompt skill's target-model profile. Two consequences are worth stating here because they
74
- show up as production defects rather than as API errors:
75
-
76
- - an unstated audio channel is not a silent one. A shot that says nothing about music or speech can
77
- come back with invented score and invented lines, so the shot's own sound intent, including what
78
- must **not** be produced, has to be in the copyable body;
79
- - readable on-screen text is likewise produced unless it is excluded, so a shot with no readable-text
80
- obligation states that exclusion instead of relying on the model to omit it.
77
+ State the intended sound layers in the prompt. For no non-diegetic music, the
78
+ H3 dialect uses `non_diegetic_music: N/A`; small probes observed unwanted music
79
+ when this layer was omitted. They did not establish that omitted speech or
80
+ negative clauses cause extra dialogue.
81
81
 
82
- Neither is written here as a fixed phrase, and this suite never injects one. The wording belongs to
83
- the shot and to the project's declared prompt language.
82
+ Follow the project's subtitle and in-scene-text policy. Prompt wording belongs
83
+ to the video-prompt skill and the declared language, not to adapter-generated
84
+ boilerplate.
84
85
 
85
86
  Protocol reference: [MiniMax video generation API](https://platform.minimax.io/docs/api-reference/video-generation-v2-create).
@@ -0,0 +1,41 @@
1
+ # MiniMax Speech adapter
2
+
3
+ Adapter command:
4
+
5
+ ```json
6
+ {"command": ["python3", "/absolute/path/provider_adapters.py", "minimax-speech"], "timeout_seconds": 600}
7
+ ```
8
+
9
+ Required environment: `MINIMAX_API_KEY`. `MINIMAX_BASE_URL` optionally overrides the default
10
+ `https://api.minimax.io/v1` and must remain HTTPS.
11
+
12
+ The job uses the suite's `tts` modality and must have exactly one output. The production prompt is the
13
+ line to be spoken, verbatim — not a description of it. Supported public parameters are `model`,
14
+ `voice_id`, `emotion`, `speed`, `vol`, `pitch`, `sample_rate`, `bitrate`, and `format`. The requested
15
+ format must match the target extension and be `mp3` or `wav`.
16
+
17
+ `model` and `voice_id` are both required and neither has a default. The model is an account-enabled
18
+ endpoint, exactly as for the video providers. The voice is a creative decision recorded in
19
+ `视觉设定.md` under the character's 声音方向, and it reaches the adapter through the confirmed job.
20
+
21
+ ## Voice selection
22
+
23
+ Read the provider's voice listing and confirm availability for the chosen model and account.
24
+ The adapter validates the ID format, not catalogue membership. Record the selected ID in the
25
+ character's 声音方向 and pass it through the confirmed job.
26
+
27
+ This workflow selects preset voices. The adapter does not enrol or clone a voice;
28
+ any supplied recording retains its documented authorization and usage scope.
29
+
30
+ ## Request shape
31
+
32
+ The request is non-streaming with `output_format: hex`, `POST /t2a_v2`, and carries
33
+ `voice_setting` (`voice_id`, and any of `emotion`, `speed`, `vol`, `pitch`) alongside `audio_setting`
34
+ (`sample_rate`, `bitrate`, `format`). The adapter validates `base_resp.status_code`, decodes
35
+ `data.audio` as hexadecimal bytes, and writes it to a private temporary file.
36
+
37
+ `emotion` accepts the provider's seven values: `happy`, `sad`, `angry`, `fearful`, `disgusted`,
38
+ `surprised`, `neutral`. Anything outside that set is refused rather than passed through — a rejected
39
+ emotion is a typo caught before it is paid for.
40
+
41
+ Protocol reference: [MiniMax Text to Speech](https://platform.minimax.io/docs/api-reference/speech-t2a-http).
@@ -29,6 +29,13 @@ profile above explicitly permits their values, then sent as the API's top-level
29
29
  `generate_audio` is a boolean. The broad compiler envelope remains configurable rather than proving
30
30
  that every Seedance release supports every value.
31
31
 
32
+ The compiler also forwards `duration: -1` without applying the positive-integer range. For ordinary
33
+ Seedance 2.0/2.5 generation this asks the model to choose within its native range; for 2.5 `edit` it
34
+ preserves the source video's duration instead. The adapter does not estimate spoken dialogue or
35
+ lengthen a short job. Fixed-duration delivery needs an explicit accepted duration; auto-duration
36
+ output needs its actual media length and speech checked before timing is accepted. The API response's
37
+ integer `duration` rounds down the frame-based duration, so it is not a precise media measurement.
38
+
32
39
  `compile_seedance_payload` accepts explicit `reference_image`, `reference_video`, and
33
40
  `reference_audio` roles with matching HTTPS, `asset://` or base64 data URI values. It emits the matching multimodal
34
41
  content type and uses `@图片N`、`@视频N`、`@音频N` in its appended reference contract. The bundled
@@ -27,8 +27,22 @@ PNG = base64.b64decode(
27
27
  def main() -> int:
28
28
  parser = argparse.ArgumentParser()
29
29
  parser.add_argument("--fail", action="store_true")
30
+ # A real video provider bills at submission and can be interrupted at any
31
+ # point afterwards. `--submit-then-fail` reproduces exactly that: record the
32
+ # handle the way a live adapter does, then die before returning anything.
33
+ parser.add_argument("--submit-then-fail", action="store_true")
30
34
  args = parser.parse_args()
31
35
  job = json.load(sys.stdin.buffer)
36
+ collecting = job.get("collect_provider_job_id")
37
+ if args.submit_then_fail and not collecting:
38
+ destination = job.get("handle_path")
39
+ if isinstance(destination, str) and destination:
40
+ path = Path(destination)
41
+ path.parent.mkdir(parents=True, exist_ok=True)
42
+ path.write_text(
43
+ json.dumps({"provider_job_id": "fixture-task-1"}), encoding="utf-8"
44
+ )
45
+ return 7
32
46
  if args.fail:
33
47
  return 7
34
48
  directory_raw = job.get("output_root")
@@ -52,7 +66,10 @@ def main() -> int:
52
66
  handle.setframerate(8000)
53
67
  handle.writeframes(struct.pack("<h", 0) * 80)
54
68
  outputs.append({"target": target, "source": str(path)})
55
- json.dump({"outputs": outputs, "provider_job_id": "fixture-local"}, sys.stdout)
69
+ response = {"outputs": outputs, "provider_job_id": "fixture-local"}
70
+ if isinstance(collecting, str) and collecting:
71
+ response["provider_job_id"] = collecting
72
+ json.dump(response, sys.stdout)
56
73
  return 0
57
74
 
58
75
 
@@ -1547,6 +1547,37 @@ def _active_run(root: Path, job_id: str) -> dict[str, Any] | None:
1547
1547
  return running[-1] if running else None
1548
1548
 
1549
1549
 
1550
+ def _read_provider_handle(root: Path, job_id: str, run_id: str) -> str | None:
1551
+ """The provider job id an adapter recorded before it started polling.
1552
+
1553
+ A video task is billed the moment it is submitted, not when it is collected.
1554
+ If the adapter is then interrupted — killed, disconnected, the machine
1555
+ sleeps — the run record would otherwise say `running` with no way back to a
1556
+ task that is alive and already paid for, and the only move left is to submit
1557
+ (and pay) again. The adapter writes the id here as soon as it has one, into
1558
+ project metadata rather than the attempt's temporary directory, so it
1559
+ outlives the process that created it.
1560
+ """
1561
+
1562
+ try:
1563
+ document = _metadata_read_json(
1564
+ root, ("handles", _job_key(job_id)), f"{run_id}.json",
1565
+ maximum=MAX_RUN_RECORD_BYTES,
1566
+ )
1567
+ except (FileNotFoundError, ValueError, json.JSONDecodeError):
1568
+ return None
1569
+ if not isinstance(document, Mapping):
1570
+ return None
1571
+ value = document.get("provider_job_id")
1572
+ if isinstance(value, str) and 0 < len(value) <= 200:
1573
+ return value
1574
+ return None
1575
+
1576
+
1577
+ def _handle_metadata_path(root: Path, job_id: str, run_id: str) -> Path:
1578
+ return root / PRODUCTION_ROOT / "handles" / _job_key(job_id) / f"{run_id}.json"
1579
+
1580
+
1550
1581
  def _write_run(root: Path, job_id: str, run: Mapping[str, Any]) -> None:
1551
1582
  _metadata_atomic_json(
1552
1583
  root,
@@ -1620,6 +1651,9 @@ def run_job(root: Path, *, job_id: str, adapter_config: Path) -> dict[str, Any]:
1620
1651
  "run_id": run_id,
1621
1652
  "project_root": str(snapshot_root),
1622
1653
  "output_root": str(output_root),
1654
+ # Where to record the provider job id the instant it exists, so
1655
+ # an interrupted attempt can be collected instead of re-billed.
1656
+ "handle_path": str(_handle_metadata_path(root, job_id, run_id)),
1623
1657
  }
1624
1658
  )
1625
1659
  try:
@@ -1640,11 +1674,20 @@ def run_job(root: Path, *, job_id: str, adapter_config: Path) -> dict[str, Any]:
1640
1674
  "sha256": digest,
1641
1675
  }
1642
1676
  )
1677
+ if _run_status(root, job_id, run_id) == "succeeded":
1678
+ # `collect` reached the same provider task first and its
1679
+ # outputs are already in the project. Leave that record
1680
+ # alone rather than replacing a collected attempt.
1681
+ raise RuntimeError(
1682
+ "这次尝试已经被 collect 取回并标为成功;本次运行不覆盖它"
1683
+ )
1643
1684
  run["status"] = "succeeded"
1644
1685
  run["finished_at"] = utc_now()
1645
1686
  run["outputs"] = written
1646
- provider_job_id = response.get("provider_job_id")
1647
- if isinstance(provider_job_id, str) and len(provider_job_id) <= 200:
1687
+ provider_job_id = response.get("provider_job_id") or _read_provider_handle(
1688
+ root, job_id, run_id
1689
+ )
1690
+ if isinstance(provider_job_id, str) and 0 < len(provider_job_id) <= 200:
1648
1691
  run["provider_job_id"] = provider_job_id
1649
1692
  _write_run(root, job_id, run)
1650
1693
  except Exception as exc:
@@ -1653,6 +1696,12 @@ def run_job(root: Path, *, job_id: str, adapter_config: Path) -> dict[str, Any]:
1653
1696
  run["finished_at"] = utc_now()
1654
1697
  if isinstance(exc, AdapterError) and exc.public_error is not None:
1655
1698
  run["error"] = exc.public_error
1699
+ # A timeout or a killed adapter does not cancel the provider's
1700
+ # task. Carry the handle onto the failed record so `audit` can
1701
+ # say "collect this" instead of the creator paying twice.
1702
+ orphan = _read_provider_handle(root, job_id, run_id)
1703
+ if orphan is not None:
1704
+ run["provider_job_id"] = orphan
1656
1705
  _write_run(root, job_id, run)
1657
1706
  raise
1658
1707
  return {
@@ -1663,6 +1712,101 @@ def run_job(root: Path, *, job_id: str, adapter_config: Path) -> dict[str, Any]:
1663
1712
  }
1664
1713
 
1665
1714
 
1715
+ def _run_status(root: Path, job_id: str, run_id: str) -> str | None:
1716
+ """The status one attempt currently has on disk, re-read rather than cached."""
1717
+
1718
+ for run in _read_run_history(root, job_id):
1719
+ if str(run.get("run_id")) == run_id:
1720
+ status = run.get("status")
1721
+ return status if isinstance(status, str) else None
1722
+ return None
1723
+
1724
+
1725
+ def collect_job(root: Path, *, job_id: str, adapter_config: Path) -> dict[str, Any]:
1726
+ """Collect an already-submitted task's result without submitting a new one.
1727
+
1728
+ This is deliberately outside the confirmation gate. The gate exists to stop
1729
+ an unintended charge; collecting costs nothing, because the charge already
1730
+ happened when the task was submitted. Requiring a fresh confirmation here
1731
+ would mean the cheapest way out of an interrupted attempt is to pay again,
1732
+ which is exactly the outcome the gate is meant to prevent.
1733
+ """
1734
+
1735
+ root = find_project(root)
1736
+ with tempfile.TemporaryDirectory(prefix="short-drama-collect-") as directory:
1737
+ output_root = Path(directory) / "outputs"
1738
+ output_root.mkdir(parents=True)
1739
+ with _project_lock(root):
1740
+ job = _read_job(root, job_id)
1741
+ command, timeout = _load_adapter(adapter_config, str(job["adapter"]), root)
1742
+ target_run = None
1743
+ for run in reversed(_read_run_history(root, job_id)):
1744
+ if run.get("status") == "succeeded":
1745
+ continue
1746
+ handle = run.get("provider_job_id") or _read_provider_handle(
1747
+ root, job_id, str(run["run_id"])
1748
+ )
1749
+ if handle:
1750
+ target_run = (run, handle)
1751
+ break
1752
+ if target_run is None:
1753
+ raise RuntimeError(
1754
+ "no unfinished attempt of this job carries a provider job id; "
1755
+ "there is nothing to collect"
1756
+ )
1757
+ run, provider_job_id = target_run
1758
+
1759
+ payload = {key: job[key] for key in ALLOWED_JOB_KEYS if key in job}
1760
+ payload.update(
1761
+ {
1762
+ "run_id": str(run["run_id"]),
1763
+ "output_root": str(output_root),
1764
+ "collect_provider_job_id": provider_job_id,
1765
+ }
1766
+ )
1767
+ response = _run_adapter(command, timeout, payload, root)
1768
+ adapter_outputs = _validate_adapter_outputs(job, response, output_root)
1769
+ written: list[dict[str, Any]] = []
1770
+ with _project_lock(root):
1771
+ # A live `run_job` on the same provider task is indistinguishable
1772
+ # from a dead one — both sit at `running`. Re-read before writing,
1773
+ # or two writers race onto the same bytes and one record is lost.
1774
+ if _run_status(root, job_id, str(run["run_id"])) == "succeeded":
1775
+ raise RuntimeError(
1776
+ "这次尝试在取回期间已经自己完成了,产物已经落进项目;"
1777
+ "没有覆盖它——先看 status,确认无误就不需要再做别的"
1778
+ )
1779
+ for target_name, source in adapter_outputs:
1780
+ target = _project_file(root, target_name, create_parent=True)
1781
+ digest, size = _copy_output(
1782
+ source, target, overwrite=bool(job["overwrite"])
1783
+ )
1784
+ written.append(
1785
+ {
1786
+ "path": target_name,
1787
+ "media_type": MEDIA_TYPES[PurePosixPath(target_name).suffix.casefold()],
1788
+ "bytes": size,
1789
+ "sha256": digest,
1790
+ }
1791
+ )
1792
+ collected = dict(run)
1793
+ collected["status"] = "succeeded"
1794
+ collected["finished_at"] = utc_now()
1795
+ collected["outputs"] = written
1796
+ collected["provider_job_id"] = provider_job_id
1797
+ collected["collected"] = True
1798
+ collected.pop("error", None)
1799
+ _write_run(root, job_id, collected)
1800
+ return {
1801
+ "job_id": job_id,
1802
+ "run_id": str(run["run_id"]),
1803
+ "state": "succeeded",
1804
+ "collected": True,
1805
+ "provider_job_id": provider_job_id,
1806
+ "outputs": written,
1807
+ }
1808
+
1809
+
1666
1810
  def job_status(root: Path, *, job_id: str) -> dict[str, Any]:
1667
1811
  root = find_project(root)
1668
1812
  job = _read_job(root, job_id)
@@ -1816,6 +1960,28 @@ def audit_project(root: Path) -> dict[str, Any]:
1816
1960
  attempts_failed += sum(run["status"] == "failed" for run in completed)
1817
1961
  attempts_running += len(running)
1818
1962
  attempts_superseded += len(history) - len(current)
1963
+ for run in current:
1964
+ if run.get("status") == "succeeded":
1965
+ continue
1966
+ handle = run.get("provider_job_id") or _read_provider_handle(
1967
+ root, job_id, str(run["run_id"])
1968
+ )
1969
+ if not handle:
1970
+ continue
1971
+ problems.append(
1972
+ {
1973
+ "code": "orphaned_provider_job",
1974
+ "job_id": job_id,
1975
+ "run_id": run["run_id"],
1976
+ "provider_job_id": handle,
1977
+ "action": "collect_before_retry",
1978
+ # A live attempt and a dead one both sit at `running`, so
1979
+ # this finding cannot tell them apart. Collecting is still
1980
+ # the right first move — it is the only one that costs
1981
+ # nothing — and it refuses safely if the attempt finished.
1982
+ "note": "status 为 running 时它可能仍在正常轮询;collect 会先确认再写",
1983
+ }
1984
+ )
1819
1985
  if running:
1820
1986
  running_jobs += 1
1821
1987
  for run in running:
@@ -1986,6 +2152,13 @@ def build_parser() -> argparse.ArgumentParser:
1986
2152
  run.add_argument("project")
1987
2153
  run.add_argument("--job-id", required=True)
1988
2154
  run.add_argument("--adapter-config", required=True)
2155
+ collect = commands.add_parser(
2156
+ "collect",
2157
+ help="Fetch the result of an already-submitted provider task without paying again.",
2158
+ )
2159
+ collect.add_argument("project")
2160
+ collect.add_argument("--job-id", required=True)
2161
+ collect.add_argument("--adapter-config", required=True)
1989
2162
  status = commands.add_parser("status", help="Show one media job state.")
1990
2163
  status.add_argument("project")
1991
2164
  status.add_argument("--job-id", required=True)
@@ -2011,6 +2184,12 @@ def main(argv: list[str] | None = None) -> int:
2011
2184
  job_id=args.job_id,
2012
2185
  adapter_config=Path(args.adapter_config),
2013
2186
  )
2187
+ elif args.command == "collect":
2188
+ result = collect_job(
2189
+ Path(args.project),
2190
+ job_id=args.job_id,
2191
+ adapter_config=Path(args.adapter_config),
2192
+ )
2014
2193
  elif args.command == "status":
2015
2194
  result = job_status(Path(args.project), job_id=args.job_id)
2016
2195
  else: