@oh-story/dsh 0.1.17 → 0.1.19

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 (136) hide show
  1. package/README.md +14 -14
  2. package/lib/client.js +147 -96
  3. package/lib/client.js.map +3 -3
  4. package/lib/drama/manifest.json +77 -247
  5. package/lib/drama/skills/short-drama/SKILL.md +9 -1
  6. package/lib/drama/skills/short-drama/references/creator-documents.md +51 -23
  7. package/lib/drama/skills/short-drama/references/reference-roles.md +1 -1
  8. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +442 -129
  9. package/lib/drama/skills/short-drama/scripts/project_tool.py +54 -1
  10. package/lib/drama/skills/short-drama-assets/SKILL.md +2 -6
  11. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +9 -3
  12. package/lib/drama/skills/short-drama-assets/references/era-anchors.md +3 -3
  13. package/lib/drama/skills/short-drama-assets/references/location-and-view.md +4 -0
  14. package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +2 -2
  15. package/lib/drama/skills/short-drama-develop/references/episode-design.md +19 -4
  16. package/lib/drama/skills/short-drama-develop/references/genre-cards//347/263/273/347/273/237/346/265/201.md +1 -1
  17. package/lib/drama/skills/short-drama-edit/SKILL.md +1 -1
  18. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +28 -6
  19. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +1 -1
  20. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +1 -5
  21. package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +2 -2
  22. package/lib/drama/skills/short-drama-produce/SKILL.md +2 -0
  23. package/lib/drama/skills/short-drama-review/SKILL.md +0 -4
  24. package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +1 -1
  25. package/lib/drama/skills/short-drama-storyboard/SKILL.md +9 -9
  26. package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +43 -13
  27. package/lib/drama/skills/short-drama-storyboard/references/keyframe-craft.md +5 -5
  28. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +5 -5
  29. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +27 -3
  30. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +2 -1
  31. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +6 -8
  32. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +16 -1
  33. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +23 -6
  34. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +2 -2
  35. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +8 -1
  36. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +41 -2
  37. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +1 -1
  38. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +5 -1
  39. package/lib/drama/skills/short-drama-write/SKILL.md +7 -3
  40. package/lib/drama/skills/short-drama-write/references/screenplay-format.md +1 -1
  41. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +94 -1
  42. package/lib/drama/skills/short-drama-write/scripts/screenplay_index.py +13 -4
  43. package/lib/drama/skills/short-drama-write/scripts/selftest.py +3 -77
  44. package/lib/index.js +18 -14
  45. package/lib/index.js.map +2 -2
  46. package/lib/novel-to-game/README.md +20 -34
  47. package/lib/novel-to-game/README_ZH.md +20 -34
  48. package/lib/novel-to-game/VERSION +1 -1
  49. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +2 -6
  50. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +33 -39
  51. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +2 -2
  52. package/lib/novel-to-game/manifest.json +46 -46
  53. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +1 -1
  54. package/lib/novel-to-game/skills/game-build/references/baked-sprite-workflow.md +2 -3
  55. package/lib/novel-to-game/skills/game-build/references/blender-asset-workflow.md +6 -7
  56. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +2 -2
  57. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +1 -4
  58. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +1 -3
  59. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +1 -3
  60. package/lib/novel-to-game/skills/game-concept/SKILL.md +1 -1
  61. package/lib/novel-to-game/skills/game-qa/SKILL.md +3 -4
  62. package/lib/novel-to-game/skills/game-qa/references/qa-contract.md +1 -1
  63. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +3 -2
  64. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +13 -14
  65. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +1 -2
  66. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +4 -4
  67. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +2 -3
  68. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +0 -8
  69. package/lib/oh-story/manifest.json +49 -49
  70. package/lib/oh-story/skills/story/SKILL.md +2 -2
  71. package/lib/oh-story/skills/story/VERSION +1 -1
  72. package/lib/oh-story/skills/story-deslop/SKILL.md +2 -2
  73. package/lib/oh-story/skills/story-import/SKILL.md +5 -5
  74. package/lib/oh-story/skills/story-import/references/deep-analysis.md +1 -1
  75. package/lib/oh-story/skills/story-import/references/import-report.md +1 -1
  76. package/lib/oh-story/skills/story-import/references/import-tracking.md +1 -1
  77. package/lib/oh-story/skills/story-import/references/outline-reverse.md +1 -1
  78. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +6 -1
  79. package/lib/oh-story/skills/story-long-analyze/SKILL.md +2 -2
  80. package/lib/oh-story/skills/story-long-write/SKILL.md +2 -2
  81. package/lib/oh-story/skills/story-long-write/references/agent-calls.md +3 -3
  82. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +2 -2
  83. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +6 -1
  84. package/lib/oh-story/skills/story-review/SKILL.md +8 -7
  85. package/lib/oh-story/skills/story-review/references/agent-prompts.md +7 -7
  86. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +2 -2
  87. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +6 -1
  88. package/lib/oh-story/skills/story-setup/SKILL.md +6 -6
  89. package/lib/oh-story/skills/story-short-write/SKILL.md +2 -2
  90. package/lib/oh-story/skills/story-short-write/references/agent-calls.md +3 -3
  91. package/lib/oh-story/skills/story-short-write/references/workflow-design.md +2 -2
  92. package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +1 -1
  93. package/lib/types/client/game-preview-state.d.ts +34 -0
  94. package/lib/types/client/game-preview-state.d.ts.map +1 -0
  95. package/lib/types/client/index.d.ts.map +1 -1
  96. package/lib/types/client/video-studio.d.ts +24 -0
  97. package/lib/types/client/video-studio.d.ts.map +1 -1
  98. package/lib/types/host-python.d.ts +10 -3
  99. package/lib/types/host-python.d.ts.map +1 -1
  100. package/lib/types/workspace-route.d.ts +6 -0
  101. package/lib/types/workspace-route.d.ts.map +1 -1
  102. package/package.json +21 -21
  103. package/lib/drama/skills/short-drama-assets/assets/character-look.example.jsonl +0 -3
  104. package/lib/drama/skills/short-drama-assets/assets/continuity.example.jsonl +0 -4
  105. package/lib/drama/skills/short-drama-assets/assets/decisions.example.jsonl +0 -5
  106. package/lib/drama/skills/short-drama-assets/assets/location-view.example.jsonl +0 -3
  107. package/lib/drama/skills/short-drama-assets/assets/occurrences.example.jsonl +0 -5
  108. package/lib/drama/skills/short-drama-assets/assets/prop-state.example.jsonl +0 -3
  109. package/lib/drama/skills/short-drama-assets/examples/minimal/characters.jsonl +0 -2
  110. package/lib/drama/skills/short-drama-assets/examples/minimal/looks.jsonl +0 -2
  111. package/lib/drama/skills/short-drama-assets/scripts/asset_check.py +0 -267
  112. package/lib/drama/skills/short-drama-assets/scripts/selftest.py +0 -89
  113. package/lib/drama/skills/short-drama-image-prompts/examples/minimal-image-prompt-specs.jsonl +0 -2
  114. package/lib/drama/skills/short-drama-image-prompts/scripts/image_prompt_check.py +0 -443
  115. package/lib/drama/skills/short-drama-image-prompts/scripts/selftest.py +0 -95
  116. package/lib/drama/skills/short-drama-review/assets/finding-template.jsonl +0 -2
  117. package/lib/drama/skills/short-drama-review/assets/supersession-decision.example.json +0 -42
  118. package/lib/drama/skills/short-drama-review/assets/verdict-template.json +0 -32
  119. package/lib/drama/skills/short-drama-review/examples/minimal-findings.jsonl +0 -2
  120. package/lib/drama/skills/short-drama-review/examples/minimal-verdict.json +0 -34
  121. package/lib/drama/skills/short-drama-review/scripts/review_check.py +0 -325
  122. package/lib/drama/skills/short-drama-review/scripts/selftest.py +0 -87
  123. package/lib/drama/skills/short-drama-storyboard/assets/coverage-audition.example.jsonl +0 -2
  124. package/lib/drama/skills/short-drama-storyboard/assets/revision-lineage.fragment.json +0 -12
  125. package/lib/drama/skills/short-drama-storyboard/assets/scene-visual-plan.example.jsonl +0 -2
  126. package/lib/drama/skills/short-drama-storyboard/scripts/selftest.py +0 -191
  127. package/lib/drama/skills/short-drama-storyboard/scripts/storyboard_check.py +0 -824
  128. package/lib/drama/skills/short-drama-video-prompts/assets/coverage-scope.fragment.json +0 -21
  129. package/lib/drama/skills/short-drama-video-prompts/assets/motion-terminal.example.jsonl +0 -2
  130. package/lib/drama/skills/short-drama-video-prompts/assets/performance.fragment.json +0 -38
  131. package/lib/drama/skills/short-drama-video-prompts/examples/minimal-music-specs.jsonl +0 -2
  132. package/lib/drama/skills/short-drama-video-prompts/scripts/container_check.py +0 -329
  133. package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +0 -451
  134. package/lib/drama/skills/short-drama-video-prompts/scripts/music_spec_check.py +0 -327
  135. package/lib/drama/skills/short-drama-video-prompts/scripts/selftest.py +0 -82
  136. package/lib/drama/skills/short-drama-write/scripts/voice_sheet_check.py +0 -416
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env python3
2
- """Offline self-test for screenplay indexing and voice-sheet projection."""
2
+ """Offline self-test for screenplay indexing and duration estimates."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
- import hashlib
7
6
  import json
8
7
  import sys
9
8
  import tempfile
@@ -11,7 +10,6 @@ from pathlib import Path
11
10
 
12
11
  from duration_estimate import StaleIndex, estimate, index_blocks
13
12
  from screenplay_index import build_index
14
- from voice_sheet_check import check
15
13
 
16
14
  MINIMUM_PYTHON = (3, 9)
17
15
  if sys.version_info < MINIMUM_PYTHON:
@@ -23,12 +21,6 @@ def require(condition: bool, message: str) -> None:
23
21
  raise AssertionError(message)
24
22
 
25
23
 
26
- def codes(result: dict[str, object]) -> set[str]:
27
- findings = result["findings"]
28
- assert isinstance(findings, list)
29
- return {finding["code"] for finding in findings}
30
-
31
-
32
24
  def measure_text(body: str, project: dict[str, object] | None = None) -> dict[str, object]:
33
25
  """Index a screenplay the documented way, then estimate from that index.
