@oh-story/dsh 0.1.9 → 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 (197) hide show
  1. package/README.md +12 -12
  2. package/lib/client.js +204 -72
  3. package/lib/client.js.map +3 -3
  4. package/lib/drama/manifest.json +84 -74
  5. package/lib/drama/skills/short-drama/SKILL.md +2 -2
  6. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +14 -0
  7. package/lib/drama/skills/short-drama-assets/references/character-and-look.md +10 -0
  8. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +6 -9
  9. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +28 -0
  10. package/lib/drama/skills/short-drama-edit/SKILL.md +27 -29
  11. package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +2 -2
  12. package/lib/drama/skills/short-drama-edit/references/cut-craft.md +24 -67
  13. package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +27 -56
  14. package/lib/drama/skills/short-drama-edit/references/generated-footage.md +37 -79
  15. package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +39 -92
  16. package/lib/drama/skills/short-drama-edit/references/stage-contract.md +12 -16
  17. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +126 -11
  18. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +64 -0
  19. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +2 -0
  20. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +11 -0
  21. package/lib/drama/skills/short-drama-produce/SKILL.md +5 -1
  22. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +14 -20
  23. package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +8 -26
  24. package/lib/drama/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
  25. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +142 -1
  26. package/lib/drama/skills/short-drama-review/SKILL.md +2 -3
  27. package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +6 -19
  28. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +9 -35
  29. package/lib/drama/skills/short-drama-storyboard/SKILL.md +10 -6
  30. package/lib/drama/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
  31. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +58 -21
  32. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +1 -1
  33. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +10 -9
  34. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +17 -0
  35. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +12 -38
  36. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +4 -7
  37. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +8 -22
  38. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +6 -19
  39. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +2 -3
  40. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +34 -9
  41. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +2 -2
  42. package/lib/drama/skills/short-drama-write/SKILL.md +2 -4
  43. package/lib/index.js +289 -91
  44. package/lib/index.js.map +4 -4
  45. package/lib/novel-to-game/README.md +299 -59
  46. package/lib/novel-to-game/README_ZH.md +299 -56
  47. package/lib/novel-to-game/VERSION +1 -1
  48. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +3 -1
  49. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +11 -3
  50. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +397 -8
  51. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +0 -31
  52. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +71 -73
  53. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +78 -89
  54. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +37 -14
  55. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +5 -5
  56. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -1
  57. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +11 -16
  58. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +11 -11
  59. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +23 -0
  60. package/lib/novel-to-game/manifest.json +77 -87
  61. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +7 -12
  62. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +27 -106
  63. package/lib/novel-to-game/skills/game-build/SKILL.md +13 -14
  64. package/lib/novel-to-game/skills/game-build/references/blender-asset-workflow.md +24 -0
  65. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +45 -82
  66. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +14 -45
  67. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +30 -65
  68. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +13 -27
  69. package/lib/novel-to-game/skills/game-concept/SKILL.md +13 -15
  70. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +28 -75
  71. package/lib/novel-to-game/skills/game-qa/SKILL.md +6 -7
  72. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +9 -5
  73. package/lib/novel-to-game/skills/game-world-design/SKILL.md +12 -16
  74. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +23 -72
  75. package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +14 -48
  76. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +36 -103
  77. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +4 -10
  78. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +4 -4
  79. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +9 -21
  80. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +5 -4
  81. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +5 -8
  82. package/lib/oh-story/manifest.json +230 -185
  83. package/lib/oh-story/roles/chapter-extractor.md +177 -237
  84. package/lib/oh-story/roles/consistency-checker.md +4 -4
  85. package/lib/oh-story/roles/narrative-writer.md +24 -25
  86. package/lib/oh-story/roles/story-architect.md +5 -5
  87. package/lib/oh-story/roles/story-explorer.md +11 -11
  88. package/lib/oh-story/skills/story/SKILL.md +13 -7
  89. package/lib/oh-story/skills/story/VERSION +1 -1
  90. package/lib/oh-story/skills/story/references/author-memory.md +54 -24
  91. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +934 -170
  92. package/lib/oh-story/skills/story-cover/SKILL.md +10 -1
  93. package/lib/oh-story/skills/story-deslop/SKILL.md +40 -53
  94. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +11 -10
  95. package/lib/oh-story/skills/story-deslop/references/author-memory.md +54 -24
  96. package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +1 -1
  97. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +934 -170
  98. package/lib/oh-story/skills/story-import/SKILL.md +77 -81
  99. package/lib/oh-story/skills/story-import/references/character-state-reverse.md +1 -1
  100. package/lib/oh-story/skills/story-import/references/state-tracking.md +4 -69
  101. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +10 -8
  102. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +8 -6
  103. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +34 -16
  104. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +210 -2
  105. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +3 -0
  106. package/lib/oh-story/skills/story-long-analyze/SKILL.md +91 -258
  107. package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +245 -0
  108. package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +2 -2
  109. package/lib/oh-story/skills/story-long-analyze/references/inspiration-library.md +105 -0
  110. package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +113 -105
  111. package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +169 -125
  112. package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +172 -64
  113. package/lib/oh-story/skills/story-long-analyze/references/semantic-acceptance-fixtures.md +84 -0
  114. package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +42 -132
  115. package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +5 -5
  116. package/lib/oh-story/skills/story-long-analyze/scripts/build_chapter_index.py +466 -0
  117. package/lib/oh-story/skills/story-long-analyze/scripts/inspect_existing_assets.py +702 -0
  118. package/lib/oh-story/skills/story-long-analyze/scripts/inspiration_index.py +864 -0
  119. package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +1106 -0
  120. package/lib/oh-story/skills/story-long-analyze/scripts/render_relation_chart.py +425 -0
  121. package/lib/oh-story/skills/story-long-scan/SKILL.md +4 -1
  122. package/lib/oh-story/skills/story-long-write/SKILL.md +38 -29
  123. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +11 -10
  124. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +25 -20
  125. package/lib/oh-story/skills/story-long-write/references/author-memory.md +54 -24
  126. package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +3 -3
  127. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +26 -0
  128. package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +4 -0
  129. package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +2 -2
  130. package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +4 -13
  131. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +1 -1
  132. package/lib/oh-story/skills/story-long-write/references/project-files.md +9 -7
  133. package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +12 -4
  134. package/lib/oh-story/skills/story-long-write/references/reference-index.md +1 -2
  135. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +8 -6
  136. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +86 -115
  137. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +55 -120
  138. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +15 -11
  139. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +66 -68
  140. package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +3 -3
  141. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +4 -12
  142. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +934 -170
  143. package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +139 -18
  144. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +34 -16
  145. package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +5 -3
  146. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +58 -0
  147. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +210 -2
  148. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +3 -0
  149. package/lib/oh-story/skills/story-review/SKILL.md +68 -85
  150. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +11 -10
  151. package/lib/oh-story/skills/story-review/references/author-memory.md +54 -24
  152. package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +4 -0
  153. package/lib/oh-story/skills/story-review/references/review-quality.md +4 -10
  154. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +8 -6
  155. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +934 -170
  156. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +210 -2
  157. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +3 -0
  158. package/lib/oh-story/skills/story-setup/SKILL.md +50 -42
  159. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +11 -10
  160. package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +1 -1
  161. package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +4 -0
  162. package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +2 -2
  163. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +1 -1
  164. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +4 -12
  165. package/lib/oh-story/skills/story-setup/references/diagnostics.md +1 -1
  166. package/lib/oh-story/skills/story-short-analyze/SKILL.md +22 -3
  167. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +5 -1
  168. package/lib/oh-story/skills/story-short-scan/SKILL.md +5 -1
  169. package/lib/oh-story/skills/story-short-write/SKILL.md +6 -147
  170. package/lib/oh-story/skills/story-short-write/references/author-memory.md +54 -24
  171. package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +4 -0
  172. package/lib/oh-story/skills/story-short-write/references/reference-index.md +2 -0
  173. package/lib/oh-story/skills/story-short-write/references/workflow-design.md +3 -2
  174. package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +126 -0
  175. package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +32 -0
  176. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +934 -170
  177. package/lib/types/client/drama-production-view.d.ts.map +1 -1
  178. package/lib/types/client/file-activity.d.ts +19 -1
  179. package/lib/types/client/file-activity.d.ts.map +1 -1
  180. package/lib/types/client/index.d.ts.map +1 -1
  181. package/lib/types/client/production-prompts.d.ts +7 -0
  182. package/lib/types/client/production-prompts.d.ts.map +1 -1
  183. package/lib/types/client/production-runtime.d.ts +16 -0
  184. package/lib/types/client/production-runtime.d.ts.map +1 -1
  185. package/lib/types/drama-adapters.d.ts +5 -3
  186. package/lib/types/drama-adapters.d.ts.map +1 -1
  187. package/lib/types/native-hooks.d.ts +52 -0
  188. package/lib/types/native-hooks.d.ts.map +1 -1
  189. package/lib/types/role-identity.d.ts +17 -0
  190. package/lib/types/role-identity.d.ts.map +1 -0
  191. package/lib/types/role-tool.d.ts.map +1 -1
  192. package/lib/types/skill-provider.d.ts.map +1 -1
  193. package/package.json +45 -45
  194. package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +0 -58
  195. package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +0 -53
  196. package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -38
  197. package/lib/oh-story/skills/story-long-write/references/state-tracking.md +0 -108
