@oh-story/dsh 0.1.6 → 0.1.8
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.
- package/README.md +29 -12
- package/lib/client.js +356 -89
- package/lib/client.js.map +4 -4
- package/lib/drama/manifest.json +34 -34
- package/lib/drama/skills/short-drama/SKILL.md +4 -1
- package/lib/drama/skills/short-drama/references/contract-and-ownership.md +2 -0
- package/lib/drama/skills/short-drama/references/creator-documents.md +30 -4
- package/lib/drama/skills/short-drama/references/creator-workflow.md +8 -2
- package/lib/drama/skills/short-drama/references/reference-roles.md +16 -2
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +328 -48
- package/lib/drama/skills/short-drama-produce/SKILL.md +4 -1
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +34 -2
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +27 -5
- package/lib/drama/skills/short-drama-storyboard/references/review-and-fixtures.md +2 -1
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +2 -2
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +11 -2
- package/lib/drama/skills/short-drama-video-prompts/assets/video-prompts.md +6 -1
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +19 -3
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +8 -2
- package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +1 -0
- package/lib/index.js +194 -62
- package/lib/index.js.map +4 -4
- package/lib/types/client/drama-production-view.d.ts +1 -0
- package/lib/types/client/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/drama-production.d.ts +3 -0
- package/lib/types/client/drama-production.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/video-studio.d.ts +2 -1
- package/lib/types/client/video-studio.d.ts.map +1 -1
- package/lib/types/client/workbench-presence.d.ts +29 -0
- package/lib/types/client/workbench-presence.d.ts.map +1 -0
- package/lib/types/drama-adapters.d.ts +65 -0
- package/lib/types/drama-adapters.d.ts.map +1 -0
- package/lib/types/host-python.d.ts +19 -0
- package/lib/types/host-python.d.ts.map +1 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts +1 -1
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/types/workspace-route.d.ts.map +1 -1
- package/package.json +41 -41
package/lib/index.js
CHANGED
|
@@ -3,7 +3,107 @@ import z from "@deepseek-ai/schemastery";
|
|
|
3
3
|
|
|
4
4
|
// src/skill-provider.ts
|
|
5
5
|
import { readdir, readFile } from "node:fs/promises";
|
|
6
|
-
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
6
|
+
import { basename, dirname as dirname2, isAbsolute, join as join2, relative, resolve as resolve2 } from "node:path";
|
|
7
|
+
|
|
8
|
+
// src/drama-adapters.ts
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { access, lstat, mkdir, rename, writeFile } from "node:fs/promises";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { dirname, join, resolve } from "node:path";
|
|
13
|
+
var DRAMA_ADAPTER_CONFIG_ENV = "OH_STORY_DRAMA_ADAPTER_CONFIG";
|
|
14
|
+
var PROVIDER_SCRIPT = "short-drama-produce/scripts/provider_adapters.py";
|
|
15
|
+
var DRAMA_ADAPTERS = [
|
|
16
|
+
{
|
|
17
|
+
name: "gpt-image-2",
|
|
18
|
+
label: "GPT Image 2",
|
|
19
|
+
modality: "image",
|
|
20
|
+
requiredEnv: ["OPENAI_API_KEY"],
|
|
21
|
+
optionalEnv: ["OPENAI_BASE_URL"],
|
|
22
|
+
timeoutSeconds: 600,
|
|
23
|
+
reference: "short-drama-produce/references/providers/gpt-image-2.md"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "seedance",
|
|
27
|
+
label: "Seedance",
|
|
28
|
+
modality: "video",
|
|
29
|
+
requiredEnv: ["ARK_API_KEY", "SEEDANCE_MODEL"],
|
|
30
|
+
optionalEnv: ["SEEDANCE_BASE_URL", "SEEDANCE_ALLOWED_RATIOS", "SEEDANCE_MIN_DURATION", "SEEDANCE_MAX_DURATION"],
|
|
31
|
+
timeoutSeconds: 3600,
|
|
32
|
+
reference: "short-drama-produce/references/providers/seedance.md"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "minimax-h3",
|
|
36
|
+
label: "MiniMax H3",
|
|
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"],
|
|
40
|
+
timeoutSeconds: 3600,
|
|
41
|
+
reference: "short-drama-produce/references/providers/minimax-h3-video.md"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "minimax-music",
|
|
45
|
+
label: "MiniMax Music",
|
|
46
|
+
modality: "music",
|
|
47
|
+
requiredEnv: ["MINIMAX_API_KEY"],
|
|
48
|
+
optionalEnv: ["MINIMAX_BASE_URL"],
|
|
49
|
+
timeoutSeconds: 600,
|
|
50
|
+
reference: "short-drama-produce/references/providers/minimax-music.md"
|
|
51
|
+
}
|
|
52
|
+
];
|
|
53
|
+
function generatedDirectory(temporaryRoot) {
|
|
54
|
+
const uid = process.getuid?.();
|
|
55
|
+
return join(temporaryRoot, uid === void 0 ? "oh-story-dsh" : `oh-story-dsh-${String(uid)}`);
|
|
56
|
+
}
|
|
57
|
+
function dramaAdapterConfigPath(skillRoot, env = process.env, temporaryRoot = tmpdir()) {
|
|
58
|
+
const custom = env[DRAMA_ADAPTER_CONFIG_ENV] ?? "";
|
|
59
|
+
if (custom !== "") return { path: resolve(custom), generated: false };
|
|
60
|
+
const key = createHash("sha256").update(resolve(skillRoot)).digest("hex").slice(0, 12);
|
|
61
|
+
return { path: join(generatedDirectory(temporaryRoot), `drama-adapters-${key}.json`), generated: true };
|
|
62
|
+
}
|
|
63
|
+
async function privateDirectory(path) {
|
|
64
|
+
await mkdir(path, { recursive: true, mode: 448 });
|
|
65
|
+
const info = await lstat(path);
|
|
66
|
+
if (!info.isDirectory()) return false;
|
|
67
|
+
const uid = process.getuid?.();
|
|
68
|
+
return uid === void 0 || info.uid === uid;
|
|
69
|
+
}
|
|
70
|
+
function dramaAdapterConfigDocument(skillRoot, python = "python3") {
|
|
71
|
+
const script = resolve(skillRoot, PROVIDER_SCRIPT);
|
|
72
|
+
const adapters = {};
|
|
73
|
+
for (const adapter of DRAMA_ADAPTERS) {
|
|
74
|
+
adapters[adapter.name] = { command: [python, script, adapter.name], timeout_seconds: adapter.timeoutSeconds };
|
|
75
|
+
}
|
|
76
|
+
return { adapters };
|
|
77
|
+
}
|
|
78
|
+
async function ensureDramaAdapterConfig(skillRoot, options = {}) {
|
|
79
|
+
const location = dramaAdapterConfigPath(skillRoot, options.env ?? process.env, options.temporaryRoot ?? tmpdir());
|
|
80
|
+
if (!location.generated) {
|
|
81
|
+
return { ...location, ok: await access(location.path).then(() => true, () => false) };
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
if (!await privateDirectory(dirname(location.path))) return { ...location, ok: false };
|
|
85
|
+
const existing = await lstat(location.path).catch(() => void 0);
|
|
86
|
+
if (existing !== void 0 && !existing.isFile()) return { ...location, ok: false };
|
|
87
|
+
const staging = `${location.path}.${String(process.pid)}.tmp`;
|
|
88
|
+
await writeFile(staging, `${JSON.stringify(dramaAdapterConfigDocument(skillRoot, options.python ?? "python3"), null, 2)}
|
|
89
|
+
`, { encoding: "utf8", mode: 384 });
|
|
90
|
+
await rename(staging, location.path);
|
|
91
|
+
return { ...location, ok: true };
|
|
92
|
+
} catch {
|
|
93
|
+
return { ...location, ok: false };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function dramaAdapterStatuses(env = process.env) {
|
|
97
|
+
return DRAMA_ADAPTERS.map((adapter) => {
|
|
98
|
+
const missing = adapter.requiredEnv.filter((name2) => (env[name2] ?? "") === "");
|
|
99
|
+
return { name: adapter.name, label: adapter.label, modality: adapter.modality, configured: missing.length === 0, missing };
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function dramaAdapterSummary() {
|
|
103
|
+
return DRAMA_ADAPTERS.map((adapter) => `${adapter.name} (${adapter.modality}: ${adapter.requiredEnv.join(" + ")})`).join(", ");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/skill-provider.ts
|
|
7
107
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
108
|
import {
|
|
9
109
|
BUNDLED_SKILL_RANK
|
|
@@ -50,7 +150,7 @@ var DSH_DRAMA_BRIDGE = [
|
|
|
50
150
|
].join("\n");
|
|
51
151
|
var DSH_DRAMA_OVERRIDES = {
|
|
52
152
|
"short-drama": "A dashboard request means focus or use the native \u77ED\u5267 tab in this DSH Session. Do not run the bundled standalone Dashboard script. New projects follow only the v0.6 creator-first contract and create only documents required by the current request.",
|
|
53
|
-
"short-drama-produce":
|
|
153
|
+
"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.`,
|
|
54
154
|
"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."
|
|
55
155
|
};
|
|
56
156
|
var DSH_GAME_BRIDGE = [
|
|
@@ -196,25 +296,25 @@ function dshSkillContent(name2, content) {
|
|
|
196
296
|
${native ?? content}`;
|
|
197
297
|
}
|
|
198
298
|
function defaultBundledSkillRoot() {
|
|
199
|
-
const current =
|
|
200
|
-
return basename(current) === "src" ?
|
|
299
|
+
const current = dirname2(fileURLToPath(import.meta.url));
|
|
300
|
+
return basename(current) === "src" ? resolve2(current, "../../knowledge/oh-story/skills") : resolve2(current, "oh-story/skills");
|
|
201
301
|
}
|
|
202
302
|
function defaultDramaSkillRoot() {
|
|
203
|
-
const current =
|
|
204
|
-
return basename(current) === "src" ?
|
|
303
|
+
const current = dirname2(fileURLToPath(import.meta.url));
|
|
304
|
+
return basename(current) === "src" ? resolve2(current, "../../knowledge/drama/skills") : resolve2(current, "drama/skills");
|
|
205
305
|
}
|
|
206
306
|
function defaultNovelToGameSkillRoot() {
|
|
207
|
-
const current =
|
|
208
|
-
return basename(current) === "src" ?
|
|
307
|
+
const current = dirname2(fileURLToPath(import.meta.url));
|
|
308
|
+
return basename(current) === "src" ? resolve2(current, "../../knowledge/novel-to-game/skills") : resolve2(current, "novel-to-game/skills");
|
|
209
309
|
}
|
|
210
310
|
function createBundledSkillProvider(providerName, skillRoot, content) {
|
|
211
|
-
const root =
|
|
311
|
+
const root = resolve2(skillRoot);
|
|
212
312
|
return {
|
|
213
313
|
name: providerName,
|
|
214
314
|
async list() {
|
|
215
315
|
const directories = (await readdir(root, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
216
316
|
return Promise.all(directories.map(async (directory) => {
|
|
217
|
-
const path =
|
|
317
|
+
const path = join2(root, directory, "SKILL.md");
|
|
218
318
|
const parsed = parseBundledSkill(await readFile(path, "utf8"));
|
|
219
319
|
if (parsed.name !== directory) throw new Error(`Bundled skill directory "${directory}" does not match name "${parsed.name}".`);
|
|
220
320
|
return {
|
|
@@ -223,7 +323,7 @@ function createBundledSkillProvider(providerName, skillRoot, content) {
|
|
|
223
323
|
invocation: { modelInvocable: true, userInvocable: parsed.userInvocable },
|
|
224
324
|
provider: providerName,
|
|
225
325
|
source: "bundled",
|
|
226
|
-
resourceBase: { kind: "directory", path:
|
|
326
|
+
resourceBase: { kind: "directory", path: join2(root, directory) },
|
|
227
327
|
rank: BUNDLED_SKILL_RANK,
|
|
228
328
|
locator: pathToFileURL(path),
|
|
229
329
|
path
|
|
@@ -232,7 +332,7 @@ function createBundledSkillProvider(providerName, skillRoot, content) {
|
|
|
232
332
|
},
|
|
233
333
|
async get(candidate) {
|
|
234
334
|
if (candidate.provider !== providerName || typeof candidate.path !== "string") return void 0;
|
|
235
|
-
const path =
|
|
335
|
+
const path = resolve2(candidate.path);
|
|
236
336
|
const relativePath = relative(root, path);
|
|
237
337
|
if (relativePath === "" || relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
238
338
|
throw new Error("Bundled skill locator escaped the packaged skill root.");
|
|
@@ -245,7 +345,7 @@ function createBundledSkillProvider(providerName, skillRoot, content) {
|
|
|
245
345
|
invocation: { modelInvocable: true, userInvocable: parsed.userInvocable },
|
|
246
346
|
provider: providerName,
|
|
247
347
|
source: "bundled",
|
|
248
|
-
resourceBase: { kind: "directory", path:
|
|
348
|
+
resourceBase: { kind: "directory", path: join2(root, parsed.name) },
|
|
249
349
|
path,
|
|
250
350
|
content: content(parsed.name, parsed.content)
|
|
251
351
|
};
|
|
@@ -255,15 +355,16 @@ function createBundledSkillProvider(providerName, skillRoot, content) {
|
|
|
255
355
|
function createOhStorySkillProvider(skillRoot = defaultBundledSkillRoot()) {
|
|
256
356
|
return createBundledSkillProvider(STORY_PROVIDER_NAME, skillRoot, dshSkillContent);
|
|
257
357
|
}
|
|
258
|
-
function dshDramaSkillContent(name2, content) {
|
|
259
|
-
const
|
|
358
|
+
function dshDramaSkillContent(name2, content, skillRoot = defaultDramaSkillRoot()) {
|
|
359
|
+
const entry = DSH_DRAMA_OVERRIDES[name2];
|
|
360
|
+
const override = typeof entry === "function" ? entry(skillRoot) : entry;
|
|
260
361
|
return `${DSH_DRAMA_BRIDGE}${override === void 0 ? "" : `
|
|
261
362
|
<skill-specific-dsh-override>${override}</skill-specific-dsh-override>`}
|
|
262
363
|
|
|
263
364
|
${content}`;
|
|
264
365
|
}
|
|
265
366
|
function createDramaSkillProvider(skillRoot = defaultDramaSkillRoot()) {
|
|
266
|
-
return createBundledSkillProvider(DRAMA_PROVIDER_NAME, skillRoot, dshDramaSkillContent);
|
|
367
|
+
return createBundledSkillProvider(DRAMA_PROVIDER_NAME, skillRoot, (name2, content) => dshDramaSkillContent(name2, content, skillRoot));
|
|
267
368
|
}
|
|
268
369
|
function dshNovelToGameSkillContent(_name, content) {
|
|
269
370
|
return `${DSH_GAME_BRIDGE}
|
|
@@ -274,8 +375,8 @@ function createNovelToGameSkillProvider(skillRoot = defaultNovelToGameSkillRoot(
|
|
|
274
375
|
return createBundledSkillProvider(GAME_PROVIDER_NAME, skillRoot, dshNovelToGameSkillContent);
|
|
275
376
|
}
|
|
276
377
|
function defaultVideoRecapSkillRoot() {
|
|
277
|
-
const current =
|
|
278
|
-
return basename(current) === "src" ?
|
|
378
|
+
const current = dirname2(fileURLToPath(import.meta.url));
|
|
379
|
+
return basename(current) === "src" ? resolve2(current, "../../knowledge/video-recap/skills") : resolve2(current, "video-recap/skills");
|
|
279
380
|
}
|
|
280
381
|
function dshVideoRecapSkillContent(_name, content) {
|
|
281
382
|
return `${DSH_VIDEO_BRIDGE}
|
|
@@ -286,6 +387,35 @@ function createVideoRecapSkillProvider(skillRoot = defaultVideoRecapSkillRoot())
|
|
|
286
387
|
return createBundledSkillProvider(VIDEO_PROVIDER_NAME, skillRoot, dshVideoRecapSkillContent);
|
|
287
388
|
}
|
|
288
389
|
|
|
390
|
+
// src/host-python.ts
|
|
391
|
+
import { execFile } from "node:child_process";
|
|
392
|
+
import { promisify } from "node:util";
|
|
393
|
+
var execFileAsync = promisify(execFile);
|
|
394
|
+
async function commandOutput(command, args) {
|
|
395
|
+
try {
|
|
396
|
+
const { stdout, stderr } = await execFileAsync(command, [...args], { encoding: "utf8", timeout: 5e3, maxBuffer: 4 * 1024 * 1024 });
|
|
397
|
+
return `${stdout}${stderr}`.trim();
|
|
398
|
+
} catch {
|
|
399
|
+
return void 0;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function pythonVersion(value) {
|
|
403
|
+
const match = /Python\s+(\d+)\.(\d+)(?:\.(\d+))?/u.exec(value ?? "");
|
|
404
|
+
if (match === null) return { ok: false };
|
|
405
|
+
const major = Number(match[1]);
|
|
406
|
+
const minor = Number(match[2]);
|
|
407
|
+
return { ok: major > 3 || major === 3 && minor >= 10, version: match[0].replace(/^Python\s+/u, "") };
|
|
408
|
+
}
|
|
409
|
+
async function hostPython(probe = commandOutput) {
|
|
410
|
+
let fallback;
|
|
411
|
+
for (const command of ["python3", "python"]) {
|
|
412
|
+
const parsed = pythonVersion(await probe(command, ["--version"]));
|
|
413
|
+
if (parsed.ok) return { command, probe: parsed };
|
|
414
|
+
if (parsed.version !== void 0) fallback ??= { command, probe: parsed };
|
|
415
|
+
}
|
|
416
|
+
return fallback ?? { command: "python3", probe: { ok: false } };
|
|
417
|
+
}
|
|
418
|
+
|
|
289
419
|
// src/native-hooks.ts
|
|
290
420
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
291
421
|
var MUTATION_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "str_replace_editor"]);
|
|
@@ -395,21 +525,21 @@ import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
|
|
|
395
525
|
|
|
396
526
|
// src/reference-tool.ts
|
|
397
527
|
import { readdir as readdir2, readFile as readFile2, realpath } from "node:fs/promises";
|
|
398
|
-
import { isAbsolute as isAbsolute2, join as
|
|
528
|
+
import { isAbsolute as isAbsolute2, join as join3, relative as relative2, resolve as resolve3 } from "node:path";
|
|
399
529
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
400
530
|
var OH_STORY_REFERENCE_TOOL_NAME = "oh_story_bundled_reference";
|
|
401
531
|
async function collectMarkdown(root, directory = root) {
|
|
402
532
|
const entries = await readdir2(directory, { withFileTypes: true });
|
|
403
533
|
const nested = await Promise.all(entries.map(async (entry) => {
|
|
404
|
-
const path =
|
|
534
|
+
const path = join3(directory, entry.name);
|
|
405
535
|
if (entry.isDirectory()) return collectMarkdown(root, path);
|
|
406
536
|
return entry.isFile() && entry.name.endsWith(".md") ? [path] : [];
|
|
407
537
|
}));
|
|
408
538
|
return nested.flat();
|
|
409
539
|
}
|
|
410
540
|
async function bundledReferences(storySetupRoot) {
|
|
411
|
-
const root = await realpath(
|
|
412
|
-
const referenceRoot = await realpath(
|
|
541
|
+
const root = await realpath(resolve3(storySetupRoot));
|
|
542
|
+
const referenceRoot = await realpath(join3(root, "references", "agent-references"));
|
|
413
543
|
const referenceDirectory = relative2(root, referenceRoot);
|
|
414
544
|
if (referenceDirectory === "" || referenceDirectory.startsWith("..") || isAbsolute2(referenceDirectory)) {
|
|
415
545
|
throw new Error("Bundled Oh Story reference directory escaped its package root.");
|
|
@@ -427,7 +557,7 @@ async function bundledReferences(storySetupRoot) {
|
|
|
427
557
|
}));
|
|
428
558
|
return values.sort((left, right) => left.name.localeCompare(right.name));
|
|
429
559
|
}
|
|
430
|
-
async function createOhStoryReferenceTool(storySetupRoot =
|
|
560
|
+
async function createOhStoryReferenceTool(storySetupRoot = join3(defaultBundledSkillRoot(), "story-setup")) {
|
|
431
561
|
const references = await bundledReferences(storySetupRoot);
|
|
432
562
|
if (references.length === 0) throw new Error("No bundled Oh Story references were found.");
|
|
433
563
|
const paths = references.map((reference) => reference.name);
|
|
@@ -473,7 +603,7 @@ function bundledReferenceGuard(definition, tools) {
|
|
|
473
603
|
|
|
474
604
|
// src/role-provider.ts
|
|
475
605
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
476
|
-
import { basename as basename2, dirname as
|
|
606
|
+
import { basename as basename2, dirname as dirname3, join as join4, resolve as resolve4 } from "node:path";
|
|
477
607
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
478
608
|
var OH_STORY_ROLE_NAMES = [
|
|
479
609
|
"chapter-extractor",
|
|
@@ -489,11 +619,11 @@ function roleBody(source) {
|
|
|
489
619
|
return source.slice(frontmatter?.[0].length ?? 0).trim();
|
|
490
620
|
}
|
|
491
621
|
function defaultBundledRoleRoot() {
|
|
492
|
-
const current =
|
|
493
|
-
return basename2(current) === "src" ?
|
|
622
|
+
const current = dirname3(fileURLToPath2(import.meta.url));
|
|
623
|
+
return basename2(current) === "src" ? resolve4(current, "../../knowledge/oh-story/roles") : resolve4(current, "oh-story/roles");
|
|
494
624
|
}
|
|
495
625
|
async function loadBundledRole(name2, roleRoot = defaultBundledRoleRoot(), execution = "tool-free") {
|
|
496
|
-
const body = roleBody(await readFile3(
|
|
626
|
+
const body = roleBody(await readFile3(join4(resolve4(roleRoot), `${name2}.md`), "utf8"));
|
|
497
627
|
if (body.length === 0) throw new Error(`Bundled role "${name2}" is empty.`);
|
|
498
628
|
const integration = execution === "tool-free" ? [
|
|
499
629
|
"You are running inside an Oh Story review-required DSH collaboration. The caller supplies every permitted input in the prompt.",
|
|
@@ -723,12 +853,10 @@ function registerOhStoryProductionTool(context) {
|
|
|
723
853
|
}
|
|
724
854
|
|
|
725
855
|
// src/workspace-route.ts
|
|
726
|
-
import { createHash } from "node:crypto";
|
|
856
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
727
857
|
import { createReadStream } from "node:fs";
|
|
728
858
|
import { readFile as readNodeFile, realpath as nodeRealpath, stat as nodeStat } from "node:fs/promises";
|
|
729
|
-
import { extname as extname2, isAbsolute as isAbsolute3, relative as relative3, resolve as
|
|
730
|
-
import { execFile } from "node:child_process";
|
|
731
|
-
import { promisify } from "node:util";
|
|
859
|
+
import { extname as extname2, isAbsolute as isAbsolute3, relative as relative3, resolve as resolve5 } from "node:path";
|
|
732
860
|
import { FsError } from "@deepseek-ai/dsh-fs";
|
|
733
861
|
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
734
862
|
|
|
@@ -991,8 +1119,8 @@ var FILE_LIMIT = 1e3;
|
|
|
991
1119
|
var PREVIEW_FILE_LIMIT = 32 * 1024 * 1024;
|
|
992
1120
|
var BUNDLED_GAME_EXAMPLE = "jin-ping-mei";
|
|
993
1121
|
var workspaceVerificationTracker = new WorkspaceVerificationTracker();
|
|
994
|
-
var execFileAsync = promisify(execFile);
|
|
995
1122
|
var videoPreflightCache;
|
|
1123
|
+
var dramaPreflightCache;
|
|
996
1124
|
var WorkspaceHttpError = class extends Error {
|
|
997
1125
|
constructor(status, message) {
|
|
998
1126
|
super(message);
|
|
@@ -1000,34 +1128,28 @@ var WorkspaceHttpError = class extends Error {
|
|
|
1000
1128
|
}
|
|
1001
1129
|
status;
|
|
1002
1130
|
};
|
|
1003
|
-
async function commandOutput(command, args) {
|
|
1004
|
-
try {
|
|
1005
|
-
const { stdout, stderr } = await execFileAsync(command, [...args], { encoding: "utf8", timeout: 5e3, maxBuffer: 4 * 1024 * 1024 });
|
|
1006
|
-
return `${stdout}${stderr}`.trim();
|
|
1007
|
-
} catch {
|
|
1008
|
-
return void 0;
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
function pythonVersion(value) {
|
|
1012
|
-
const match = /Python\s+(\d+)\.(\d+)(?:\.(\d+))?/u.exec(value ?? "");
|
|
1013
|
-
if (match === null) return { ok: false };
|
|
1014
|
-
const major = Number(match[1]);
|
|
1015
|
-
const minor = Number(match[2]);
|
|
1016
|
-
return { ok: major > 3 || major === 3 && minor >= 10, version: match[0].replace(/^Python\s+/u, "") };
|
|
1017
|
-
}
|
|
1018
1131
|
function configured(...names) {
|
|
1019
1132
|
return names.some((name2) => (process.env[name2] ?? "") !== "");
|
|
1020
1133
|
}
|
|
1134
|
+
var dramaPreflightInFlight;
|
|
1135
|
+
async function dramaPreflight() {
|
|
1136
|
+
if (dramaPreflightCache !== void 0 && dramaPreflightCache.expires > Date.now()) return dramaPreflightCache.value;
|
|
1137
|
+
dramaPreflightInFlight ??= (async () => {
|
|
1138
|
+
try {
|
|
1139
|
+
const python = await hostPython();
|
|
1140
|
+
const adapterConfig = await ensureDramaAdapterConfig(defaultDramaSkillRoot(), { python: python.command });
|
|
1141
|
+
const value = { python: python.probe, adapterConfig, adapters: dramaAdapterStatuses() };
|
|
1142
|
+
dramaPreflightCache = { expires: Date.now() + 3e4, value };
|
|
1143
|
+
return value;
|
|
1144
|
+
} finally {
|
|
1145
|
+
dramaPreflightInFlight = void 0;
|
|
1146
|
+
}
|
|
1147
|
+
})();
|
|
1148
|
+
return dramaPreflightInFlight;
|
|
1149
|
+
}
|
|
1021
1150
|
async function videoPreflight() {
|
|
1022
1151
|
if (videoPreflightCache !== void 0 && videoPreflightCache.expires > Date.now()) return videoPreflightCache.value;
|
|
1023
|
-
|
|
1024
|
-
for (const command of ["python3", "python"]) {
|
|
1025
|
-
const parsed = pythonVersion(await commandOutput(command, ["--version"]));
|
|
1026
|
-
if (parsed.version !== void 0) {
|
|
1027
|
-
python = parsed;
|
|
1028
|
-
break;
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1152
|
+
const python = (await hostPython()).probe;
|
|
1031
1153
|
const [ffmpegOutput, ffmpegFilters, ffprobeOutput] = await Promise.all([
|
|
1032
1154
|
commandOutput("ffmpeg", ["-version"]),
|
|
1033
1155
|
commandOutput("ffmpeg", ["-hide_banner", "-filters"]),
|
|
@@ -1342,7 +1464,7 @@ async function previewDigest(realm, projectRoot) {
|
|
|
1342
1464
|
}
|
|
1343
1465
|
};
|
|
1344
1466
|
await visit(app, "");
|
|
1345
|
-
return { ready, version:
|
|
1467
|
+
return { ready, version: createHash2("sha256").update(entries.sort().join("\n")).digest("hex").slice(0, 16) };
|
|
1346
1468
|
}
|
|
1347
1469
|
function headingTitle(content, fallback) {
|
|
1348
1470
|
const heading = content?.split(/\r?\n/u).find((line) => /^#\s+/u.test(line));
|
|
@@ -1408,14 +1530,14 @@ async function workspaceVideoProjects(realm, files, maxBytes) {
|
|
|
1408
1530
|
}));
|
|
1409
1531
|
}
|
|
1410
1532
|
function bundledExampleRoot() {
|
|
1411
|
-
return
|
|
1533
|
+
return resolve5(defaultNovelToGameSkillRoot(), `../examples/${BUNDLED_GAME_EXAMPLE}`);
|
|
1412
1534
|
}
|
|
1413
1535
|
async function bundledGameExample() {
|
|
1414
1536
|
const root = bundledExampleRoot();
|
|
1415
1537
|
const [example, verification, manifest] = await Promise.all([
|
|
1416
|
-
readNodeFile(
|
|
1417
|
-
readNodeFile(
|
|
1418
|
-
readNodeFile(
|
|
1538
|
+
readNodeFile(resolve5(root, "example.json"), "utf8"),
|
|
1539
|
+
readNodeFile(resolve5(root, "qa/verification.json"), "utf8"),
|
|
1540
|
+
readNodeFile(resolve5(defaultNovelToGameSkillRoot(), "../manifest.json"), "utf8")
|
|
1419
1541
|
]);
|
|
1420
1542
|
const exampleJson = JSON.parse(example);
|
|
1421
1543
|
const manifestJson = JSON.parse(manifest);
|
|
@@ -1516,8 +1638,8 @@ async function previewBytes(context, pathname) {
|
|
|
1516
1638
|
const id = segments.shift();
|
|
1517
1639
|
const path = segments.join("/") || "index.html";
|
|
1518
1640
|
if (id !== BUNDLED_GAME_EXAMPLE || !safeRelativePath(path)) throw new WorkspaceHttpError(404, "\u6E38\u620F\u793A\u4F8B\u4E0D\u5B58\u5728\u3002");
|
|
1519
|
-
const appRoot =
|
|
1520
|
-
const target2 =
|
|
1641
|
+
const appRoot = resolve5(bundledExampleRoot(), "build/app");
|
|
1642
|
+
const target2 = resolve5(appRoot, path);
|
|
1521
1643
|
const escaped = relative3(appRoot, target2);
|
|
1522
1644
|
if (escaped.startsWith("..") || isAbsolute3(escaped)) throw new WorkspaceHttpError(403, "\u9884\u89C8\u8D44\u6E90\u79BB\u5F00\u4E86\u6E38\u620F\u76EE\u5F55\u3002");
|
|
1523
1645
|
const info = await nodeStat(target2).catch(() => void 0);
|
|
@@ -1597,6 +1719,10 @@ async function handle(context, request, response, options) {
|
|
|
1597
1719
|
send(response, 200, await videoPreflight());
|
|
1598
1720
|
return;
|
|
1599
1721
|
}
|
|
1722
|
+
if (url.pathname === "/oh-story/drama-preflight" && request.method === "GET") {
|
|
1723
|
+
send(response, 200, await dramaPreflight());
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1600
1726
|
if (url.pathname === "/oh-story/file" && request.method === "GET") {
|
|
1601
1727
|
const realm = await workspaceRealm(context, url);
|
|
1602
1728
|
const path = url.searchParams.get("path");
|
|
@@ -1667,10 +1793,13 @@ async function apply(context, config = {}) {
|
|
|
1667
1793
|
registerOhStoryProductionTool(context);
|
|
1668
1794
|
await registerOhStoryRoleTool(context);
|
|
1669
1795
|
registerWorkspaceRoute(context, { maxBytes: config.editorMaxBytes ?? 2097152, trustedHosts });
|
|
1796
|
+
await ensureDramaAdapterConfig(defaultDramaSkillRoot(), { python: (await hostPython()).command });
|
|
1670
1797
|
}
|
|
1671
1798
|
var index_default = { name, inject, Config, apply };
|
|
1672
1799
|
export {
|
|
1673
1800
|
Config,
|
|
1801
|
+
DRAMA_ADAPTERS,
|
|
1802
|
+
DRAMA_ADAPTER_CONFIG_ENV,
|
|
1674
1803
|
OH_STORY_PRODUCTION_TOOL_NAME,
|
|
1675
1804
|
OH_STORY_REFERENCE_TOOL_NAME,
|
|
1676
1805
|
OH_STORY_ROLE_NAMES,
|
|
@@ -1685,6 +1814,9 @@ export {
|
|
|
1685
1814
|
createOhStorySkillProvider,
|
|
1686
1815
|
createVideoRecapSkillProvider,
|
|
1687
1816
|
index_default as default,
|
|
1817
|
+
dramaAdapterConfigPath,
|
|
1818
|
+
dramaAdapterStatuses,
|
|
1819
|
+
ensureDramaAdapterConfig,
|
|
1688
1820
|
inject,
|
|
1689
1821
|
loadBundledRole,
|
|
1690
1822
|
name,
|