34
26
 
@@ -85,7 +77,7 @@ def test_duration_uses_the_projects_own_rates() -> None:
85
77
 
86
78
 
87
79
  def test_production_tags_are_not_performed_time() -> None:
88
- report = measure_text("[画面文字] 账号后台:粉丝 2\n\n他把门关上。\n")
80
+ report = measure_text("[画面文字] 店门告示:暂停营业\n\n他把门关上。\n")
89
81
  require(report["counts"]["production_tag_lines"] == 1, "tag counted separately")
90
82
  require(report["counts"]["action_paragraphs"] == 1, "tag is not an action")
91
83
 
@@ -146,72 +138,6 @@ def main() -> int:
146
138
  speakers=["陈予安"],
147
139
  )
148
140
  require(summary["review_status"] == "clean", "valid screenplay index")
149
- index_bytes = index_path.read_bytes()
150
- records = [
151
- json.loads(line) for line in index_bytes.decode("utf-8").splitlines()
152
- ]
153
- dialogue = next(record for record in records if record.get("kind") == "dialogue")
154
-
155
- header = {
156
- "record_type": "sources",
157
- "schema_version": "1.0.0",
158
- "sources": {
159
- "screenplay-index": {
160
- "owner": "short-drama-write",
161
- "artifact": "剧集/EP001/screenplay-index.jsonl",
162
- "hash": hashlib.sha256(index_bytes).hexdigest(),
163
- }
164
- },
165
- }
166
- line = {
167
- "line_id": "LINE-001",
168
- "channel": "sync",
169
- "speaker_display": "陈予安",
170
- "line_text": "我回来了。",
171
- "source_ref": {"src": "screenplay-index", "record_id": dialogue["block_id"]},
172
- }
173
- sheet = [header, line]
174
- result = check(sheet, records, screenplay.read_bytes())
175
- require(result["status"] == "pass", "faithful voice sheet")
176
- require(result["lines"] == 1, "the sources header is not a voice line")
177
-
178
- # A sheet written before the compact form still resolves; real projects
179
- # hold both spellings and neither is rewritten.
180
- expanded = [
181
- dict(
182
- line,
183
- source_ref={
184
- "owner": "short-drama-write",
185
- "artifact": "剧集/EP001/screenplay-index.jsonl",
186
- "hash": hashlib.sha256(index_bytes).hexdigest(),
187
- "record_id": dialogue["block_id"],
188
- },
189
- )
190
- ]
191
- require(
192
- check(expanded, records, screenplay.read_bytes())["status"] == "pass",
193
- "expanded voice sheet",
194
- )
195
-
196
- undeclared = [header, dict(line, source_ref={"src": "screenplay", "record_id": "X"})]
197
- require(
198
- "VOICE_SOURCE_REF_UNDECLARED"
199
- in codes(check(undeclared, records, screenplay.read_bytes())),
200
- "src without a sources entry was not detected",
201
- )
202
-
203
- unbound = [header, dict(line, source_ref={"record_id": dialogue["block_id"]})]
204
- require(
205
- "VOICE_SOURCE_REF_MISSING"
206
- in codes(check(unbound, records, screenplay.read_bytes())),
207
- "source_ref naming no upstream snapshot was not detected",
208
- )
209
-
210
- changed = [header, dict(line, line_text="我走了。")]
211
- require(
212
- check(changed, records, screenplay.read_bytes())["status"] == "fail",
213
- "changed voice line was not detected",
214
- )
215
141
 