package/lib/index.js CHANGED
@@ -35,8 +35,9 @@ var DRAMA_ADAPTERS = [
35
35
  name: "minimax-h3",
36
36
  label: "MiniMax H3",
37
37
  modality: "video",
38
- requiredEnv: ["MINIMAX_API_KEY", "MINIMAX_VIDEO_MODEL", "MINIMAX_VIDEO_RESOLUTIONS"],
39
- optionalEnv: ["MINIMAX_VIDEO_BASE_URL", "MINIMAX_VIDEO_RATIOS", "MINIMAX_VIDEO_MIN_DURATION", "MINIMAX_VIDEO_MAX_DURATION"],
38
+ // Every H3 job carries an integer duration, and the adapter refuses it without this range.
39
+ requiredEnv: ["MINIMAX_API_KEY", "MINIMAX_VIDEO_MODEL", "MINIMAX_VIDEO_RESOLUTIONS", "MINIMAX_VIDEO_MIN_DURATION", "MINIMAX_VIDEO_MAX_DURATION"],
40
+ optionalEnv: ["MINIMAX_VIDEO_BASE_URL", "MINIMAX_VIDEO_RATIOS"],
40
41
  timeoutSeconds: 3600,
41
42
  reference: "short-drama-produce/references/providers/minimax-h3-video.md"
42
43
  },
@@ -48,6 +49,15 @@ var DRAMA_ADAPTERS = [
48
49
  optionalEnv: ["MINIMAX_BASE_URL"],
49
50
  timeoutSeconds: 600,
50
51
  reference: "short-drama-produce/references/providers/minimax-music.md"
52
+ },
53
+ {
54
+ name: "minimax-speech",
55
+ label: "MiniMax Speech",
56
+ modality: "tts",
57
+ requiredEnv: ["MINIMAX_API_KEY"],
58
+ optionalEnv: ["MINIMAX_BASE_URL"],
59
+ timeoutSeconds: 600,
60
+ reference: "short-drama-produce/references/providers/minimax-speech.md"
51
61
  }
52
62
  ];