216
142
  test_duration_reports_counts_without_rates()
217
143
  test_duration_uses_the_projects_own_rates()
@@ -221,7 +147,7 @@ def main() -> int:
221
147
  test_comments_are_not_production_content()
222
148
  test_an_index_from_other_bytes_is_refused()
223
149
 
224
- print("14 self-tests passed")
150
+ print("8 self-tests passed")
225
151
  return 0
226
152
 
227
153
 
package/lib/index.js CHANGED
@@ -129,7 +129,7 @@ var DSH_SKILL_BRIDGE = [
129
129
  "Never start another Agent runtime, session transport, Dashboard, SSE stream, polling loop, or model configuration.",
130
130
  "All seven upstream Oh Story specialist Roles are bundled. Invoke one with oh_story_role and a self-contained prompt.",
131
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.",
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, not invoke_subagent, and not an Agent tool \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.",
133
133
  "Use only DSH-visible tools. DSH sandbox and permission policy remain authoritative.",
134
134
  // Upstream deploys these 「与作者协作」 rules only in its CLAUDE.md/AGENTS.md
135
135
  // templates, which DSH does not install.
@@ -166,7 +166,7 @@ var DSH_DRAMA_BRIDGE = [
166
166
  "The \u77ED\u5267 tab is the creator workspace. Never start dashboard_server.py, another web server, Dashboard, Agent runtime, session transport, or model configuration.",
167
167
  "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.",
168
168
  "\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 readable media exists and records what reaches the cut \u2014 which footage or accepted static keyframes are kept or left unused and why, each cut's in/out timing, its subtitles, \u753B\u9762\u6587\u5B57 overlays, \u97F3\u6548 cues, \u914D\u97F3 and \u73AF\u5883\u58F0, post-processing such as shot-match correction or an external mix, and the delivery spec including optional \u9897\u7C92 grain and \u63A5\u955C\u5339\u914D. 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.",
169
- "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.",
169
+ "For a v0.6 project, never create a parallel JSON/JSONL lifecycle truth, indexes, fingerprints, coverage tables, or QA records; Drama 0.9.1 no longer bundles the legacy JSON checkers, and creator_markdown_check.py is the structural check.",
170
170
  "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.",
171
171
  "Use only tools visible in the current DSH preset and preserve the upstream project ownership, freshness, review, and explicit production-confirmation contracts.",
172
172
  "Use oh_story_production only for semantic production-view intents (open/focus, explicit shot order, or tracking a job that this Agent is actually executing). Cosmetic canvas layout remains creator-controlled. The tool changes only the Session projection: it does not edit creator documents, generate media, or authorize production.",
@@ -489,6 +489,8 @@ function createVideoRecapSkillProvider(skillRoot = defaultVideoRecapSkillRoot())
489
489
  import { execFile } from "node:child_process";
490
490
  import { promisify } from "node:util";
491
491
  var execFileAsync = promisify(execFile);
492
+ var DRAMA_PYTHON = [3, 9];
493
+ var VIDEO_PYTHON = [3, 10];
492
494
  async function commandOutput(command, args) {
493
495
  try {
494
496
  const { stdout, stderr } = await execFileAsync(command, [...args], { encoding: "utf8", timeout: 5e3, maxBuffer: 4 * 1024 * 1024 });
@@ -497,21 +499,22 @@ async function commandOutput(command, args) {
497
499
  return void 0;
498
500
  }
499
501
  }
500
- function pythonVersion(value) {
502
+ function pythonVersion(value, floor) {
503
+ const minimum = `${String(floor[0])}.${String(floor[1])}`;
501
504
  const match = /Python\s+(\d+)\.(\d+)(?:\.(\d+))?/u.exec(value ?? "");
502
- if (match === null) return { ok: false };
505
+ if (match === null) return { ok: false, minimum };
503
506
  const major = Number(match[1]);
504
507
  const minor = Number(match[2]);
505
- return { ok: major > 3 || major === 3 && minor >= 10, version: match[0].replace(/^Python\s+/u, "") };
508
+ return { ok: major > floor[0] || major === floor[0] && minor >= floor[1], version: match[0].replace(/^Python\s+/u, ""), minimum };
506
509
  }
507
- async function hostPython(probe = commandOutput) {
510
+ async function hostPython(floor, probe = commandOutput) {
508
511
  let fallback;
509
512
  for (const command of ["python3", "python"]) {
510
- const parsed = pythonVersion(await probe(command, ["--version"]));
513
+ const parsed = pythonVersion(await probe(command, ["--version"]), floor);
511
514
  if (parsed.ok) return { command, probe: parsed };
512
515
  if (parsed.version !== void 0) fallback ??= { command, probe: parsed };
513
516
  }
514
- return fallback ?? { command: "python3", probe: { ok: false } };
517
+ return fallback ?? { command: "python3", probe: { ok: false, minimum: pythonVersion(void 0, floor).minimum } };
515
518
  }
516
519
 
517
520
  // src/native-hooks.ts
@@ -1482,7 +1485,7 @@ async function dramaPreflight() {
1482
1485
  if (dramaPreflightCache !== void 0 && dramaPreflightCache.expires > Date.now()) return dramaPreflightCache.value;
1483
1486
  dramaPreflightInFlight ??= (async () => {
1484
1487
  try {
1485
- const python = await hostPython();
1488
+ const python = await hostPython(DRAMA_PYTHON);
1486
1489
  const adapterConfig = await ensureDramaAdapterConfig(defaultDramaSkillRoot(), { python: python.command });
1487
1490
  const value = { python: python.probe, adapterConfig, adapters: dramaAdapterStatuses() };
1488
1491
  dramaPreflightCache = { expires: Date.now() + 3e4, value };
@@ -1495,7 +1498,7 @@ async function dramaPreflight() {
1495
1498
  }
1496
1499
  async function videoPreflight() {
1497
1500
  if (videoPreflightCache !== void 0 && videoPreflightCache.expires > Date.now()) return videoPreflightCache.value;
1498
- const python = (await hostPython()).probe;
1501
+ const python = (await hostPython(VIDEO_PYTHON)).probe;
1499
1502
  const [ffmpegOutput, ffmpegFilters, ffprobeOutput] = await Promise.all([
1500
1503
  commandOutput("ffmpeg", ["-version"]),
1501
1504
  commandOutput("ffmpeg", ["-hide_banner", "-filters"]),
@@ -1815,9 +1818,10 @@ async function previewDigest(realm, projectRoot) {
1815
1818
  await visit(app, "");
1816
1819
  return { ready, version: createHash2("sha256").update(entries.sort().join("\n")).digest("hex").slice(0, 16) };
1817
1820
  }
1818
- function headingTitle(content, fallback) {
1821
+ function gameProjectTitle(content, fallback) {
1819
1822
  const heading = content?.split(/\r?\n/u).find((line) => /^#\s+/u.test(line));
1820
- return heading?.replace(/^#\s+/u, "").replace(/^PRODUCT_BRIEF\s*[·・:]?\s*/iu, "").trim() || fallback;
1823
+ const title = heading?.replace(/^#\s+/u, "").replace(/^(?:PRODUCT[_ ]BRIEF|产品简报)/iu, "").replace(/^[\s·・::|—–-]+/u, "").trim().replace(/^《([^《》]+)》$/u, "$1").trim();
1824
+ return title || fallback;
1821
1825
  }
1822
1826
  async function workspaceGameProjects(realm, files, sessionId, maxBytes) {
1823
1827
  const roots = [...new Set(files.flatMap((file) => {
@@ -1843,7 +1847,7 @@ async function workspaceGameProjects(realm, files, sessionId, maxBytes) {
1843
1847
  return {
1844
1848
  id: `workspace:${id}`,
1845
1849
  root,
1846
- title: headingTitle(brief, id),
1850
+ title: gameProjectTitle(brief, id),
1847
1851
  source: "workspace",
1848
1852
  previewReady: preview.ready,
1849
1853
  previewUrl: preview.ready ? `/oh-story/game-preview/workspace/${token(sessionId)}/${token(root)}/index.html` : void 0,
@@ -2142,7 +2146,7 @@ async function apply(context, config = {}) {
2142
2146
  registerOhStoryProductionTool(context);
2143
2147
  await registerOhStoryRoleTool(context);
2144
2148
  registerWorkspaceRoute(context, { maxBytes: config.editorMaxBytes ?? 2097152, trustedHosts });
2145
- await ensureDramaAdapterConfig(defaultDramaSkillRoot(), { python: (await hostPython()).command });
2149
+ await ensureDramaAdapterConfig(defaultDramaSkillRoot(), { python: (await hostPython(DRAMA_PYTHON)).command });
2146
2150
  }
2147
2151
  var index_default = { name, inject, Config, apply };
2148
2152
  export {