53
63
  function generatedDirectory(temporaryRoot) {
@@ -118,17 +128,30 @@ var DSH_SKILL_BRIDGE = [
118
128
  "DSH owns the workspace, model, preset, permissions, Session Log, tools, subagents, cancellation, resume, and Agent UI.",
119
129
  "Never start another Agent runtime, session transport, Dashboard, SSE stream, polling loop, or model configuration.",
120
130
  "All seven upstream Oh Story specialist Roles are bundled. Invoke one with oh_story_role and a self-contained prompt.",
121
- "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.",
131
+ "Never inspect .claude/agents, .codex/agents, .opencode/agents, .agents/agents, .zcode, or .story-deployed to decide whether a Role is available.",
132
+ "Oh Story Roles are reached only through oh_story_role. Never start one through a generic subagent or Task tool \u2014 not DSH's subagent or subagent_fork tools and not invoke_subagent \u2014 and never pass a Role name as subagent_type, agent_type, agent, or TypeName, even where the upstream text below describes that for another host.",
122
133
  "Use only DSH-visible tools. DSH sandbox and permission policy remain authoritative.",
134
+ // Upstream deploys these 「与作者协作」 rules only in its CLAUDE.md/AGENTS.md
135
+ // templates, which DSH does not install.
136
+ "Working with the author: reply and report in the author's writing terms (book, chapter, outline, settings), never in script, field, or state names; attach a raw error only when something failed. A template block marked <!-- author-report --> is only a format reference: output its text directly, without that marker and without a code fence.",
137
+ "Record a writing habit or preference the author asks to remember, forget, or confirm only through the story Skill's author memory, whose scripts/author_memory_commit.py writes .story/\u4F5C\u8005\u8BB0\u5FC6/; never keep it in a host or DSH memory. Simply follow a one-off request without recording it.",
138
+ "Never edit the bundled Skill files (SKILL.md, references/, scripts/). When a Skill script fails, stop and show the author the command you ran and its error instead of patching or working around it.",
123
139
  "</oh-story-dsh-integration>"
124
140
  ].join("\n");
141
+ var DSH_ANALYSIS_BATCHES = [
142
+ "Stage 2 batches in DSH: dispatch each planned batch with oh_story_role, role chapter-extractor, and a self-contained prompt carrying the batch ID, the input kind, the plan's source_files (per-chapter source_locator for a raw batch) and chapter_chars, the output file {\u62C6\u6587\u76EE\u5F55}/_analysis_cache/\u8F93\u5165-{\u6279\u6B21ID}.md, and the hand-off cache path when there is one.",
143
+ "The extractor reads the source itself, writes or edits only that batch input file (DSH denies any other write or edit by a chapter-extractor child), and replies with one receipt line; do not read the file yourself.",
144
+ "Commit it with manage_analysis_run.py commit, one commit at a time in this session; on rejection hand the error and the file path back to a chapter-extractor to fix in place with edit.",
145
+ "Run manage_analysis_run.py and the other bundled Python 3 scripts through the current DSH execution world, trying python3, python, then py -3. If none is available, stop and tell the author in plain words that this step needs Python 3 on the machine running DSH; never simulate a script's output or hand-write the files it owns.",
146
+ "Keep concurrent oh_story_role calls modest. The default is upstream's \u6709\u9650\u5E76\u884C tier: three batches per round, issued together, the next round only after all three are committed; an unasked, full, multi-book, or import run uses it. DSH runs calls from one step in parallel only up to the host's own limit and may run them one after another, so never promise more. Use \u4E32\u884C or \u4E0D\u9650\u6279\u6B21\u987A\u5E8F only when the author chooses it, and under \u4E0D\u9650\u6279\u6B21\u987A\u5E8F halve the count on any rate limit, timeout, or child error."
147
+ ].join(" ");
125
148
  var DSH_SKILL_OVERRIDES = {
126
149
  story: "The \u5C0F\u8BF4 workspace is an official DSH conversation view. Never start or open a second web application.",
127
150
  "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.",
128
- "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.",
129
- "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.",
130
- "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.",
131
- "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.",
151
+ "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. ${DSH_ANALYSIS_BATCHES}`,
152
+ "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. Create and assemble \u6B63\u6587/ chapter files only with the write and edit tools, never through shell redirection, cp, mv, tee, or a script, so that DSH's outline guard, its Tracking reminder, and the \u5C0F\u8BF4 view observe the write; upstream's own in-place chapter fixes such as storyctl.py chapter check --fix-punctuation stay as upstream describes.",
153
+ "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. When a review falls back to solo in DSH, the reason is that oh_story_role or DSH's spawn runtime is unavailable in this Session (Fallback agent tool unavailable -> solo) or a Role run failed (Fallback spawn failed -> solo); say that in one plain sentence. Never tell the author that the reviewers are not installed, missing, or outdated, and never tell them to run /story-setup.",
154
+ "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. Phase 2 runs the story-long-analyze pipeline the DSH way below; an import counts as automatic continuation, so it uses the limited-parallel tier without asking. ${DSH_ANALYSIS_BATCHES} Run the Python 3 scripts this Skill needs (storyctl.py wordcount measure, tracking_commit.py init and check) the same way. Keep upstream's Phase 3-L order: Step 2 copies the manuscript into \u6B63\u6587/, Step 6 writes the \u7EC6\u7EB2, Step 7 initialises Tracking. DSH's prose guard mirrors the outline gate of upstream proseBlockReason for books with \u5927\u7EB2/ or \u8FFD\u8E2A/, so open an import window before Step 2 copies any chapter: create .story/work/\u5BFC\u5165\u4E2D.md in the book directory (the workspace root in DSH's usual single-book layout) with one line naming the book being imported, and delete it right after Step 7's tracking_commit.py init and check both succeed. The window only holds while \u8FFD\u8E2A/_tracking-state.json is absent.`,
132
155
  "story-deslop": "Use the bundled narrative-writer Role through oh_story_role when specialist review is useful. Never inspect platform agent directories.",
133
156
  "story-short-analyze": "Use oh_story_role for specialist analysis. Never inspect platform agent directories or require external Agent deployment.",
134
157
  "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.",
@@ -141,7 +164,7 @@ var DSH_DRAMA_BRIDGE = [
141
164
  "DSH owns the workspace, model, preset, permissions, Session Log, tools, approvals, cancellation, resume, and Agent UI.",
142
165
  "The \u77ED\u5267 tab is the creator workspace. Never start dashboard_server.py, another web server, Dashboard, Agent runtime, session transport, or model configuration.",
143
166
  "Drama Skills 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.",
144
- "\u5267\u96C6/<EP>/\u526A\u8F91\u5355.md is the v0.7 assembly record, not a sixth creative truth. It is written after footage exists and records only which frames of that footage reach the cut; changing a line, a shot's job, or a declared duration means returning to the document that owns it.",
167
+ "\u5267\u96C6/<EP>/\u526A\u8F91\u5355.md is the v0.7 assembly record, not a sixth creative truth: the five creator documents stay the creative truth. It is written after footage exists and records what reaches the cut \u2014 which footage is kept or left unused and why, each cut's in/out timing, post-processing such as shot-match correction or an external mix, and the delivery spec including optional \u9897\u7C92 grain. It never changes a line, a shot's job, or a declared duration; changing any of those means returning to the document that owns it.",
145
168
  "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.",
146
169
  "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.",
147
170
  "Use only tools visible in the current DSH preset and preserve the upstream project ownership, freshness, review, and explicit production-confirmation contracts.",
@@ -151,8 +174,16 @@ var DSH_DRAMA_BRIDGE = [
151
174
  ].join("\n");
152
175
  var DSH_DRAMA_OVERRIDES = {
153
176
  "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.",
154
- "short-drama-produce": (skillRoot) => `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. DeepSeek generates no media: every image, video, or music result comes from a provider adapter. The bundled adapters are ${dramaAdapterSummary()}; they are registered for this DSH host in ${dramaAdapterConfigPath(skillRoot).path} \u2014 pass that file as --adapter-config unless the creator names another config (${DRAMA_ADAPTER_CONFIG_ENV}). Credentials are read only from the DSH host process environment. Before preparing a paid job, name the adapter that will run it; if its variables are missing, tell the creator exactly which ones to export before starting DSH instead of failing inside run. Never read, print, or write credential values. A submitted video job is already billed, so an interrupted run is recovered, not resubmitted: when audit reports orphaned_provider_job, recover it with collect, passing --job-id \u2014 the drama job the finding names, not its provider_job_id, which collect reads from the run handle itself. collect spends nothing and does not need the confirmation gate \u2014 the gate exists to stop unintended spending \u2014 but re-running the same job does, and would charge the creator twice.`,
155
- "short-drama-edit": "Assembly runs inside this DSH Session. Write \u5267\u96C6/<EP>/\u526A\u8F91\u5355.md first, then run edit_tool.py check and render through the current DSH execution world; the rendered cut belongs under \u5267\u96C6/<EP>/\u5236\u4F5C\u6210\u679C/\u6210\u7247/ and the \u77ED\u5267 tab shows it there. ffmpeg and ffprobe come from the DSH host \u2014 when one is missing, say so instead of approximating or reporting an untested measurement as a pass. Keep the zero-dependency ffmpeg subtitle route by default; the Remotion route installs Node dependencies and renders every frame through a headless browser, so take it only after the creator approves that install, and never treat it as a second Agent runtime, dashboard, or session transport. This stage never generates footage and never edits \u5267\u672C.md, \u5206\u955C.md, or \u89C6\u9891\u63D0\u793A\u8BCD.md \u2014 route those back to their owning Skill.",
177
+ "short-drama-produce": (skillRoot) => `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 a previewed image or video 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. track_job's jobKind is image, video or composition (the assembled cut), so from this Skill it registers image and video jobs only: speech (tts) and music jobs pass through the same prepare \u2192 explicit creator confirmation \u2192 run gate but are never registered with track_job or relabelled as another kind, and their audio results stay under \u5267\u96C6/<EP>/\u5236\u4F5C\u6210\u679C/. In this DSH integration audio is never bound as a video job's reference: upstream's creator-first path has no audio binding (\u8F93\u5165\u53C2\u8003\u56FE takes png/jpg/webp images only) and documents audio only as an external step, the edit stage's mix. DeepSeek generates no media: every image, video, speech, or music result comes from a provider adapter. The bundled adapters are ${dramaAdapterSummary()}; they are registered for this DSH host in ${dramaAdapterConfigPath(skillRoot).path} \u2014 pass that file as --adapter-config unless the creator names another config (${DRAMA_ADAPTER_CONFIG_ENV}). Credentials are read only from the DSH host process environment. Before preparing a paid job, name the adapter that will run it; if its variables are missing, tell the creator exactly which ones to export before starting DSH instead of failing inside run. Never read, print, or write credential values. A submitted video job is already billed, so an interrupted run is recovered, not resubmitted: when audit reports orphaned_provider_job, recover it with collect, passing --job-id \u2014 the drama job the finding names, not its provider_job_id, which collect reads from the run handle itself. collect spends nothing and does not need the confirmation gate \u2014 the gate exists to stop unintended spending \u2014 but re-running the same job does, and would charge the creator twice.`,
178
+ "short-drama-edit": [
179
+ "Assembly runs inside this DSH Session. Write \u5267\u96C6/<EP>/\u526A\u8F91\u5355.md first, then run edit_tool.py check and render through the current DSH execution world; the rendered cut belongs under \u5267\u96C6/<EP>/\u5236\u4F5C\u6210\u679C/\u6210\u7247/ and the \u77ED\u5267 tab shows it there. ffmpeg and ffprobe come from the DSH host \u2014 when one is missing, say so instead of approximating or reporting an untested measurement as a pass.",
180
+ // edit_tool.py 0.7.1 `_unaccounted_shots`: `- 未采用镜头:` is read only outside CUT blocks, split on `;`, and each item must fullmatch `MOTION-…(理由:<non-empty>)`.
181
+ "check blocks until every `## MOTION-*` heading in \u89C6\u9891\u63D0\u793A\u8BCD.md either backs a CUT's \u6765\u6E90 or is listed in the cut list's opening block, before the first `## CUT-` heading, on one line: `- \u672A\u91C7\u7528\u955C\u5934\uFF1AMOTION-\u2026\uFF08\u7406\u7531\uFF1A\u2026\uFF09\uFF1BMOTION-\u2026\uFF08\u7406\u7531\uFF1A\u2026\uFF09`. Join items with a full-width `\uFF1B`; each reason is non-empty, contains no `\uFF1B`, and names \u6587\u4EF6\u7F3A\u5931, \u8D28\u91CF\u4E0D\u53EF\u7528 or \u53D9\u4E8B\u53D6\u820D.",
182
+ "Every cut must share one width, height and frame rate: render hard-joins the encoded segments and never scales or retimes them. When check reports a mismatch, normalise the odd clips to the delivery spec with ffmpeg in the DSH execution world, under approval, preserving the composition. Write them under \u5267\u96C6/<EP>/\u5236\u4F5C\u6210\u679C/\u6210\u7247/\u89C4\u683C\u7EDF\u4E00/, an edit-owned intermediate, never beside the produce-stage originals and never over produced footage, and name them without the original's job-id token (the MOTION ID will do). Record the crop or pad in that CUT's block (not in its \u753B\u9762 line, which accepts only \u4EAE\u5EA6/\u9971\u548C/\u8272\u6E29), point \u6765\u6E90 at the new file, update \u5165\u70B9/\u51FA\u70B9 if the conversion moved them, and rerun check.",
183
+ "The default burned-subtitle route needs an ffmpeg built with libass; when the host's ffmpeg lacks it, say so instead of silently dropping subtitles. The Remotion route installs Node dependencies and renders every frame through a headless browser, so take it only after the creator approves that install, and never treat it as a second Agent runtime, dashboard, or session transport.",
184
+ "The \u58F0\u97F3 line in \u526A\u8F91\u5355.md is a record, not an instruction the built-in render executes: render only trims, hard-joins, burns subtitles, applies \u753B\u9762 corrections and optional \u9897\u7C92, and normalises loudness. A mix, crossfade, music bed, transition or format conversion is an external ffmpeg step, run under approval, that writes a temporary file, ends as render does with whole-film two-pass loudnorm to the declared \u4EA4\u4ED8\u54CD\u5EA6 (I=<\u4EA4\u4ED8\u54CD\u5EA6>:TP=-1.5:LRA=11, the second pass fed the first pass's measurements with linear=true, audio re-encoded as AAC 192k at 48 kHz), and only then replaces \u5267\u96C6/<EP>/\u5236\u4F5C\u6210\u679C/\u6210\u7247/\u6210\u7247.mp4; record its command in \u526A\u8F91\u5355.md and repeat it after any re-render, which overwrites that file. Run edit_tool.py verify last, on that delivered file, and report every \u672A\u6D4B item as untested, never as passed.",
185
+ "This stage never generates footage and never edits \u5267\u672C.md, \u5206\u955C.md, or \u89C6\u9891\u63D0\u793A\u8BCD.md \u2014 route those back to their owning Skill."
186
+ ].join(" "),
156
187
  "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."
157
188
  };
158
189
  var DSH_GAME_BRIDGE = [
@@ -184,29 +215,59 @@ var DSH_NATIVE_SKILLS = {
184
215
  \u5728\u5F53\u524D DSH Session \u5185\u5224\u65AD\u7528\u6237\u610F\u56FE\uFF0C\u5E76\u52A0\u8F7D\u6700\u5339\u914D\u7684 Oh Story Skill\uFF1A
185
216
 
186
217
  - \u65B0\u5EFA\u6216\u4FEE\u590D\u5C0F\u8BF4\u5DE5\u7A0B\uFF1Astory-setup
187
- - \u957F\u7BC7\u9009\u9898/\u626B\u699C/\u62C6\u6587/\u65E5\u66F4\uFF1Astory-long-scan\u3001story-long-analyze\u3001story-long-write
218
+ - \u957F\u7BC7\u89C4\u5212\u4E0E\u5199\u4F5C\uFF08\u8BA8\u8BBA\u957F\u7BC7\u7ED3\u6784\u3001\u89C4\u5212\u5267\u60C5\u3001\u5F00\u4E66\u3001\u5199\u5927\u7EB2\u3001\u8865\u7EC6\u7EB2\u3001\u5199\u6B63\u6587\u3001\u65E5\u66F4\uFF09\uFF1A
219
+ story-long-write\u3002\u53EA\u8981\u7ED3\u6784\u3001\u5927\u7EB2\u3001\u5377\u7EB2\u6216\u7EC6\u7EB2\u65F6\u53EA\u4EA4\u4ED8\u6240\u8BF7\u6C42\u7684\u89C4\u5212\uFF0C\u4E0D\u5199\u6B63\u6587\uFF1B
220
+ \u7528\u6237\u660E\u786E\u8981\u6C42\u5199\u6B63\u6587\u624D\u8FDB\u5165\u6B63\u6587\u6D41\u7A0B
221
+ - \u957F\u7BC7\u9009\u9898/\u626B\u699C\uFF1Astory-long-scan\uFF1B\u957F\u7BC7\u62C6\u6587\uFF1Astory-long-analyze
222
+ - \u7075\u611F\u5E93\u3001\u63D0\u70BC\u7075\u611F\u3001\u8DE8\u4E66\u7075\u611F\u805A\u5408\u3001\u66F4\u65B0\u7075\u611F\u5E93\uFF1Astory-long-analyze \u7684\u53EF\u9009\u7075\u611F\u5E93\u7BA1\u9053
223
+ \uFF08\u5355\u4E66\u62C6\u6587\u4E0D\u81EA\u52A8\u5165\u5E93\uFF09
188
224
  - \u77ED\u7BC7\u9009\u9898/\u62C6\u6587/\u5199\u4F5C\uFF1Astory-short-scan\u3001story-short-analyze\u3001story-short-write
189
225
  - \u5BFC\u5165\u5DF2\u6709\u4F5C\u54C1\uFF1Astory-import
190
226
  - \u5BA1\u7A3F\u4E0E\u53BB AI \u5473\uFF1Astory-review\u3001story-deslop
191
227
  - \u5C01\u9762\uFF1Astory-cover
228
+ - \u67E5\u672C\u4E66\u7684\u89D2\u8272\u3001\u4F0F\u7B14\u3001\u8FDB\u5EA6\u6216\u8BBE\u5B9A\uFF1A\u7528 oh_story_role \u8C03\u7528 story-explorer\uFF1B\u67E5\u5916\u90E8\u8D44\u6599\uFF1A
229
+ story-researcher\u3002\u56DE\u7B54\u8BB2\u6545\u4E8B\u91CC\u7684\u4E8B\uFF0C\u7F16\u53F7\u53EA\u8DDF\u7740\u6545\u4E8B\u63CF\u8FF0\u51FA\u73B0
192
230
  - \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
193
231
  references/author-memory.md\uFF0C\u53EA\u7528\u672C skill \u7684 scripts/author_memory_commit.py \u7BA1\u7406
194
- \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
232
+ \u4E24\u7EA7\u4F5C\u8005\u8BB0\u5FC6\uFF1A\u5168\u5C40\u3001\u9898\u6750\u3001\u6D41\u7A0B\u6761\u76EE\u5728\u5DE5\u4F5C\u533A .story/\u4F5C\u8005\u8BB0\u5FC6/\uFF08AP \u7F16\u53F7\uFF09\uFF0C\u672C\u4E66\u6761\u76EE\u5728
233
+ \u4E66\u7EA7 store\uFF08BP \u7F16\u53F7\uFF09\u3002--workspace \u5FC5\u987B\u663E\u5F0F\u4F20\u3002DSH \u7684\u5C0F\u8BF4\u5DE5\u4F5C\u533A\u901A\u5E38\u5C31\u662F\u4E66\u6839
234
+ \uFF08\u6B63\u6587/\u3001\u8BBE\u5B9A/ \u76F4\u63A5\u5728\u5DE5\u4F5C\u533A\u6839\uFF09\uFF1A\u6B64\u65F6 --workspace \u4E0E --book-root \u90FD\u4F20\u5DE5\u4F5C\u533A\u672C\u8EAB\uFF0C
235
+ \u4E66\u7EA7 store \u5728 .story/\u4F5C\u8005\u8BB0\u5FC6/\u4E66\u7EA7/\uFF1B\u4E66\u5728\u5B50\u76EE\u5F55\u65F6 --book-root \u4F20\u90A3\u672C\u4E66\u7684\u76EE\u5F55\u3002
236
+ \u5904\u7406\u67D0\u672C\u4E66\u65F6\u6BCF\u6761\u547D\u4EE4\u90FD\u5E26 --book-root\uFF08\u4E66\u7EA7\u64CD\u4F5C\u7F3A\u5B83\u76F4\u63A5\u62A5\u9519\uFF09\uFF0Cquery \u5FC5\u987B\u5E26
237
+ --kind\uFF08\u53EF\u91CD\u590D\uFF09\u3002\u53EA\u8BB0\u4F5C\u8005\u660E\u786E\u8BF4\u51FA\u7684\u504F\u597D\uFF0C\u4E0D\u4ECE\u53CD\u590D\u4FEE\u6539\u6216\u6210\u7A3F\u63A8\u65AD\u3002\u5DE5\u5177\u672A\u8FD4\u56DE
238
+ Author Memory Receipt \u524D\u4E0D\u5F97\u58F0\u79F0\u5DF2\u8BB0\u4F4F\uFF1B\u544A\u8BC9\u4F5C\u8005\u65F6\u5148\u7528\u4E00\u53E5\u4EBA\u8BDD\u8BF4\u8BB0\u4F4F\u4E86\u4EC0\u4E48\uFF0C
239
+ \u56DE\u6267\u653E\u6700\u540E\u4E00\u884C\u3002\u5DE5\u4F5C\u533A\u753B\u50CF\u91CC\u8FD8\u6709\u300C\u672C\u4E66\uFF1A\u300D\u6761\u76EE\uFF08\u5347\u7EA7\u524D\u5199\u5165\u7684\u672C\u4E66\u504F\u597D\uFF0C\u5DF2\u4E0D\u53C2\u4E0E
240
+ \u67E5\u8BE2\uFF09\u65F6\uFF0C\u63D0\u8BAE\u8FD0\u884C\u4E00\u6B21 migrate --workspace {\u5DE5\u4F5C\u533A} --book-root {\u4E66\u76EE\u5F55} \u628A\u5B83\u4EEC\u642C\u8FDB
241
+ \u4E66\u7EA7 store\u3002
195
242
 
196
- \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
197
- 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
198
- \u89C6\u56FE\u67E5\u770B\uFF0CAgent \u8FC7\u7A0B\u901A\u8FC7\u53F3\u4FA7\u52A8\u6001\u680F\u6216\u5B98\u65B9 Chat \u67E5\u770B\u3002\u7981\u6B62\u542F\u52A8\u72EC\u7ACB Dashboard\u3002`,
243
+ \u53EA\u8BF4 /story\u3001\u770B\u4E0D\u51FA\u610F\u56FE\u65F6\uFF0C\u4E0D\u8D34\u8DEF\u7531\u8868\uFF0C\u7ED9\u56DB\u4E2A\u767D\u8BDD\u9009\u9879\uFF1A\u300C\u5F00\u4E00\u672C\u957F\u7BC7\u6216\u63A5\u7740\u5199\u300D\u2192
244
+ story-long-write\uFF1B\u300C\u5199\u4E00\u7BC7\u77ED\u7BC7\u300D\u2192 story-short-write\uFF1B\u300C\u628A\u4E00\u7AE0\u6539\u5F97\u4E0D\u90A3\u4E48 AI\u300D\u2192
245
+ story-deslop\uFF1B\u300C\u66F4\u591A\uFF08\u62C6\u4E66\u3001\u626B\u699C\u3001\u5BFC\u5165\u65E7\u7A3F\u3001\u5BA1\u7A3F\u3001\u5C01\u9762\uFF09\u300D\u2192 \u518D\u5217\u8FDB\u9636\u9879\u3002\u610F\u56FE\u660E\u786E\u65F6
246
+ \u76F4\u63A5\u8FDB\u5165\u5BF9\u5E94 Skill\uFF1B\u53EA\u5DEE\u4E00\u4E2A\u4F1A\u6539\u53D8\u6D41\u7A0B\u7684\u9009\u62E9\u65F6\u53EA\u95EE\u8FD9\u4E00\u4E2A\u95EE\u9898\u3002\u9879\u76EE\u6587\u4EF6\u3001Agent\u3001
247
+ \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\u89C6\u56FE\u67E5\u770B\uFF0C
248
+ Agent \u8FC7\u7A0B\u901A\u8FC7\u53F3\u4FA7\u52A8\u6001\u680F\u6216\u5B98\u65B9 Chat \u67E5\u770B\u3002\u7981\u6B62\u542F\u52A8\u72EC\u7ACB Dashboard\u3002`,
199
249
  "story-setup": `# story-setup \u2014 DSH \u539F\u751F\u5C0F\u8BF4\u5DE5\u7A0B\u521D\u59CB\u5316
200
250
 
201
251
  \u53EA\u521D\u59CB\u5316\u6216\u6821\u9A8C\u5F53\u524D DSH workspace \u4E2D\u7684\u5C0F\u8BF4\u6570\u636E\uFF0C\u4E0D\u90E8\u7F72\u4EFB\u4F55 Agent \u5E73\u53F0\u6587\u4EF6\u3002
202
252
 
203
253
  1. \u68C0\u67E5\u73B0\u6709\u6B63\u6587\u3001\u8BBE\u5B9A\u3001\u5927\u7EB2\u548C\u8FFD\u8E2A\u6587\u4EF6\uFF0C\u5DF2\u6709\u5185\u5BB9\u7EDD\u4E0D\u8986\u76D6\u3002
204
254
  2. \u6839\u636E\u7528\u6237\u58F0\u660E\u4E0E\u73B0\u6709\u7ED3\u6784\u5224\u65AD\u957F\u7BC7/\u77ED\u7BC7\uFF1B\u65E0\u6CD5\u53EF\u9760\u5224\u65AD\u65F6\u8BF7\u6C42\u786E\u8BA4\u3002
205
- 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
255
+ 3. \u957F\u7BC7\u6309\u9700\u5EFA\u7ACB \u6B63\u6587/\u3001\u8BBE\u5B9A/\u3001\u5927\u7EB2/\u3002\u521D\u59CB\u5316\u3001\u5F00\u4E66\u3001\u5927\u7EB2\u548C\u7EC6\u7EB2\u9636\u6BB5\u90FD\u4E0D\u5EFA \u8FFD\u8E2A/\uFF0C
256
+ \u4E5F\u4E0D\u5199 \u8FFD\u8E2A/_tracking-state.json\uFF1A\u8FFD\u8E2A\u5728\u7B2C\u4E00\u7AE0\u6B63\u6587\u52A8\u7B14\u524D\uFF0C\u7531 story-long-write
257
+ \u5355\u7AE0\u6D41\u7A0B\u7528\u5B83\u7684 scripts/tracking_commit.py init \u521D\u59CB\u5316\uFF08\u4E8B\u52A1 JSON \u653E\u4E66\u76EE\u5F55
258
+ .story/work/\uFF0Ccheck \u901A\u8FC7\u540E\u5220\u6389\uFF09\uFF0C\u4E4B\u540E\u6BCF\u7AE0\u7531\u811A\u672C\u63D0\u4EA4\u3002_tracking-state.json \u4E0E
259
+ \u4E0A\u4E0B\u6587.md \u7B49\u6D3E\u751F Tracking \u89C6\u56FE\u53EA\u7531\u811A\u672C\u751F\u6210\uFF0C\u7EDD\u4E0D\u624B\u6539\u3002
206
260
  \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
207
- 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
261
+ 4. \u5DF2\u6709\u6B63\u6587\u5374\u6CA1\u6709 \u8FFD\u8E2A/_tracking-state.json \u65F6\uFF0C\u4E0D\u81EA\u884C\u8865\u5EFA\u8FFD\u8E2A\u6587\u4EF6\uFF1A\u5DF2\u6709\u65E7 \u8FFD\u8E2A/
262
+ \u7684\u662F\u65E7\u8FFD\u8E2A\u7ED3\u6784\uFF0C\u5EFA\u8BAE\u8D70 story-import \u7684\u300C\u65E7\u8FFD\u8E2A\u9879\u76EE\u8FC1\u79FB\u300D\uFF1B\u6CA1\u6709 \u8FFD\u8E2A/ \u7684\u65E2\u6709\u4E66\u7A3F
263
+ \u5EFA\u8BAE\u7528 story-import \u5BFC\u5165\u3002
264
+ 5. \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
208
265
  \u751F\u6210 .claude\u3001.codex\u3001.opencode\u3001.agents\u3001.zcode\u3001AGENTS.md \u6216 .story-deployed\u3002
209
- 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
266
+ 6. \u7ED9\u4F5C\u8005\u7684\u62A5\u544A\u6309\u8FD9\u4E2A\u987A\u5E8F\u5199\uFF1A\u5148\u5199\u300C\u73B0\u5728\u53EF\u4EE5\u505A\u4EC0\u4E48\u300D\uFF0C\u7528\u5199\u4E66\u7684\u8BDD\u5217\u771F\u6B63\u53EF\u7528\u7684\u4E8B
267
+ \uFF08\u5982\u300C\u53EF\u4EE5\u5F00\u65B0\u4E66\u3001\u7EED\u5199\uFF1A\u8BF4 /story-long-write\u300D\uFF09\uFF1B\u518D\u5199\u300C\u4F60\u8FD8\u9700\u8981\u505A\u7684\u4E8B\u300D\uFF0C\u9010\u6761
268
+ \u53EF\u7167\u505A\uFF08\u542B\u9700\u8981\u4F5C\u8005\u786E\u8BA4\u7684\u957F\u7BC7/\u77ED\u7BC7\u5224\u65AD\uFF09\uFF0C\u6CA1\u6709\u5C31\u5199\u300C\u65E0\u9700\u5176\u4ED6\u64CD\u4F5C\u300D\u3002\u8FD9\u4E24\u6BB5\u4E0D\u51FA\u73B0
269
+ \u811A\u672C\u540D\u3001\u5B57\u6BB5\u540D\u3001\u72B6\u6001\u540D\u6216\u6587\u4EF6\u8DEF\u5F84\uFF1B\u6700\u540E\u624D\u7B80\u77ED\u5217\u51FA\u521B\u5EFA\u548C\u4FDD\u7559\u4E86\u54EA\u4E9B\u6587\u4EF6\u3002
270
+ \u4E0D\u8981\u914D\u7F6E\u6A21\u578B\u3001\u6743\u9650\u3001Hooks \u6216 Session\u3002
210
271
 
211
272
  \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
212
273
  \u53EA\u52A0\u8F7D\u5F53\u524D\u4EFB\u52A1\u9700\u8981\u7684\u6587\u4EF6\u3002`,
@@ -233,6 +294,8 @@ Agent\u3001\u6A21\u578B\u3001\u6743\u9650\u3001Session Log \u548C UI \u5747\u753
233
294
  4. \u6309\u9898\u6750\u5206\u5E03\u3001\u65B0\u9898\u6750\u4FE1\u53F7\u3001\u7ECF\u5178\u9898\u6750\u53D8\u5316\u3001\u7BC7\u5E45\u4E0E\u66F4\u65B0\u3001\u4E66\u540D\u6A21\u5F0F\u3001\u5F00\u5934\u5356\u70B9\u548C
234
295
  \u5DEE\u5F02\u5316\u5143\u7D20\u5206\u6790\u3002\u9700\u8981\u51B3\u7B56\u95E8\u7981\u65F6\u4F7F\u7528 references/topic-decision.md\u3002
235
296
  5. \u8F93\u51FA\u5E02\u573A\u6982\u51B5\u3001\u9898\u6750\u70ED\u5EA6\u3001\u8BC1\u636E\u4E0E\u53EF\u4FE1\u5EA6\u3001\u98CE\u9669\u3001\u4E09\u9879\u53EF\u6267\u884C\u65B9\u5411\u548C\u4E0B\u6B21\u590D\u626B\u65F6\u95F4\u3002
297
+ 6. \u62A5\u544A\u5199\u7ED9\u4F5C\u8005\uFF1A\u8BB2\u5E02\u573A\u7ED3\u8BBA\u548C\u80FD\u5199\u7684\u65B9\u5411\uFF1B\u811A\u672C\u540D\u3001\u547D\u4EE4\u548C\u91C7\u96C6\u72B6\u6001\u7801\u4E0D\u8FDB\u62A5\u544A\u3002
298
+ \u67D0\u4E2A\u699C\u6CA1\u91C7\u5230\uFF0C\u5C31\u7528\u4E00\u53E5\u8BDD\u8BF4\u660E\u300CXX \u699C\u8FD9\u6B21\u6CA1\u62FF\u5230\uFF08\u539F\u56E0\uFF09\uFF0C\u7ED3\u8BBA\u4E0D\u542B\u5B83\u300D\u3002
236
299
 
237
300
  \u5F53\u524D Preset \u6CA1\u6709\u7F51\u9875\u80FD\u529B\u4E14\u7528\u6237\u4E5F\u672A\u63D0\u4F9B\u6837\u672C\u65F6\uFF0C\u4F7F\u7528\u5185\u7F6E\u53C2\u8003\u5B8C\u6210\u65B9\u6CD5\u8BBA\u5206\u6790\uFF0C
238
301
  \u5E76\u5217\u51FA\u4ECD\u9700\u6838\u9A8C\u7684\u699C\u5355\uFF1B\u4E0D\u8981\u542F\u52A8 CDP\u3001\u72EC\u7ACB\u6D4F\u89C8\u5668\u6216\u5E76\u884C\u8FD0\u884C\u65F6\u3002`,
@@ -247,7 +310,10 @@ Agent\u3001\u6A21\u578B\u3001\u6743\u9650\u3001Session Log \u548C UI \u5747\u753
247
310
  3. \u8BB0\u5F55\u6765\u6E90 URL\u3001\u91C7\u96C6\u65E5\u671F\u3001\u699C\u5355\u53E3\u5F84\u3001\u6709\u6548\u6837\u672C\u6570\u3001\u7F3A\u5931\u5B57\u6BB5\u548C\u5F02\u5E38\u9879\u3002
248
311
  4. \u5206\u6790\u60C5\u7EEA\u7C7B\u578B\u3001\u9898\u6750\u70ED\u70B9\u3001\u7BC7\u5E45\u3001\u5F00\u5934\u3001\u7ED3\u5C3E\u3001\u6807\u9898\u3001\u4EBA\u8BBE\u4E0E\u4F20\u64AD\u89E6\u53D1\u70B9\uFF1B
249
312
  \u7ED9\u6BCF\u4E2A\u8D8B\u52BF\u6807\u6CE8\u8BC1\u636E\u5F3A\u5EA6\u3001\u9971\u548C\u98CE\u9669\u548C\u6709\u6548\u671F\u3002
250
- 5. \u8F93\u51FA\u5E02\u573A\u6982\u51B5\u3001\u60C5\u7EEA\u70ED\u5EA6\u3001\u9898\u6750\u70ED\u70B9\u3001\u5173\u952E\u6570\u636E\u3001\u98CE\u53E3\u9884\u8B66\u3001\u4E09\u9879\u53EF\u5199\u65B9\u5411\u548C\u590D\u626B\u65F6\u95F4\u3002
313
+ 5. \u8F93\u51FA\u5E02\u573A\u6982\u51B5\uFF08\u626B\u699C\u65F6\u95F4\u3001\u6838\u5FC3\u53D1\u73B0\u548C\u53EF\u4FE1\u5EA6\uFF1A\u6837\u672C\u591A\u5C11\u7BC7\u3001\u6765\u81EA\u54EA\u51E0\u4E2A\u699C\uFF0C\u5E76\u5EFA\u8BAE
314
+ \u54EA\u5929\u524D\u540E\u518D\u626B\u4E00\u6B21\uFF09\u3001\u60C5\u7EEA\u70ED\u5EA6\u3001\u9898\u6750\u70ED\u70B9\u3001\u5173\u952E\u6570\u636E\u3001\u98CE\u53E3\u9884\u8B66\u3001\u4E09\u9879\u53EF\u5199\u65B9\u5411\u3002
315
+ 6. \u62A5\u544A\u5199\u7ED9\u4F5C\u8005\uFF1A\u8BB2\u5E02\u573A\u7ED3\u8BBA\u548C\u80FD\u5199\u7684\u65B9\u5411\uFF1B\u811A\u672C\u540D\u3001\u547D\u4EE4\u548C SKIP \u8FD9\u7C7B\u91C7\u96C6\u72B6\u6001\u4E0D\u8FDB\u62A5\u544A\u3002
316
+ \u67D0\u4E2A\u5E73\u53F0\u6CA1\u91C7\u5230\uFF0C\u5C31\u7528\u4E00\u53E5\u8BDD\u8BF4\u660E\u300CXX \u8FD9\u6B21\u6CA1\u62FF\u5230\uFF08\u539F\u56E0\uFF09\uFF0C\u7ED3\u8BBA\u4E0D\u542B\u5B83\u300D\u3002
251
317
 
252
318
  \u5F53\u524D Preset \u6CA1\u6709\u7F51\u9875\u80FD\u529B\u4E14\u7528\u6237\u4E5F\u672A\u63D0\u4F9B\u6837\u672C\u65F6\uFF0C\u53EA\u505A\u5386\u53F2\u8D44\u6599\u5206\u6790\u5E76\u5217\u51FA\u9A8C\u8BC1\u52A8\u4F5C\uFF1B
253
319
  \u4E0D\u8981\u542F\u52A8 CDP\u3001\u72EC\u7ACB\u6D4F\u89C8\u5668\u6216\u5E76\u884C\u8FD0\u884C\u65F6\u3002`
@@ -419,11 +485,82 @@ async function hostPython(probe = commandOutput) {
419
485
  }
420
486
 
421
487
  // src/native-hooks.ts
422
- import { createUserMessage } from "@deepseek-ai/dsh-llm";
488
+ import { boundContextSummary, createUserMessage } from "@deepseek-ai/dsh-llm";
489
+
490
+ // src/role-provider.ts
491
+ import { readFile as readFile2 } from "node:fs/promises";
492
+ import { basename as basename2, dirname as dirname3, join as join3, resolve as resolve3 } from "node:path";
493
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
494
+ var OH_STORY_ROLE_NAMES = [
495
+ "chapter-extractor",
496
+ "character-designer",
497
+ "consistency-checker",
498
+ "narrative-writer",
499
+ "story-architect",
500
+ "story-explorer",
501
+ "story-researcher"
502
+ ];
503
+ function roleBody(source) {
504
+ const frontmatter = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/u.exec(source);
505
+ return source.slice(frontmatter?.[0].length ?? 0).trim();
506
+ }
507
+ function defaultBundledRoleRoot() {
508
+ const current = dirname3(fileURLToPath2(import.meta.url));
509
+ return basename2(current) === "src" ? resolve3(current, "../../knowledge/oh-story/roles") : resolve3(current, "oh-story/roles");
510
+ }
511
+ async function loadBundledRole(name2, roleRoot = defaultBundledRoleRoot(), execution = "tool-free") {
512
+ const body = roleBody(await readFile2(join3(resolve3(roleRoot), `${name2}.md`), "utf8"));
513
+ if (body.length === 0) throw new Error(`Bundled role "${name2}" is empty.`);
514
+ const integration = execution === "tool-free" ? [
515
+ "You are running inside an Oh Story review-required DSH collaboration. The caller supplies every permitted input in the prompt.",
516
+ "Do not read or write project files, do not call tools, and do not follow legacy .claude/.codex path or deployment instructions in the upstream role text.",
517
+ "Return only the output contract requested by the caller; never claim to have changed the project."
518
+ ] : [
519
+ "You are running as a native oh-story-dsh specialist. The current DSH workspace and visible tool set are your complete authority boundary.",
520
+ "Never inspect or require legacy .claude/.opencode/.codex agent deployment files; this exact pinned Role is already active.",
521
+ "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.",
522
+ "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.",
523
+ "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."
524
+ ];
525
+ return [
526
+ `OH_STORY_DSH_ROLE:${name2}`,
527
+ ...integration,
528
+ "",
529
+ body
530
+ ].join("\n");
531
+ }
532
+
533
+ // src/role-identity.ts
534
+ var ROLE_LABEL_PREFIX = "oh-story:";
535
+ function ohStoryRoleLabel(role) {
536
+ return `${ROLE_LABEL_PREFIX}${role}`;
537
+ }
538
+ function roleFromLabel(label) {
539
+ if (typeof label !== "string" || !label.startsWith(ROLE_LABEL_PREFIX)) return void 0;
540
+ const name2 = label.slice(ROLE_LABEL_PREFIX.length);
541
+ return OH_STORY_ROLE_NAMES.find((role) => role === name2);
542
+ }
543
+ var roleSessions = /* @__PURE__ */ new WeakMap();
544
+ function observeOhStoryRoleDescriptor(session, event) {
545
+ if (event.type !== "subagent/descriptor" || roleSessions.has(session)) return;
546
+ const role = roleFromLabel(event.data.label);
547
+ if (role !== void 0) roleSessions.set(session, role);
548
+ }
549
+ function markOhStoryRoleSession(session, role) {
550
+ if (!roleSessions.has(session)) roleSessions.set(session, role);
551
+ }
552
+ function ohStoryRoleOfSession(session) {
553
+ return session === void 0 ? void 0 : roleSessions.get(session);
554
+ }
555
+
556
+ // src/native-hooks.ts
423
557
  var MUTATION_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "str_replace_editor"]);
558
+ function argumentRecord(args) {
559
+ return typeof args === "object" && args !== null && !Array.isArray(args) ? args : void 0;
560
+ }
424
561
  function mutationPath(name2, args) {
425
- if (!MUTATION_TOOLS.has(name2) || typeof args !== "object" || args === null || Array.isArray(args)) return void 0;
426
- const record2 = args;
562
+ const record2 = argumentRecord(args);
563
+ if (!MUTATION_TOOLS.has(name2) || record2 === void 0) return void 0;
427
564
  if (name2 === "str_replace_editor") {
428
565
  if (!(/* @__PURE__ */ new Set(["create", "str_replace", "insert"])).has(String(record2.command))) return void 0;
429
566
  return typeof record2.path === "string" && record2.path.trim() !== "" ? record2.path : void 0;
@@ -441,6 +578,19 @@ function normalizedRelativePath(path) {
441
578
  }
442
579
  return segments.join("/");
443
580
  }
581
+ function proseLocation(path) {
582
+ const segments = path.split("/");
583
+ if (segments.length < 2) return void 0;
584
+ const index = segments.lastIndexOf("\u6B63\u6587", segments.length - 2);
585
+ if (index < 0) return void 0;
586
+ return { book: segments.slice(0, index).join("/"), name: segments.at(-1) ?? "" };
587
+ }
588
+ function inBook(book, path) {
589
+ return book === "" ? path : `${book}/${path}`;
590
+ }
591
+ function lastSegment(path) {
592
+ return path.replaceAll("\\", "/").split("/").filter((part) => part !== "").at(-1) ?? "";
593
+ }
444
594
  function detectStoryMutation(name2, args, cwd) {
445
595
  const rawPath = mutationPath(name2, args);
446
596
  if (cwd === void 0 || rawPath === void 0) return void 0;
@@ -451,8 +601,9 @@ function detectStoryMutation(name2, args, cwd) {
451
601
  if (absolute && !insideRoot) return void 0;
452
602
  const normalized = normalizedRelativePath(absolute ? candidate.slice(root.length + 1) : candidate);
453
603
  if (normalized === void 0) return void 0;
454
- if (!normalized.startsWith("\u6B63\u6587/")) return void 0;
455
- const chapterText = /第0*(\d+)章/u.exec(normalized)?.[1];
604
+ const location = proseLocation(normalized);
605
+ if (location === void 0) return void 0;
606
+ const chapterText = /^第0*(\d+)章/u.exec(location.name)?.[1];
456
607
  return { root, path: normalized, ...chapterText === void 0 ? {} : { chapter: Number(chapterText) } };
457
608
  }
458
609
  async function storyMutation(exec, fs) {
@@ -476,20 +627,35 @@ async function target(fs, root, path, signal) {
476
627
  async function exists(fs, root, path, signal) {
477
628
  return target(fs, root, path, signal).then((value) => fs.stat(value, signal)).then((info) => info !== void 0, () => false);
478
629
  }
479
- async function hasChapterOutline(fs, root, chapter, signal) {
480
- const entries = await target(fs, root, "\u5927\u7EB2", signal).then((directory) => fs.listDir(directory, signal)).catch(() => []);
630
+ async function isLongFormBook(fs, root, book, signal) {
631
+ return await exists(fs, root, inBook(book, "\u5927\u7EB2"), signal) || await exists(fs, root, inBook(book, "\u8FFD\u8E2A"), signal);
632
+ }
633
+ async function hasChapterOutline(fs, root, book, chapter, signal) {
634
+ const entries = await target(fs, root, inBook(book, "\u5927\u7EB2"), signal).then((directory) => fs.listDir(directory, signal)).catch(() => []);
481
635
  return entries.some((entry) => entry.type === "file" && Number(/^细纲_第0*(\d+)章.*\.md$/u.exec(entry.name)?.[1]) === chapter);
482
636
  }
637
+ var TRACKING_STATE = "\u8FFD\u8E2A/_tracking-state.json";
638
+ var IMPORT_MARKER = ".story/work/\u5BFC\u5165\u4E2D.md";
639
+ async function hasTrackingState(fs, root, book, signal) {
640
+ return exists(fs, root, inBook(book, TRACKING_STATE), signal);
641
+ }
642
+ async function hasImportSignal(fs, root, book, signal) {
643
+ if (await exists(fs, root, inBook(book, IMPORT_MARKER), signal)) return true;
644
+ const bookName = book === "" ? lastSegment(root) : lastSegment(book);
645
+ return bookName !== "" && await exists(fs, root, `\u62C6\u6587\u5E93/${bookName}`, signal);
646
+ }
483
647
  async function validateStoryMutation(fs, mutation, signal) {
484
- const hasLongFormLayout = await exists(fs, mutation.root, "\u5927\u7EB2", signal) || await exists(fs, mutation.root, "\u8FFD\u8E2A", signal);
485
- if (!hasLongFormLayout) return void 0;
486
- if (!await exists(fs, mutation.root, "\u8FFD\u8E2A/_tracking-state.json", signal)) {
648
+ const location = proseLocation(mutation.path);
649
+ if (mutation.chapter === void 0 || location === void 0) return void 0;
650
+ const { book } = location;
651
+ if (!await isLongFormBook(fs, mutation.root, book, signal)) return void 0;
652
+ if (await exists(fs, mutation.root, mutation.path, signal)) return void 0;
653
+ if (!await hasTrackingState(fs, mutation.root, book, signal) && await hasImportSignal(fs, mutation.root, book, signal)) {
487
654
  return void 0;
488
655
  }
489
- if (mutation.chapter !== void 0 && !await hasChapterOutline(fs, mutation.root, mutation.chapter, signal)) {
490
- return `Oh Story \u963B\u6B62\u5199\u5165\u7B2C ${String(mutation.chapter)} \u7AE0\uFF1A\u672A\u627E\u5230\u5BF9\u5E94\u7684 \u5927\u7EB2/\u7EC6\u7EB2_\u7B2CXXX\u7AE0*.md\u3002\u8BF7\u5148\u5B8C\u6210\u7EC6\u7EB2\u3002`;
491
- }
492
- return void 0;
656
+ if (await hasChapterOutline(fs, mutation.root, book, mutation.chapter, signal)) return void 0;
657
+ const padded = String(mutation.chapter).padStart(3, "0");
658
+ return `Oh Story \u963B\u6B62\u5199\u5165\u7B2C ${String(mutation.chapter)} \u7AE0\uFF1A\u672A\u627E\u5230\u5BF9\u5E94\u7684 ${inBook(book, "\u5927\u7EB2")}/\u7EC6\u7EB2_\u7B2C${padded}\u7AE0*.md\u3002\u5148\u6309 story-long-write \u5355\u7AE0\u6D41\u7A0B\u8865\u5EFA\u7EC6\u7EB2\u518D\u5199\u6B63\u6587\u3002`;
493
659
  }
494
660
  async function decideStoryMutation(exec, next) {
495
661
  const fs = exec.agent?.ctx.get("fs");
@@ -500,20 +666,90 @@ async function decideStoryMutation(exec, next) {
500
666
  if (reason !== void 0) return { kind: "deny", reason };
501
667
  return next();
502
668
  }
669
+ var ANALYSIS_INPUT = /^输入-(RAW|REUSE)-(\d+)-(\d+)\.md$/u;
670
+ var ANALYSIS_INPUT_RULE = "\u53EA\u5141\u8BB8\u5199\u8C03\u7528\u65B9\u7ED9\u7684 {\u62C6\u6587\u76EE\u5F55}/_analysis_cache/\u8F93\u5165-{\u6279\u6B21ID}.md\uFF08\u6279\u6B21 ID \u5982 RAW-4-6\uFF09\uFF0C\u4E0D\u5199\u5176\u4ED6\u6587\u4EF6\u3002";
671
+ function isFileMutationCall(name2, args) {
672
+ if (name2 === "write" || name2 === "edit") return true;
673
+ return name2 === "str_replace_editor" && argumentRecord(args)?.command !== "view";
674
+ }
675
+ function displayParent(path) {
676
+ const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
677
+ return index <= 0 ? path.slice(0, index + 1) : path.slice(0, index);
678
+ }
679
+ function displayName(path) {
680
+ return path.slice(Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")) + 1);
681
+ }
682
+ async function isAnalysisDirectory(fs, directory, signal) {
683
+ return await exists(fs, directory, "chapter_index.csv", signal) || await exists(fs, directory, "_progress.md", signal);
684
+ }
685
+ async function validateAnalysisInputWrite(fs, cwd, path, signal) {
686
+ const deny = (shown, reasons2) => `Oh Story \u963B\u6B62 chapter-extractor \u5199\u5165 ${shown}\uFF1A${reasons2.join("\uFF1B")}\u3002${ANALYSIS_INPUT_RULE}`;
687
+ if (path === void 0) return deny("\uFF08\u672A\u7ED9\u51FA\u8DEF\u5F84\uFF09", ["\u5199\u5165\u6CA1\u6709\u7ED9\u51FA\u6587\u4EF6\u8DEF\u5F84"]);
688
+ let rootTarget;
689
+ let fileTarget;
690
+ try {
691
+ [rootTarget, fileTarget] = await Promise.all([
692
+ fs.resolve(cwd, signal === void 0 ? {} : { signal }),
693
+ fs.resolve(path, { cwd, ...signal === void 0 ? {} : { signal } })
694
+ ]);
695
+ } catch {
696
+ return deny(path, ["\u8DEF\u5F84\u65E0\u6CD5\u89E3\u6790"]);
697
+ }
698
+ const reasons = [];
699
+ const match = ANALYSIS_INPUT.exec(displayName(fileTarget.displayPath));
700
+ if (match === null) reasons.push("\u6587\u4EF6\u540D\u5FC5\u987B\u662F \u8F93\u5165-{RAW|REUSE}-{\u8D77\u7AE0}-{\u6B62\u7AE0}.md");
701
+ else if (Number(match[2]) < 1 || Number(match[3]) < Number(match[2])) reasons.push("\u6279\u6B21\u7AE0\u53F7\u8303\u56F4\u65E0\u6548");
702
+ const cacheDirectory = displayParent(fileTarget.displayPath);
703
+ if (displayName(cacheDirectory) !== "_analysis_cache") reasons.push("\u53EA\u80FD\u5199\u5728\u62C6\u6587\u76EE\u5F55\u7684 _analysis_cache/ \u4E0B");
704
+ else if (!await isAnalysisDirectory(fs, displayParent(cacheDirectory), signal)) {
705
+ reasons.push("\u4E0A\u7EA7\u76EE\u5F55\u4E0D\u662F\u62C6\u6587\u76EE\u5F55\uFF08\u7F3A chapter_index.csv \u4E0E _progress.md\uFF09");
706
+ }
707
+ if (!fs.contains(rootTarget, fileTarget)) reasons.push("\u8DEF\u5F84\u5728\u9879\u76EE\u76EE\u5F55\u4E4B\u5916");
708
+ return reasons.length === 0 ? void 0 : deny(path, reasons);
709
+ }
710
+ async function decideChapterExtractorWrite(exec, next) {
711
+ if (ohStoryRoleOfSession(exec.agent?.session) !== "chapter-extractor" || !isFileMutationCall(exec.name, exec.arguments)) {
712
+ return next();
713
+ }
714
+ const fs = exec.agent?.ctx.get("fs");
715
+ const cwd = exec.agent?.session.header.cwd;
716
+ const reason = fs === void 0 || cwd === void 0 ? `Oh Story \u963B\u6B62 chapter-extractor \u5199\u5165\uFF1A\u65E0\u6CD5\u786E\u8BA4\u9879\u76EE\u76EE\u5F55\u3002${ANALYSIS_INPUT_RULE}` : await validateAnalysisInputWrite(fs, cwd, mutationPath(exec.name, exec.arguments), exec.signal);
717
+ return reason === void 0 ? next() : { kind: "deny", reason };
718
+ }
719
+ function postWriteReminderText(path, options = {}) {
720
+ return [
721
+ `<oh-story-post-write>\u6B63\u6587 ${path} \u5DF2\u53D8\u66F4\u3002`,
722
+ "\u6BCF\u6B21\u5199\u5165\u6B63\u6587\u90FD\u4F1A\u51FA\u73B0\u8FD9\u6761\u63D0\u9192\uFF0C\u4E00\u7AE0\u5199\u5230\u4E00\u534A\u65F6\u4E5F\u4F1A\uFF1B\u5B83\u4E0D\u8868\u793A\u672C\u7AE0\u5DF2\u7ECF\u5B8C\u6210\uFF0C\u4E5F\u4E0D\u662F\u4F5C\u8005\u7684\u65B0\u5199\u4F5C\u8981\u6C42\uFF0C\u7EE7\u7EED\u5F53\u524D\u6B65\u9AA4\u5373\u53EF\u3002",
723
+ ...options.trackingUninitialized === true ? [
724
+ "\u672C\u4E66\u8FD8\u6CA1\u6709 \u8FFD\u8E2A/_tracking-state.json\u3002\u65B0\u4E66\u5199\u7B2C\u4E00\u7AE0\u65F6\uFF0Cdraft \u4E4B\u524D\u5148\u6309 story-long-write workflow-daily \u7684\u300C\u9996\u6B21\u521D\u59CB\u5316\u300D\u5B8C\u6574\u8BFB\u53D6 references/tracking-initialization.md\uFF0C\u6784\u9020 last_chapter=0 \u7684\u521D\u59CB\u5316\u4E8B\u52A1\u5B58\u4E66\u76EE\u5F55 .story/work/init.json\uFF0C\u8FD0\u884C scripts/tracking_commit.py init\uFF0C\u518D\u8FD0\u884C tracking_commit.py check\uFF0C\u901A\u8FC7\u540E\u5220\u6389 init.json\uFF1B",
725
+ "\u4E66\u91CC\u5728\u672C\u7AE0\u4E4B\u524D\u5DF2\u6709\u6B63\u6587\u65F6\u4E0D\u8981\u521D\u59CB\u5316\uFF0C\u505C\u4E0B\u6765\u6539\u8D70 story-import \u7684\u300C\u65E7\u8FFD\u8E2A\u9879\u76EE\u8FC1\u79FB\u300D\u3002"
726
+ ] : [],
727
+ "\u7B49\u672C\u7AE0\u5199\u5B8C\u6536\u5C3E\u65F6\u518D\u63D0\u4EA4\u8FFD\u8E2A\uFF1A\u65B0\u7AE0\u5148\u7528 story-long-write \u7684 scripts/tracking_commit.py draft \u751F\u6210\u672C\u7AE0\u8349\u7A3F\uFF0C\u586B\u597D\u540E\u6267\u884C scripts/storyctl.py chapter commit\uFF08\u4F5C\u8005\u63A5\u53D7\u5F53\u524D\u957F\u5EA6\u65F6\u7528 chapter accept-current-length\uFF09\uFF1B",
728
+ "\u4FEE\u6539\u5DF2\u63D0\u4EA4\u7684\u7AE0\u8282\u6309 workflow-revision \u63D0\u4EA4\u4E00\u6B21 mode=revision \u4E8B\u52A1\uFF1B\u5BFC\u5165\u65E2\u6709\u6B63\u6587\u65F6\u7531 story-import \u5728\u8FC1\u79FB\u540E\u7528 tracking_commit.py init \u7EDF\u4E00\u521D\u59CB\u5316\u3002",
729
+ "\u8FFD\u8E2A/_tracking-state.json \u4EE5\u53CA \u4E0A\u4E0B\u6587.md\u3001\u89D2\u8272\u72B6\u6001/\u3001\u4F0F\u7B14.md\u3001\u65F6\u95F4\u7EBF/\u3001\u9010\u7AE0\u8BB0\u5F55/ \u7B49\u6D3E\u751F Tracking \u89C6\u56FE\u53EA\u7531\u8FD9\u4E9B\u811A\u672C\u5199\u5165\uFF0C\u7EDD\u4E0D\u624B\u6539\u3002</oh-story-post-write>"
730
+ ].join("");
731
+ }
503
732
  function registerOhStoryHooks(context) {
733
+ context.on("session/event", (session, event) => {
734
+ observeOhStoryRoleDescriptor(session, event);
735
+ });
736
+ context.on("tools/pre-execute", decideChapterExtractorWrite);
504
737
  context.on("tools/pre-execute", decideStoryMutation);
505
738
  context.on("tools/post-execute", async (exec, result, next) => {
506
739
  const downstream = await next();
507
740
  if (result.isError || downstream.kind !== "accept") return downstream;
741
+ if (ohStoryRoleOfSession(exec.agent?.session) !== void 0) return downstream;
508
742
  const fs = exec.agent?.ctx.get("fs");
509
743
  const mutation = fs === void 0 ? void 0 : await storyMutation(exec, fs);
510
- if (mutation === void 0) return downstream;
744
+ const location = mutation === void 0 ? void 0 : proseLocation(mutation.path);
745
+ if (fs === void 0 || mutation === void 0 || location === void 0) return downstream;
746
+ const { root } = mutation;
747
+ const { book } = location;
748
+ if (!await isLongFormBook(fs, root, book, exec.signal)) return downstream;
749
+ const trackingUninitialized = !await hasTrackingState(fs, root, book, exec.signal) && !await hasImportSignal(fs, root, book, exec.signal);
511
750
  const reminder = createUserMessage({
512
- source: { kind: "plugin", plugin: "oh-story" },
513
- content: [{
514
- type: "text",
515
- text: `<oh-story-post-write>\u6B63\u6587 ${mutation.path} \u5DF2\u53D8\u66F4\u3002\u7EE7\u7EED\u5F53\u524D\u6B65\u9AA4\u524D\u6838\u5BF9\u5E76\u66F4\u65B0 _tracking-state.json \u53CA\u5BF9\u5E94\u6D3E\u751F Tracking \u89C6\u56FE\uFF1B\u4E0D\u8981\u628A\u8FD9\u6761\u63D0\u9192\u5F53\u4F5C\u7528\u6237\u7684\u65B0\u5199\u4F5C\u8981\u6C42\u3002</oh-story-post-write>`
516
- }]
751
+ source: { kind: "oh-story-post-write", form: "notice", summary: boundContextSummary(`\u6B63\u6587 ${mutation.path} \u5DF2\u53D8\u66F4`) },
752
+ content: [{ type: "text", text: postWriteReminderText(mutation.path, { trackingUninitialized }) }]
517
753
  });
518
754
  return {
519
755
  ...downstream,
@@ -526,22 +762,22 @@ function registerOhStoryHooks(context) {
526
762
  import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
527
763
 
528
764
  // src/reference-tool.ts
529
- import { readdir as readdir2, readFile as readFile2, realpath } from "node:fs/promises";
530
- import { isAbsolute as isAbsolute2, join as join3, relative as relative2, resolve as resolve3 } from "node:path";
765
+ import { readdir as readdir2, readFile as readFile3, realpath } from "node:fs/promises";
766
+ import { isAbsolute as isAbsolute2, join as join4, relative as relative2, resolve as resolve4 } from "node:path";
531
767
  import { defineTool } from "@deepseek-ai/dsh-tools";
532
768
  var OH_STORY_REFERENCE_TOOL_NAME = "oh_story_bundled_reference";
533
769
  async function collectMarkdown(root, directory = root) {
534
770
  const entries = await readdir2(directory, { withFileTypes: true });
535
771
  const nested = await Promise.all(entries.map(async (entry) => {
536
- const path = join3(directory, entry.name);
772
+ const path = join4(directory, entry.name);
537
773
  if (entry.isDirectory()) return collectMarkdown(root, path);
538
774
  return entry.isFile() && entry.name.endsWith(".md") ? [path] : [];
539
775
  }));
540
776
  return nested.flat();
541
777
  }
542
778
  async function bundledReferences(storySetupRoot) {
543
- const root = await realpath(resolve3(storySetupRoot));
544
- const referenceRoot = await realpath(join3(root, "references", "agent-references"));
779
+ const root = await realpath(resolve4(storySetupRoot));
780
+ const referenceRoot = await realpath(join4(root, "references", "agent-references"));
545
781
  const referenceDirectory = relative2(root, referenceRoot);
546
782
  if (referenceDirectory === "" || referenceDirectory.startsWith("..") || isAbsolute2(referenceDirectory)) {
547
783
  throw new Error("Bundled Oh Story reference directory escaped its package root.");
@@ -559,7 +795,7 @@ async function bundledReferences(storySetupRoot) {
559
795
  }));
560
796
  return values.sort((left, right) => left.name.localeCompare(right.name));
561
797
  }
562
- async function createOhStoryReferenceTool(storySetupRoot = join3(defaultBundledSkillRoot(), "story-setup")) {
798
+ async function createOhStoryReferenceTool(storySetupRoot = join4(defaultBundledSkillRoot(), "story-setup")) {
563
799
  const references = await bundledReferences(storySetupRoot);
564
800
  if (references.length === 0) throw new Error("No bundled Oh Story references were found.");
565
801
  const paths = references.map((reference) => reference.name);
@@ -595,7 +831,7 @@ ${value.content}`
595
831
  async execute(args) {
596
832
  const path = byName.get(args.reference);
597
833
  if (path === void 0) throw new Error(`Oh Story reference is not bundled: ${args.reference}`);
598
- return { reference: args.reference, content: await readFile2(path, "utf8") };
834
+ return { reference: args.reference, content: await readFile3(path, "utf8") };
599
835
  }
600
836
  });
601
837
  }
@@ -603,53 +839,12 @@ function bundledReferenceGuard(definition, tools) {
603
839
  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;
604
840
  }
605
841
 
606
- // src/role-provider.ts
607
- import { readFile as readFile3 } from "node:fs/promises";
608
- import { basename as basename2, dirname as dirname3, join as join4, resolve as resolve4 } from "node:path";
609
- import { fileURLToPath as fileURLToPath2 } from "node:url";
610
- var OH_STORY_ROLE_NAMES = [
611
- "chapter-extractor",
612
- "character-designer",
613
- "consistency-checker",
614
- "narrative-writer",
615
- "story-architect",
616
- "story-explorer",
617
- "story-researcher"
618
- ];
619
- function roleBody(source) {
620
- const frontmatter = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/u.exec(source);
621
- return source.slice(frontmatter?.[0].length ?? 0).trim();
622
- }
623
- function defaultBundledRoleRoot() {
624
- const current = dirname3(fileURLToPath2(import.meta.url));
625
- return basename2(current) === "src" ? resolve4(current, "../../knowledge/oh-story/roles") : resolve4(current, "oh-story/roles");
626
- }
627
- async function loadBundledRole(name2, roleRoot = defaultBundledRoleRoot(), execution = "tool-free") {
628
- const body = roleBody(await readFile3(join4(resolve4(roleRoot), `${name2}.md`), "utf8"));
629
- if (body.length === 0) throw new Error(`Bundled role "${name2}" is empty.`);
630
- const integration = execution === "tool-free" ? [
631
- "You are running inside an Oh Story review-required DSH collaboration. The caller supplies every permitted input in the prompt.",
632
- "Do not read or write project files, do not call tools, and do not follow legacy .claude/.codex path or deployment instructions in the upstream role text.",
633
- "Return only the output contract requested by the caller; never claim to have changed the project."
634
- ] : [
635
- "You are running as a native oh-story-dsh specialist. The current DSH workspace and visible tool set are your complete authority boundary.",
636
- "Never inspect or require legacy .claude/.opencode/.codex agent deployment files; this exact pinned Role is already active.",
637
- "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.",
638
- "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.",
639
- "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."
640
- ];
641
- return [
642
- `OH_STORY_DSH_ROLE:${name2}`,
643
- ...integration,
644
- "",
645
- body
646
- ].join("\n");
647
- }
648
-
649
842
  // src/role-tool.ts
650
843
  var OH_STORY_ROLE_TOOL_NAME = "oh_story_role";
651
844
  var roleTools = {
652
- "chapter-extractor": ["read", "glob", "grep"],
845
+ // Oh Story 0.8.0 lets the extractor write its own batch input file (never
846
+ // bash). native-hooks fences write/edit to {拆文目录}/_analysis_cache/输入-*.md.
847
+ "chapter-extractor": ["read", "glob", "grep", "write", "edit"],
653
848
  "character-designer": [OH_STORY_REFERENCE_TOOL_NAME, "read", "glob", "grep", "write", "edit"],
654
849
  "consistency-checker": [OH_STORY_REFERENCE_TOOL_NAME, "read", "glob", "grep"],
655
850
  "narrative-writer": [OH_STORY_REFERENCE_TOOL_NAME, "read", "glob", "grep", "write", "edit", "bash"],
@@ -705,7 +900,9 @@ async function createOhStoryRoleTool(subagents) {
705
900
  const runtime = subagents ?? exec.agent.ctx.get("subagents");
706
901
  if (runtime === void 0) throw new Error("oh_story_role requires the DSH subagent runtime.");
707
902
  const run = await runtime.start("spawn", {
708
- label: `oh-story:${args.role}`,
903
+ // DSH persists this label in the child's subagent/descriptor; the
904
+ // native hooks read the Role back from it before the child's first tool call.
905
+ label: ohStoryRoleLabel(args.role),
709
906
  prompt: [{ type: "text", text: args.prompt }],
710
907
  parent: exec.agent,
711
908
  persona,
@@ -713,6 +910,7 @@ async function createOhStoryRoleTool(subagents) {
713
910
  maxDepth: 1,
714
911
  signal: exec.signal
715
912
  });
913
+ if (run.localAgent !== void 0) markOhStoryRoleSession(run.localAgent.session, args.role);
716
914
  try {
717
915
  const result = await run.result;
718
916
  if (result.stopReason !== "completed") {