@mulmoclaude/mulmoscript-plugin 0.2.0 → 0.2.2
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/dist/core/paths.d.ts +11 -6
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/{plugin-W1ppnyhR.js → plugin-BQyzxkui.js} +17 -10
- package/dist/plugin-BQyzxkui.js.map +1 -0
- package/dist/{plugin-CtKUt8DX.cjs → plugin-Bi986Wga.cjs} +17 -10
- package/dist/plugin-Bi986Wga.cjs.map +1 -0
- package/dist/server/ops.d.ts.map +1 -1
- package/dist/server.cjs +22 -8
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +22 -8
- package/dist/server.js.map +1 -1
- package/dist/style.css +18 -18
- package/dist/vue/View.vue.d.ts.map +1 -1
- package/dist/vue.cjs +10 -2
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.js +10 -2
- package/dist/vue.js.map +1 -1
- package/package.json +1 -1
- package/dist/plugin-CtKUt8DX.cjs.map +0 -1
- package/dist/plugin-W1ppnyhR.js.map +0 -1
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","names":[],"sources":["../src/server/support.ts","../src/server/mulmoErrorCapture.ts","../src/server/ops.ts","../src/server/dispatch.ts"],"sourcesContent":["// Small server-side utilities, self-contained so the package works in any\n// host. `resolveWithinRoot` is a faithful copy of MulmoClaude's\n// realpath-based traversal check (server/utils/files/safe.ts) — the\n// security-critical primitive must not drift per host, so it ships with the\n// ops that depend on it.\n\nimport { realpathSync } from \"fs\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"path\";\n\nexport function errorText(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (err !== null && typeof err === \"object\") {\n const obj = err as { details?: unknown; message?: unknown };\n if (typeof obj.details === \"string\" && obj.details) return obj.details;\n if (typeof obj.message === \"string\" && obj.message) return obj.message;\n }\n return String(err);\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nexport function stripDataUri(dataUri: string): string {\n return dataUri.replace(/^data:image\\/[^;]+;base64,/, \"\");\n}\n\n/** Realpath-based containment: resolve `relPath` against the ROOT's\n * realpath and require the target's realpath to stay inside it. Returns\n * null on ENOENT or traversal (symlink escapes included). */\nexport function resolveWithinRoot(rootReal: string, relPath: string): string | null {\n const normalized = path.normalize(relPath || \"\");\n const resolved = path.resolve(rootReal, normalized);\n let resolvedReal: string;\n try {\n resolvedReal = realpathSync(resolved);\n } catch {\n return null;\n }\n if (resolvedReal !== rootReal && !resolvedReal.startsWith(rootReal + path.sep)) {\n return null;\n }\n return resolvedReal;\n}\n\n// Async so reading a large generated image/audio file doesn't stall the\n// host's event loop (CodeRabbit on #2137).\nexport async function fileToDataUri(filePath: string, mimeType: string): Promise<string> {\n const data = await readFile(filePath);\n return `data:${mimeType};base64,${data.toString(\"base64\")}`;\n}\n","// Surfaces the underlying provider error that mulmocast swallows when a\n// generation fails. mulmocast catches the real error (missing API key,\n// quota, moderation, …), logs it via GraphAILogger.error, and rethrows a\n// generic wrapper like \"generateReferenceImage: generate error: key=x\" —\n// and `setGraphAILogger(false)` (called per request in buildContext to\n// silence GraphAI's chatty info/debug output) turns off even the error\n// level, so the true cause used to vanish entirely.\n//\n// Moved verbatim from MulmoClaude's server/utils/mulmoErrorCapture.ts in\n// phase 3 (only mulmoScript code ever used it). Hosts must resolve ONE\n// hoisted `graphai` copy shared with their `mulmocast` — GraphAILogger\n// state is module-local, and a second copy would break this capture\n// silently. That's why `graphai` is a peer dependency.\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { GraphAILogger } from \"graphai\";\nimport { errorText, isRecord } from \"./support\";\nimport type { MulmoScriptServerLog } from \"./types\";\n\nconst capturedErrors = new AsyncLocalStorage<string[]>();\nlet loggerInstalled = false;\nlet captureLog: MulmoScriptServerLog | null = null;\n\n/** Route captured GraphAI errors into the host logger. Set once by\n * `createMulmoScriptServerOps`; the GraphAILogger sink is global, so the\n * last-configured host logger wins (one ops instance per process). */\nexport function setMulmoErrorCaptureLogger(log: MulmoScriptServerLog | null): void {\n captureLog = log;\n}\n\nfunction formatLogArg(arg: unknown): string {\n if (typeof arg === \"string\") return arg;\n if (arg instanceof Error) return arg.message;\n try {\n return JSON.stringify(arg);\n } catch {\n return String(arg);\n }\n}\n\n/**\n * Re-enable GraphAI's error level (everything else stays silenced) and\n * route it into the host logger + the per-operation capture store.\n * Call after every `setGraphAILogger(false)` — that helper disables all\n * levels including error. Idempotent.\n */\nexport function enableGraphAIErrorCapture(): void {\n GraphAILogger.setLevelEnabled(\"error\", true);\n if (loggerInstalled) return;\n loggerInstalled = true;\n GraphAILogger.setLogger((level, ...args) => {\n if (level !== \"error\") return;\n const message = args.map(formatLogArg).join(\" \");\n captureLog?.warn(\"mulmocast generation error\", { message });\n capturedErrors.getStore()?.push(message);\n });\n}\n\n// Structured-`cause` fields mulmocast attaches for i18n notifications\n// (mulmocast lib/utils/error_cause.js) — agent + error type identify\n// which provider failed; envVarName names a missing API key outright.\nconst CAUSE_FIELDS = [\"type\", \"agentName\", \"envVarName\", \"errorCode\", \"errorType\"] as const;\n\n/** Render mulmocast's structured error `cause` as \"field=value\" pairs. */\nexport function describeMulmoCause(err: unknown): string | null {\n if (!(err instanceof Error) || !isRecord(err.cause)) return null;\n const { cause } = err;\n const parts = CAUSE_FIELDS.flatMap((field) => {\n const value = cause[field];\n return typeof value === \"string\" && value !== \"\" ? [`${field}=${value}`] : [];\n });\n return parts.length > 0 ? parts.join(\" \") : null;\n}\n\n/**\n * Compose the enriched message for a failed mulmocast operation:\n * mulmocast's own message, then its structured cause, then the\n * captured underlying provider error(s). Deduped — GraphAI retries\n * log the same error more than once.\n */\nexport function composeMulmoErrorMessage(err: unknown, captured: readonly string[]): string {\n const base = errorText(err);\n const details = [...new Set(captured)].filter((message) => message !== \"\" && message !== base);\n return [base, describeMulmoCause(err), ...details].filter(Boolean).join(\" — \");\n}\n\n/**\n * Run a mulmocast operation, capturing GraphAI error logs emitted while\n * it executes. On failure, rethrows with the captured provider error(s)\n * appended to the message (original error kept as `cause`). Uses\n * AsyncLocalStorage so concurrent operations don't cross-attribute.\n */\nexport async function withMulmoErrorCapture<T>(operation: () => Promise<T>): Promise<T> {\n return capturedErrors.run([], async () => {\n try {\n return await operation();\n } catch (err) {\n throw new Error(composeMulmoErrorMessage(err, capturedErrors.getStore() ?? []), { cause: err });\n }\n });\n}\n","// Transport-free cores for every mulmoScript operation, moved from\n// MulmoClaude's `server/api/routes/mulmo-script-ops.ts` in phase 3 so the\n// SAME implementation backs every host surface:\n//\n// - MulmoClaude's legacy REST routes (kept for wire compat),\n// - the generic plugin dispatch (see `./dispatch`) that the package View\n// calls in both MulmoClaude and MulmoTerminal.\n//\n// Every op returns an `OpResult` — failures are data (`code` preserves the\n// HTTP mapping for REST adapters) and never exceptions. Generation ops\n// publish start/finish through the instance's edge-triggered tracker, which\n// fans out via the injected `backend.onGenerationEvent` (session channels,\n// UI pubsub — host-specific) and backs the View's mount-time\n// `pendingGenerations` snapshot.\n//\n// Host-specific transport is injected via `MulmoScriptServerBackend`; the\n// mulmocast orchestration, realpath containment, and generation-state\n// tracking all live here.\n\nimport { existsSync, mkdirSync, realpathSync, statSync, unlinkSync } from \"fs\";\nimport path from \"path\";\nimport {\n getFileObject,\n initializeContextFromFiles,\n generateBeatImage,\n getBeatPngImagePath,\n generateBeatAudio,\n getBeatAudioPathOrUrl,\n getBeatAnimatedVideoPath,\n getBeatMoviePaths,\n generateReferenceImage,\n getReferenceImagePath,\n images,\n audio,\n movie,\n movieFilePath,\n pdf,\n pdfFilePath,\n setGraphAILogger,\n addSessionProgressCallback,\n removeSessionProgressCallback,\n} from \"mulmocast\";\nimport type { MulmoBeat, MulmoImagePromptMedia, MulmoStudioContext } from \"@mulmocast/types\";\nimport type { MulmoScriptGenerationEvent } from \"../core/contract\";\nimport { errorText, fileToDataUri, resolveWithinRoot, stripDataUri } from \"./support\";\nimport { enableGraphAIErrorCapture, setMulmoErrorCaptureLogger, withMulmoErrorCapture } from \"./mulmoErrorCapture\";\nimport type {\n GenerateOpArgs,\n MovieGenerationResult,\n MovieProgressEvent,\n MulmoScriptServerBackend,\n MulmoScriptServerLog,\n OpFailure,\n OpResult,\n PdfGenerationResult,\n} from \"./types\";\n\ntype GenerationKind = MulmoScriptGenerationEvent[\"kind\"];\n\n// We pin pdfMode=\"slide\" + pdfSize=\"a4\" — that's the configured default\n// for the storyboard editor; mulmocast's other modes (talk / handout /\n// letter) stay reachable via the CLI for power users. (#1614)\nexport const PDF_MODE = \"slide\" as const;\nexport const PDF_SIZE = \"a4\" as const;\n\nfunction opBadRequest(error: string): OpFailure {\n return { ok: false, code: \"bad_request\", error };\n}\n\nfunction opNotFound(error: string): OpFailure {\n return { ok: false, code: \"not_found\", error };\n}\n\nfunction opServerError(error: string): OpFailure {\n return { ok: false, code: \"server_error\", error };\n}\n\nconst NOOP_LOG: MulmoScriptServerLog = { info: () => {}, warn: () => {}, error: () => {} };\n\n// Helper: build mulmo context for a story file. The explicit return\n// annotation keeps declaration emit portable — the inferred type would\n// reference mulmocast's internal usage-collector path.\nexport async function buildContext(absoluteFilePath: string, force = false): Promise<MulmoStudioContext | null | undefined> {\n // setGraphAILogger(false) silences GraphAI's chatty info/debug output\n // but also its error level — re-enable error capture so a failed\n // generation surfaces the real provider error, not just mulmocast's\n // generic \"generate error\" wrapper.\n setGraphAILogger(false);\n enableGraphAIErrorCapture();\n const files = getFileObject({\n file: absoluteFilePath,\n basedir: path.dirname(absoluteFilePath),\n grouped: true,\n });\n return initializeContextFromFiles(files, true, force);\n}\n\n// Awaited context type used by every op that calls buildContext.\nexport type StoryContext = NonNullable<Awaited<ReturnType<typeof buildContext>>>;\n\nexport interface RunStoryOpDeps {\n resolveStory?: (filePath: string) => { ok: true; absolutePath: string } | OpFailure;\n buildContext?: (absoluteFilePath: string, force?: boolean) => Promise<StoryContext | undefined>;\n}\n\nexport interface RunStoryOpOptions<T> {\n force?: boolean;\n /**\n * Op-specific tag included in the failure log so dashboards can\n * distinguish which op is failing (e.g. `\"generate-beat-audio\"`).\n * Falls back to a generic `\"op failed\"` entry when omitted.\n */\n operation?: string;\n /**\n * Soft-fail override for `buildContext` returning undefined. Some\n * ops (e.g. `beatAudio`) historically returned a 200 `{ audio: null }`\n * in that case so the frontend can silently retry. If provided, this\n * callback returns the fallback result instead of the default\n * server_error \"Failed to initialize mulmo context\".\n */\n onContextMissing?: () => OpResult<T>;\n}\n\n// Map each beat to its array index, keyed by beat.id (falling back to\n// a synthetic `__index__<n>` for id-less beats). Shared by the movie\n// and PDF pipelines to translate mulmocast's per-beat progress events\n// (which carry the beat id) back into an index the UI can address.\nexport function buildBeatIdIndex(beats: MulmoBeat[]): Map<string, number> {\n const idToIndex = new Map<string, number>();\n beats.forEach((beat, index) => {\n const key = beat.id ?? `__index__${index}`;\n idToIndex.set(key, index);\n });\n return idToIndex;\n}\n\n/** Map identity for the in-flight tracker. JSON array keeps the three\n * fields unambiguous (a human-visible delimiter could collide). */\nfunction generationMapKey(kind: GenerationKind, filePath: string, key: string): string {\n return JSON.stringify([kind, filePath, key]);\n}\n\n/**\n * Build the per-host mulmoScript server ops instance. One instance per\n * process — it owns the in-flight movie/PDF dedup sets and the\n * generation-state tracker, and binds the injected host backend.\n */\nexport function createMulmoScriptServerOps(backend: MulmoScriptServerBackend) {\n const log = backend.log ?? NOOP_LOG;\n setMulmoErrorCaptureLogger(log);\n const storiesDir = path.resolve(backend.storiesDir);\n\n // ── Story path infrastructure ─────────────────────────────────\n\n // The download / status ops expect \"stories/<rel>\" (historical\n // convention, independent of the on-disk location) — the wire format\n // every endpoint keys on. Relativize against the REALPATH root when it\n // resolves: with a symlinked stories dir, mulmocast returns output\n // paths under the link's target, and relativizing against the link\n // itself would produce a traversal-like \"stories/../../…\" ref that\n // resolveStory then rejects (CodeRabbit on #2137).\n function toStoryRef(absolutePath: string): string {\n const root = ensureStoriesReal() ?? storiesDir;\n const rel = path.relative(root, absolutePath).split(path.sep).join(\"/\");\n return rel ? `stories/${rel}` : \"stories\";\n }\n\n // Lazily realpath the stories dir on first use. We can't realpath at\n // instance creation because the directory may not exist yet (it's\n // created on demand by the save route). The cache is invalidated\n // never — once the dir exists, its realpath is stable.\n let storiesRealCache: string | null = null;\n function ensureStoriesReal(): string | null {\n if (storiesRealCache) return storiesRealCache;\n try {\n mkdirSync(storiesDir, { recursive: true });\n storiesRealCache = realpathSync(storiesDir);\n return storiesRealCache;\n } catch {\n return null;\n }\n }\n\n /**\n * Resolve and validate a stories wire path to its absolute realpath.\n *\n * Uses the realpath-based resolveWithinRoot helper to defeat\n * symlink-based escapes. Callers pass workspace-relative paths like\n * \"stories/foo.json\" or \"stories/__movies__/bar.mp4\". We strip the\n * leading \"stories/\" segment and resolve the remainder against the\n * realpath of the stories directory itself — this works whether\n * stories/ is a regular directory or a legitimate symlink to another\n * location. ENOENT and traversal are distinguished (404 vs 400).\n */\n function resolveStory(filePath: string): { ok: true; absolutePath: string } | OpFailure {\n const storiesReal = ensureStoriesReal();\n if (!storiesReal) {\n return opServerError(\"stories directory not available\");\n }\n // Reject absolute paths and parent traversal at the syntactic\n // level — defense in depth on top of the realpath check below.\n if (path.isAbsolute(filePath)) {\n return opBadRequest(\"Invalid filePath\");\n }\n // Strip the optional \"stories/\" prefix so the remainder is a path\n // relative to storiesReal. Accepts both \"stories/foo.json\" (the\n // canonical caller convention) and bare \"foo.json\".\n const STORIES_PREFIX = `stories${path.sep}`;\n const relFromStories =\n filePath === \"stories\" ? \"\" : filePath.startsWith(STORIES_PREFIX) || filePath.startsWith(\"stories/\") ? filePath.slice(\"stories/\".length) : filePath;\n // resolveWithinRoot enforces both the realpath boundary AND\n // existence; ENOENT and traversal both produce null. Distinguish\n // them via a follow-up existsSync so 404 vs 400 stays accurate —\n // but only consult the filesystem for lexically in-root candidates:\n // a traversal path must never touch the fs (and gets a uniform\n // bad_request so responses don't leak existence outside the root).\n const resolved = resolveWithinRoot(storiesReal, relFromStories);\n if (!resolved) {\n const candidate = path.resolve(storiesReal, relFromStories);\n const inRoot = candidate === storiesReal || candidate.startsWith(storiesReal + path.sep);\n if (inRoot && !existsSync(candidate)) {\n return opNotFound(`File not found: ${filePath}`);\n }\n return opBadRequest(\"Invalid filePath\");\n }\n return { ok: true, absolutePath: resolved };\n }\n\n /**\n * Realpath containment pre-guard for wire paths handed to the phase-1\n * core's save/reopen/update executes. The core's own path guard is\n * lexical (it runs against the generic FileOps, whose read/write follows\n * symlinks), so hosts re-assert the realpath boundary here before\n * invoking it — a symlink planted below the stories dir can't read or\n * write outside the tree (Codex P1 on MulmoClaude#2133).\n *\n * Returns null when `filePath` isn't a non-empty string — shape\n * validation (including the script-vs-filePath mode check) belongs to\n * the core.\n */\n function guardStoryWirePath(filePath: unknown): OpFailure | null {\n if (typeof filePath !== \"string\" || filePath === \"\") return null;\n const resolved = resolveStory(filePath);\n return resolved.ok ? null : resolved;\n }\n\n // mulmocast shells out to ffmpeg for movie / beat rendering. When the\n // host's probe reports it absent, intercept with a clear failure\n // instead of letting the library throw an opaque spawn ENOENT\n // mid-pipeline. `undefined` means the probe hasn't completed — assume\n // available so a brief startup window never blocks a render.\n function ffmpegGuard(): OpFailure | null {\n if (backend.isFfmpegAvailable?.() === false) {\n return {\n ok: false,\n code: \"unavailable\",\n error: \"ffmpeg is not installed — movie and beat rendering are unavailable. Install ffmpeg and restart the server.\",\n };\n }\n return null;\n }\n\n // ── Generation tracker (edge-triggered) ───────────────────────\n\n // Refcounted: two concurrent generations with the same kind/filePath/key\n // (e.g. the same beat rendered from two tabs) must not have the first\n // completion erase the second run's snapshot entry, and only the first\n // start / LAST finish reach the host channels — an early completion\n // can't clear subscribers' spinners while a duplicate run is active.\n // A finish with no tracked start (the movie/PDF pipelines' per-beat\n // completion pulses) always publishes.\n const inFlightGenerations = new Map<string, { kind: GenerationKind; filePath: string; key: string; count: number }>();\n\n function publishGeneration(chatSessionId: string | undefined, kind: GenerationKind, filePath: string, key: string, finished: boolean, error?: string): void {\n const mapKey = generationMapKey(kind, filePath, key);\n const existing = inFlightGenerations.get(mapKey);\n if (finished) {\n if (existing && existing.count > 1) {\n existing.count -= 1;\n return; // a duplicate run is still active — suppress the early finish\n }\n inFlightGenerations.delete(mapKey);\n } else {\n if (existing) {\n existing.count += 1;\n return; // already reported as started\n }\n inFlightGenerations.set(mapKey, { kind, filePath, key, count: 1 });\n }\n const event: MulmoScriptGenerationEvent = { kind, filePath, key, done: finished, ...(error ? { error } : {}) };\n backend.onGenerationEvent?.(chatSessionId, event);\n }\n\n /** Snapshot of generations currently in flight for one script — the\n * View's mount-time catch-up, filtered to its wire `filePath`. */\n function pendingGenerations(filePath: string): MulmoScriptGenerationEvent[] {\n return [...inFlightGenerations.values()].filter((entry) => entry.filePath === filePath).map(({ kind, key }) => ({ kind, filePath, key, done: false }));\n }\n\n // ── Op scaffolding ────────────────────────────────────────────\n\n /**\n * Shared scaffolding for mulmoScript ops. Resolves the wire filePath,\n * builds the mulmo context, and folds unexpected handler errors into a\n * server_error failure (with a warn breadcrumb). Accepts a `deps` param\n * so unit tests can inject fakes without the full mulmocast stack.\n */\n async function runStoryOp<T>(\n filePath: string,\n options: RunStoryOpOptions<T>,\n handler: (ctx: { absoluteFilePath: string; context: StoryContext }) => Promise<OpResult<T>>,\n deps: RunStoryOpDeps = {},\n ): Promise<OpResult<T>> {\n const resolver = deps.resolveStory ?? resolveStory;\n const build = deps.buildContext ?? buildContext;\n const resolved = resolver(filePath);\n if (!resolved.ok) return resolved;\n try {\n const context = await build(resolved.absolutePath, options.force ?? false);\n if (!context) {\n if (options.onContextMissing) return options.onContextMissing();\n return opServerError(\"Failed to initialize mulmo context\");\n }\n // withMulmoErrorCapture appends the underlying provider error\n // (missing API key, quota, …) to any mulmocast failure, which\n // otherwise reaches the client as a generic \"generate error\".\n return await withMulmoErrorCapture(() => handler({ absoluteFilePath: resolved.absolutePath, context }));\n } catch (err) {\n // Log every op failure at warn so operators get a breadcrumb even\n // when the op doesn't wrap its own try/catch.\n log.warn(\"op failed\", {\n ...(options.operation ? { operation: options.operation } : {}),\n filePath,\n error: errorText(err),\n });\n return opServerError(errorText(err));\n }\n }\n\n // ── Probe ops ─────────────────────────────────────────────────\n\n async function beatImageOp(filePath: string, beatIndex: number): Promise<OpResult<{ image: string | null }>> {\n return runStoryOp<{ image: string | null }>(filePath, { operation: \"beat-image\" }, async ({ context }) => {\n const { imagePath } = getBeatPngImagePath(context, beatIndex);\n if (!existsSync(imagePath)) return { ok: true, image: null };\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n }\n\n // beatAudio is a probe — the frontend polls it expecting `{ audio: null }`\n // when nothing has been generated yet. Override the default\n // server_error-on-context-missing so the soft-fail contract is preserved.\n async function beatAudioOp(filePath: string, beatIndex: number): Promise<OpResult<{ audio: string | null }>> {\n return runStoryOp<{ audio: string | null }>(\n filePath,\n { operation: \"beat-audio\", onContextMissing: () => ({ ok: true, audio: null }) },\n async ({ context }) => {\n const beat = context.studio.script.beats[beatIndex];\n const audioPath = getBeatAudioPathOrUrl(beat.text ?? \"\", context, beat, context.lang);\n if (!audioPath || !existsSync(audioPath)) return { ok: true, audio: null };\n return { ok: true, audio: await fileToDataUri(audioPath, \"audio/mpeg\") };\n },\n );\n }\n\n // Probe for a beat's generated video clip. Preference order mirrors the\n // movie-assembly pipeline's \"most processed wins\": lip-synced > with\n // sound effect > raw movie clip > animated html_tailwind render. The\n // response is the \"stories/…\" wire path so the client can stream it\n // through the host's authenticated media download.\n async function beatMovieOp(filePath: string, beatIndex: number): Promise<OpResult<{ moviePath: string | null }>> {\n return runStoryOp<{ moviePath: string | null }>(filePath, { operation: \"beat-movie\" }, async ({ context }) => {\n const { movieFile, soundEffectFile, lipSyncFile } = getBeatMoviePaths(context, beatIndex);\n const candidates = [lipSyncFile, soundEffectFile, movieFile, getBeatAnimatedVideoPath(context, beatIndex)];\n const existing = candidates.find((candidate) => existsSync(candidate));\n return { ok: true, moviePath: existing ? toStoryRef(existing) : null };\n });\n }\n\n async function characterImageOp(filePath: string, key: string): Promise<OpResult<{ image: string | null }>> {\n return runStoryOp<{ image: string | null }>(filePath, { operation: \"character-image\" }, async ({ context }) => {\n const imagePath = getReferenceImagePath(context, key, \"png\");\n if (!existsSync(imagePath)) return { ok: true, image: null };\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n }\n\n /** Shared \"output exists and is newer than the source script\" gate for\n * movie / PDF status. A stale artifact (script edited after it was\n * generated) reports null so the UI re-offers the Generate button. */\n function freshOutputRef(outputPath: string, absoluteFilePath: string): string | null {\n if (!existsSync(outputPath)) return null;\n const outputMtime = statSync(outputPath).mtimeMs;\n const sourceMtime = statSync(absoluteFilePath).mtimeMs;\n if (outputMtime < sourceMtime) return null;\n return toStoryRef(outputPath);\n }\n\n async function movieStatusOp(filePath: string): Promise<OpResult<{ moviePath: string | null }>> {\n return runStoryOp(\n filePath,\n { operation: \"movie-status\", onContextMissing: () => ({ ok: true, moviePath: null }) },\n async ({ absoluteFilePath, context }) => ({ ok: true, moviePath: freshOutputRef(movieFilePath(context), absoluteFilePath) }),\n );\n }\n\n async function pdfStatusOp(filePath: string): Promise<OpResult<{ pdfPath: string | null }>> {\n return runStoryOp(filePath, { operation: \"pdf-status\", onContextMissing: () => ({ ok: true, pdfPath: null }) }, async ({ absoluteFilePath, context }) => ({\n ok: true,\n pdfPath: freshOutputRef(pdfFilePath(context, PDF_MODE), absoluteFilePath),\n }));\n }\n\n // ── Generation ops ────────────────────────────────────────────\n\n async function renderBeatOp(args: Required<Pick<GenerateOpArgs, \"filePath\" | \"beatIndex\">> & GenerateOpArgs): Promise<OpResult<{ image: string }>> {\n const { filePath, beatIndex, force, chatSessionId } = args;\n const ffmpeg = ffmpegGuard();\n if (ffmpeg) return ffmpeg;\n\n const mapKey = String(beatIndex);\n publishGeneration(chatSessionId, \"beatImage\", filePath, mapKey, false);\n let genError: string | undefined;\n try {\n const result = await runStoryOp<{ image: string }>(filePath, { force, operation: \"render-beat\" }, async ({ context }) => {\n await generateBeatImage({\n index: beatIndex,\n context,\n args: force ? { forceImage: true } : undefined,\n });\n const { imagePath } = getBeatPngImagePath(context, beatIndex);\n if (!existsSync(imagePath)) {\n return opServerError(\"Image was not generated\");\n }\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n if (!result.ok) genError = result.error;\n return result;\n } finally {\n publishGeneration(chatSessionId, \"beatImage\", filePath, mapKey, true, genError);\n }\n }\n\n async function generateBeatAudioOp(args: Required<Pick<GenerateOpArgs, \"filePath\" | \"beatIndex\">> & GenerateOpArgs): Promise<OpResult<{ audio: string }>> {\n const { filePath, beatIndex, force, chatSessionId } = args;\n const mapKey = String(beatIndex);\n publishGeneration(chatSessionId, \"beatAudio\", filePath, mapKey, false);\n let genError: string | undefined;\n try {\n const result = await runStoryOp<{ audio: string }>(filePath, { force, operation: \"generate-beat-audio\" }, async ({ context }) => {\n await generateBeatAudio(beatIndex, context, {\n settings: process.env as Record<string, string>,\n } as Parameters<typeof generateBeatAudio>[2]);\n\n const beat = context.studio.script.beats[beatIndex];\n const audioPath = context.studio.beats[beatIndex]?.audioFile ?? getBeatAudioPathOrUrl(beat.text ?? \"\", context, beat, context.lang);\n\n if (!audioPath || !existsSync(audioPath)) {\n // Logic-flow failure (not an exception) — emit a targeted\n // log. Don't write raw `beat.text` into persistent logs —\n // it's free-form user content and can contain sensitive\n // data.\n log.error(\"audio was not generated\", {\n beatIndex,\n audioPath,\n exists: audioPath ? existsSync(audioPath) : false,\n beatTextLength: typeof beat?.text === \"string\" ? beat.text.length : 0,\n audioFilePresent: Boolean(context.studio.beats[beatIndex]?.audioFile),\n });\n return opServerError(\"Audio was not generated\");\n }\n return { ok: true, audio: await fileToDataUri(audioPath, \"audio/mpeg\") };\n });\n if (!result.ok) genError = result.error;\n return result;\n } finally {\n publishGeneration(chatSessionId, \"beatAudio\", filePath, mapKey, true, genError);\n }\n }\n\n async function renderCharacterOp(args: Required<Pick<GenerateOpArgs, \"filePath\" | \"key\">> & GenerateOpArgs): Promise<OpResult<{ image: string }>> {\n const { filePath, key, force, chatSessionId } = args;\n publishGeneration(chatSessionId, \"characterImage\", filePath, key, false);\n let genError: string | undefined;\n try {\n const result = await runStoryOp<{ image: string }>(filePath, { force, operation: \"render-character\" }, async ({ context }) => {\n // `imageEntries` (not `images`) to avoid shadowing mulmocast's\n // imported `images()` pipeline stage.\n const imageEntries = context.studio.script.imageParams?.images ?? {};\n const imageEntry = imageEntries[key];\n if (!imageEntry || imageEntry.type !== \"imagePrompt\") {\n return opBadRequest(`No imagePrompt entry for key: ${key}`);\n }\n\n const index = Object.keys(imageEntries).indexOf(key);\n const imagePath = getReferenceImagePath(context, key, \"png\");\n mkdirSync(path.dirname(imagePath), { recursive: true });\n\n await generateReferenceImage({\n context,\n key,\n index,\n image: imageEntry as MulmoImagePromptMedia,\n force,\n });\n if (!existsSync(imagePath)) {\n return opServerError(\"Character image was not generated\");\n }\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n if (!result.ok) genError = result.error;\n return result;\n } finally {\n publishGeneration(chatSessionId, \"characterImage\", filePath, key, true, genError);\n }\n }\n\n // ── Upload ops ────────────────────────────────────────────────\n\n async function uploadBeatImageOp(filePath: string, beatIndex: number, imageData: string): Promise<OpResult<{ image: string }>> {\n return runStoryOp<{ image: string }>(filePath, { operation: \"upload-beat-image\" }, async ({ context }) => {\n const { imagePath } = getBeatPngImagePath(context, beatIndex);\n // writeFileAtomic creates parent dirs and prevents a half-\n // written PNG from surviving a crash mid-write (#881 v2).\n const base64 = stripDataUri(imageData);\n await backend.writeFileAtomic(imagePath, Buffer.from(base64, \"base64\"));\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n }\n\n async function uploadCharacterImageOp(filePath: string, key: string, imageData: string): Promise<OpResult<{ image: string }>> {\n return runStoryOp<{ image: string }>(filePath, { operation: \"upload-character-image\" }, async ({ context }) => {\n const imagePath = getReferenceImagePath(context, key, \"png\");\n const base64 = stripDataUri(imageData);\n await backend.writeFileAtomic(imagePath, Buffer.from(base64, \"base64\"));\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n }\n\n // ── Movie / PDF pipelines ─────────────────────────────────────\n\n // Per-instance dedup so a foreground call (SSE route or long-held\n // dispatch) and a fire-and-forget background call can't race on the same\n // script. Keyed by the realpath (absoluteFilePath) so two different wire\n // spellings of the same file still collide. Process-local — a\n // multi-process deployment would need an external lock; out of scope.\n const inFlightMovies = new Set<string>();\n\n // Same dedup model as inFlightMovies, scoped to PDF generation\n // (#1614). PDFs and movies don't share the lock — they write to\n // different output files and can safely run in parallel.\n const inFlightPdfs = new Set<string>();\n\n // Shared core for the SSE-streaming route, the long-held dispatch op, and\n // the fire-and-forget background path triggered by `autoGenerateMovie`.\n // Builds the mulmo context, runs audio→images→movie, and reports\n // per-beat progress through the supplied callback. Throws on\n // unexpected pipeline errors; returns a structured failure when the\n // pipeline runs to completion but the output file is missing.\n async function runMovieGeneration(absoluteFilePath: string, onProgressEvent: (event: MovieProgressEvent) => void): Promise<MovieGenerationResult> {\n return withMulmoErrorCapture(() => runMoviePipeline(absoluteFilePath, onProgressEvent));\n }\n\n async function runMoviePipeline(absoluteFilePath: string, onProgressEvent: (event: MovieProgressEvent) => void): Promise<MovieGenerationResult> {\n const context = await buildContext(absoluteFilePath);\n if (!context) return { ok: false, error: \"Failed to initialize mulmo context\" };\n\n const idToIndex = buildBeatIdIndex(context.studio.script.beats as MulmoBeat[]);\n\n // Known limitation: addSessionProgressCallback is global, so when two\n // movie generations for *different* scripts run concurrently, both\n // closures are invoked for every beat event and rely on idToIndex to\n // filter out the other run's events. That filter is reliable only\n // when each beat carries an explicit `id`. Beats without one fall\n // back to \"__index__${index}\", and identical fallback ids across\n // scripts collide → progress meant for script A surfaces on script B.\n // Fixing this properly needs mulmocast to attach a per-run identifier\n // to its progress events (or a global serialization gate); tracked\n // separately.\n const onProgress = (event: { kind: string; sessionType: string; id?: string; inSession: boolean }) => {\n if (event.kind !== \"beat\" || event.inSession || event.id === undefined) return;\n const beatIndex = idToIndex.get(event.id);\n if (beatIndex === undefined) return;\n if (event.sessionType !== \"image\" && event.sessionType !== \"audio\") return;\n onProgressEvent({ kind: event.sessionType, beatIndex });\n };\n\n addSessionProgressCallback(onProgress);\n try {\n // Order matters: audio() must run before images(). For html_tailwind\n // beats with `animation: true`, mulmocast only emits the per-beat\n // `_animated.mp4` when the beat's duration is already known (see\n // processHtmlTailwindAnimated in mulmocast). Durations are populated\n // by audio(), so running images() first leaves the .mp4 files\n // missing and movie() then fails in validateBeatSource.\n const audioContext = await audio(context);\n const imagesContext = await images(audioContext);\n await movie(imagesContext);\n\n const outputPath = movieFilePath(imagesContext);\n if (!existsSync(outputPath)) return { ok: false, error: \"Movie was not generated\" };\n return { ok: true, outputPath };\n } finally {\n removeSessionProgressCallback(onProgress);\n }\n }\n\n /**\n * Long-held foreground movie generation (the package View's\n * `generateMovie` dispatch). Resolves when the whole pipeline finishes.\n * Per-beat completions are mirrored to the generation channels so the\n * initiating View (and any other mounted View) reloads assets off disk\n * as they land — the successor of the SSE per-beat events.\n */\n async function generateMovieOp(filePath: string, chatSessionId: string | undefined): Promise<OpResult<{ moviePath: string }>> {\n const ffmpeg = ffmpegGuard();\n if (ffmpeg) return ffmpeg;\n const resolved = resolveStory(filePath);\n if (!resolved.ok) return resolved;\n const absoluteFilePath = resolved.absolutePath;\n\n if (inFlightMovies.has(absoluteFilePath)) {\n return opBadRequest(\"Movie generation is already in progress for this script\");\n }\n\n inFlightMovies.add(absoluteFilePath);\n publishGeneration(chatSessionId, \"movie\", filePath, \"\", false);\n let genError: string | undefined;\n try {\n const result = await runMovieGeneration(absoluteFilePath, (event) => {\n const eventKind = event.kind === \"image\" ? \"beatImage\" : \"beatAudio\";\n publishGeneration(chatSessionId, eventKind, filePath, String(event.beatIndex), true);\n });\n if (!result.ok) {\n genError = result.error;\n return opServerError(result.error);\n }\n return { ok: true, moviePath: toStoryRef(result.outputPath) };\n } catch (err) {\n genError = errorText(err);\n return opServerError(genError);\n } finally {\n inFlightMovies.delete(absoluteFilePath);\n publishGeneration(chatSessionId, \"movie\", filePath, \"\", true, genError);\n }\n }\n\n function triggerAutoBackgroundMovie(absoluteFilePath: string, wireFilePath: string, chatSessionId: string | undefined): void {\n if (inFlightMovies.has(absoluteFilePath)) return;\n inFlightMovies.add(absoluteFilePath);\n void runBackgroundMovieGeneration(absoluteFilePath, wireFilePath, chatSessionId);\n }\n\n // Detached movie generation. Reports progress through the generation\n // channels the View watches — so a user opening the canvas\n // mid-generation sees spinners, and a user opening it after completion\n // sees the finished movie loaded from disk by the View's normal\n // mount-time path. Errors are persisted to a `<filename>.error.txt`\n // sidecar next to the script (no synchronous client to alert); any\n // stale sidecar from a previous run is cleared on each new attempt.\n // Triggered server-side from the unified save route when the caller\n // passes `autoGenerateMovie: true`.\n async function runBackgroundMovieGeneration(absoluteFilePath: string, wireFilePath: string, chatSessionId: string | undefined): Promise<void> {\n const errorSidecarPath = `${absoluteFilePath}.error.txt`;\n // Clear stale error from a previous failed run before starting; if it\n // doesn't exist that's fine. Catch any unexpected fs errors silently —\n // the worst case is the user sees an out-of-date error file later.\n try {\n unlinkSync(errorSidecarPath);\n } catch {\n // intentional: ENOENT is the common case, others non-fatal\n }\n\n publishGeneration(chatSessionId, \"movie\", wireFilePath, \"\", false);\n let genError: string | undefined;\n try {\n const result = await runMovieGeneration(absoluteFilePath, (event) => {\n // Mirror per-beat completions through the generation channels so\n // subscribed Views reload the asset off disk. We fire start+finish\n // in two ticks — `setImmediate` lets the session SSE writer flush\n // the start event before the finish removes the entry, otherwise\n // Vue's batched reactivity could see a net \"no change\" and skip\n // the reload.\n const eventKind = event.kind === \"image\" ? \"beatImage\" : \"beatAudio\";\n const key = String(event.beatIndex);\n publishGeneration(chatSessionId, eventKind, wireFilePath, key, false);\n setImmediate(() => publishGeneration(chatSessionId, eventKind, wireFilePath, key, true));\n });\n\n if (!result.ok) {\n genError = result.error;\n await writeErrorSidecar(errorSidecarPath, result.error);\n log.warn(\"background movie generation failed\", { filePath: wireFilePath, error: result.error });\n return;\n }\n log.info(\"background movie generation done\", {\n filePath: wireFilePath,\n outputPath: result.outputPath,\n });\n } catch (err) {\n genError = errorText(err);\n await writeErrorSidecar(errorSidecarPath, genError);\n log.error(\"background movie generation crashed\", { filePath: wireFilePath, error: genError });\n } finally {\n inFlightMovies.delete(absoluteFilePath);\n publishGeneration(chatSessionId, \"movie\", wireFilePath, \"\", true, genError);\n }\n }\n\n // Atomic write so a crash mid-write can't leave a truncated sidecar.\n async function writeErrorSidecar(errorSidecarPath: string, message: string): Promise<void> {\n try {\n await backend.writeFileAtomic(errorSidecarPath, message);\n } catch (writeErr) {\n log.error(\"failed to write error sidecar\", {\n errorSidecarPath,\n error: errorText(writeErr),\n });\n }\n }\n\n // ── PDF (#1614) ───────────────────────────────────────────────\n\n // Shared core for the SSE-streaming route and the long-held dispatch op.\n // Mirrors the movie pipeline's per-beat progress reporting so the UI can\n // light spinners during the image pass; the PDF action itself doesn't\n // emit progress events, so only image events are forwarded. Returns a\n // structured failure when the pipeline completes but the output file is\n // missing.\n async function runPdfGeneration(context: StoryContext, onImageBeatDone: (beatIndex: number) => void): Promise<PdfGenerationResult> {\n return withMulmoErrorCapture(() => runPdfPipeline(context, onImageBeatDone));\n }\n\n async function runPdfPipeline(context: StoryContext, onImageBeatDone: (beatIndex: number) => void): Promise<PdfGenerationResult> {\n const idToIndex = buildBeatIdIndex(context.studio.script.beats as MulmoBeat[]);\n const onProgress = (event: { kind: string; sessionType: string; id?: string; inSession: boolean }) => {\n if (event.kind !== \"beat\" || event.inSession || event.id === undefined) return;\n const beatIndex = idToIndex.get(event.id);\n if (beatIndex === undefined) return;\n if (event.sessionType !== \"image\") return;\n onImageBeatDone(beatIndex);\n };\n addSessionProgressCallback(onProgress);\n try {\n const imagesContext = await images(context);\n await pdf(imagesContext, PDF_MODE, PDF_SIZE);\n const outputPath = pdfFilePath(imagesContext, PDF_MODE);\n if (!existsSync(outputPath)) return { ok: false, error: \"PDF was not generated\" };\n return { ok: true, outputPath };\n } finally {\n removeSessionProgressCallback(onProgress);\n }\n }\n\n /** Long-held foreground PDF generation (the package View's `generatePdf`\n * dispatch) — the PDF sibling of `generateMovieOp`. */\n async function generatePdfOp(filePath: string, chatSessionId: string | undefined): Promise<OpResult<{ pdfPath: string }>> {\n const ffmpeg = ffmpegGuard();\n if (ffmpeg) return ffmpeg;\n const resolved = resolveStory(filePath);\n if (!resolved.ok) return resolved;\n const absoluteFilePath = resolved.absolutePath;\n\n if (inFlightPdfs.has(absoluteFilePath)) {\n return opBadRequest(\"PDF generation is already in progress for this script\");\n }\n\n inFlightPdfs.add(absoluteFilePath);\n publishGeneration(chatSessionId, \"pdf\", filePath, \"\", false);\n let genError: string | undefined;\n try {\n const context = await buildContext(absoluteFilePath);\n if (!context) {\n genError = \"Failed to initialize mulmo context\";\n return opServerError(genError);\n }\n const result = await runPdfGeneration(context, (beatIndex) => {\n publishGeneration(chatSessionId, \"beatImage\", filePath, String(beatIndex), true);\n });\n if (!result.ok) {\n genError = result.error;\n return opServerError(result.error);\n }\n return { ok: true, pdfPath: toStoryRef(result.outputPath) };\n } catch (err) {\n genError = errorText(err);\n return opServerError(genError);\n } finally {\n inFlightPdfs.delete(absoluteFilePath);\n publishGeneration(chatSessionId, \"pdf\", filePath, \"\", true, genError);\n }\n }\n\n return {\n backend,\n toStoryRef,\n resolveStory,\n guardStoryWirePath,\n ffmpegGuard,\n runStoryOp,\n publishGeneration,\n pendingGenerations,\n beatImageOp,\n beatAudioOp,\n beatMovieOp,\n characterImageOp,\n movieStatusOp,\n pdfStatusOp,\n renderBeatOp,\n generateBeatAudioOp,\n renderCharacterOp,\n uploadBeatImageOp,\n uploadCharacterImageOp,\n inFlightMovies,\n inFlightPdfs,\n runMovieGeneration,\n runPdfGeneration,\n generateMovieOp,\n generatePdfOp,\n triggerAutoBackgroundMovie,\n };\n}\n\nexport type MulmoScriptServerOps = ReturnType<typeof createMulmoScriptServerOps>;\n","// The mulmoScript dispatch router, moved from MulmoClaude's\n// `server/plugins/mulmoscript-builtin.ts` in phase 3 so every host serves\n// the package View's `useRuntime().dispatch({ kind, … })` calls with the\n// SAME kind routing and validation. Hosts register the returned handler on\n// their dispatch channel (MulmoClaude: `registerBuiltinDispatch`;\n// MulmoTerminal: its `/api/plugin` interception).\n//\n// Response contract: every kind resolves to an `{ ok: … }` envelope (see\n// `../core/contract.ts`) — business failures are data, not thrown errors,\n// so user-facing messages stay free of transport prefixes.\n\nimport { executeMulmoScriptSave, executeUpdateBeat, executeUpdateScript, type MulmoScriptFailure } from \"../core/plugin\";\nimport type { MulmoScriptExecuteContext } from \"../core/types\";\nimport type { MulmoScriptServerOps } from \"./ops\";\nimport type { OpFailure } from \"./types\";\n\ninterface DispatchFailure {\n ok: false;\n code: \"bad_request\" | \"not_found\" | \"server_error\";\n error: string;\n}\n\nfunction fromOpFailure(failure: OpFailure): DispatchFailure {\n // \"unavailable\" (ffmpeg missing) has no slot in the contract's code\n // union — the View only reads `error`, so fold it into server_error\n // rather than widening the shared contract for one case.\n const code = failure.code === \"unavailable\" ? \"server_error\" : failure.code;\n return { ok: false, code, error: failure.error };\n}\n\nfunction fromPackageFailure(failure: MulmoScriptFailure): DispatchFailure {\n return { ok: false, code: failure.code, error: failure.error };\n}\n\nfunction invalidArgs(kind: string): DispatchFailure {\n return { ok: false, code: \"bad_request\", error: `invalid arguments for mulmoScript dispatch kind \"${kind}\"` };\n}\n\nfunction str(value: unknown): string | undefined {\n return typeof value === \"string\" && value !== \"\" ? value : undefined;\n}\n\n// Beat indexes must be non-negative integers — reject `-1` / `1.5` at the\n// dispatch boundary so invalid client input surfaces as a deterministic\n// bad_request instead of leaking into beat-indexed ops.\nfunction num(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0 ? value : undefined;\n}\n\ninterface BeatArgs {\n filePath: string;\n beatIndex: number;\n}\n\ninterface KeyArgs {\n filePath: string;\n key: string;\n}\n\n/** Pass ok results through untouched; normalize failures for the wire. */\nfunction envelope<T>(result: ({ ok: true } & T) | OpFailure): ({ ok: true } & T) | DispatchFailure {\n return result.ok ? result : fromOpFailure(result);\n}\n\nfunction beatArgs(args: Record<string, unknown>): BeatArgs | null {\n const filePath = str(args.filePath);\n const beatIndex = num(args.beatIndex);\n if (!filePath || beatIndex === undefined) return null;\n return { filePath, beatIndex };\n}\n\nfunction keyArgs(args: Record<string, unknown>): KeyArgs | null {\n const filePath = str(args.filePath);\n const key = str(args.key);\n if (!filePath || !key) return null;\n return { filePath, key };\n}\n\nconst PROBE_KINDS = new Set([\"beatImage\", \"beatAudio\", \"beatMovie\", \"characterImage\", \"movieStatus\", \"pdfStatus\"]);\nconst GENERATE_KINDS = new Set([\"renderBeat\", \"generateBeatAudio\", \"renderCharacter\", \"generateMovie\", \"generatePdf\"]);\nconst UPLOAD_KINDS = new Set([\"uploadBeatImage\", \"uploadCharacterImage\"]);\n\nexport type MulmoScriptDispatchHandler = (args: Record<string, unknown>) => Promise<unknown>;\n\n/**\n * Build the kind router over an ops instance. The save / reopen / update\n * kinds run the phase-1 core executes against the backend's artifacts\n * FileOps, guarded by the instance's realpath containment\n * (`guardStoryWirePath`) — the core's own guard is lexical.\n */\nexport function createMulmoScriptDispatchHandler(ops: MulmoScriptServerOps): MulmoScriptDispatchHandler {\n const executeContext: MulmoScriptExecuteContext = { files: { artifacts: ops.backend.artifacts } };\n\n async function saveKind(args: Record<string, unknown>): Promise<unknown> {\n const guard = ops.guardStoryWirePath(args.filePath);\n if (guard) return fromOpFailure(guard);\n const outcome = await executeMulmoScriptSave(executeContext, {\n script: args.script,\n filename: str(args.filename),\n filePath: str(args.filePath),\n });\n if (!outcome.ok) return fromPackageFailure(outcome);\n return { ok: true, script: outcome.script, filePath: outcome.filePath, message: outcome.message };\n }\n\n async function updateKind(kind: \"updateBeat\" | \"updateScript\", args: Record<string, unknown>): Promise<unknown> {\n const guard = ops.guardStoryWirePath(args.filePath);\n if (guard) return fromOpFailure(guard);\n const outcome = kind === \"updateBeat\" ? await executeUpdateBeat(executeContext, args) : await executeUpdateScript(executeContext, args);\n return outcome.ok ? { ok: true } : fromPackageFailure(outcome);\n }\n\n const STATUS_OPS = { movieStatus: ops.movieStatusOp, pdfStatus: ops.pdfStatusOp } as const;\n const BEAT_PROBE_OPS = { beatImage: ops.beatImageOp, beatAudio: ops.beatAudioOp, beatMovie: ops.beatMovieOp } as const;\n\n async function probeKind(kind: string, args: Record<string, unknown>): Promise<unknown> {\n const statusOp = STATUS_OPS[kind as keyof typeof STATUS_OPS];\n if (statusOp) {\n const filePath = str(args.filePath);\n return filePath ? envelope(await statusOp(filePath)) : invalidArgs(kind);\n }\n if (kind === \"characterImage\") {\n const parsed = keyArgs(args);\n return parsed ? envelope(await ops.characterImageOp(parsed.filePath, parsed.key)) : invalidArgs(kind);\n }\n const parsed = beatArgs(args);\n if (!parsed) return invalidArgs(kind);\n return envelope(await BEAT_PROBE_OPS[kind as keyof typeof BEAT_PROBE_OPS](parsed.filePath, parsed.beatIndex));\n }\n\n async function generateKind(kind: string, args: Record<string, unknown>): Promise<unknown> {\n const chatSessionId = str(args.chatSessionId);\n const force = args.force === true;\n if (kind === \"generateMovie\" || kind === \"generatePdf\") {\n const filePath = str(args.filePath);\n if (!filePath) return invalidArgs(kind);\n const result = kind === \"generateMovie\" ? await ops.generateMovieOp(filePath, chatSessionId) : await ops.generatePdfOp(filePath, chatSessionId);\n return envelope(result);\n }\n if (kind === \"renderCharacter\") {\n const parsed = keyArgs(args);\n return parsed ? envelope(await ops.renderCharacterOp({ ...parsed, force, chatSessionId })) : invalidArgs(kind);\n }\n const parsed = beatArgs(args);\n if (!parsed) return invalidArgs(kind);\n const result =\n kind === \"renderBeat\" ? await ops.renderBeatOp({ ...parsed, force, chatSessionId }) : await ops.generateBeatAudioOp({ ...parsed, force, chatSessionId });\n return envelope(result);\n }\n\n async function uploadKind(kind: string, args: Record<string, unknown>): Promise<unknown> {\n const imageData = str(args.imageData);\n if (!imageData) return invalidArgs(kind);\n if (kind === \"uploadCharacterImage\") {\n const parsed = keyArgs(args);\n return parsed ? envelope(await ops.uploadCharacterImageOp(parsed.filePath, parsed.key, imageData)) : invalidArgs(kind);\n }\n const parsed = beatArgs(args);\n if (!parsed) return invalidArgs(kind);\n return envelope(await ops.uploadBeatImageOp(parsed.filePath, parsed.beatIndex, imageData));\n }\n\n return async (args: Record<string, unknown>): Promise<unknown> => {\n const kind = str(args.kind);\n if (!kind) return invalidArgs(\"<missing>\");\n if (kind === \"save\") return saveKind(args);\n if (kind === \"updateBeat\" || kind === \"updateScript\") return updateKind(kind, args);\n if (PROBE_KINDS.has(kind)) return probeKind(kind, args);\n if (GENERATE_KINDS.has(kind)) return generateKind(kind, args);\n if (UPLOAD_KINDS.has(kind)) return uploadKind(kind, args);\n if (kind === \"pendingGenerations\") {\n const filePath = str(args.filePath);\n if (!filePath) return invalidArgs(kind);\n return { ok: true, pending: ops.pendingGenerations(filePath) };\n }\n return { ok: false, code: \"bad_request\", error: `unknown mulmoScript dispatch kind \"${kind}\"` };\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../src/server/support.ts","../src/server/mulmoErrorCapture.ts","../src/server/ops.ts","../src/server/dispatch.ts"],"sourcesContent":["// Small server-side utilities, self-contained so the package works in any\n// host. `resolveWithinRoot` is a faithful copy of MulmoClaude's\n// realpath-based traversal check (server/utils/files/safe.ts) — the\n// security-critical primitive must not drift per host, so it ships with the\n// ops that depend on it.\n\nimport { realpathSync } from \"fs\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"path\";\n\nexport function errorText(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (err !== null && typeof err === \"object\") {\n const obj = err as { details?: unknown; message?: unknown };\n if (typeof obj.details === \"string\" && obj.details) return obj.details;\n if (typeof obj.message === \"string\" && obj.message) return obj.message;\n }\n return String(err);\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nexport function stripDataUri(dataUri: string): string {\n return dataUri.replace(/^data:image\\/[^;]+;base64,/, \"\");\n}\n\n/** Realpath-based containment: resolve `relPath` against the ROOT's\n * realpath and require the target's realpath to stay inside it. Returns\n * null on ENOENT or traversal (symlink escapes included). */\nexport function resolveWithinRoot(rootReal: string, relPath: string): string | null {\n const normalized = path.normalize(relPath || \"\");\n const resolved = path.resolve(rootReal, normalized);\n let resolvedReal: string;\n try {\n resolvedReal = realpathSync(resolved);\n } catch {\n return null;\n }\n if (resolvedReal !== rootReal && !resolvedReal.startsWith(rootReal + path.sep)) {\n return null;\n }\n return resolvedReal;\n}\n\n// Async so reading a large generated image/audio file doesn't stall the\n// host's event loop (CodeRabbit on #2137).\nexport async function fileToDataUri(filePath: string, mimeType: string): Promise<string> {\n const data = await readFile(filePath);\n return `data:${mimeType};base64,${data.toString(\"base64\")}`;\n}\n","// Surfaces the underlying provider error that mulmocast swallows when a\n// generation fails. mulmocast catches the real error (missing API key,\n// quota, moderation, …), logs it via GraphAILogger.error, and rethrows a\n// generic wrapper like \"generateReferenceImage: generate error: key=x\" —\n// and `setGraphAILogger(false)` (called per request in buildContext to\n// silence GraphAI's chatty info/debug output) turns off even the error\n// level, so the true cause used to vanish entirely.\n//\n// Moved verbatim from MulmoClaude's server/utils/mulmoErrorCapture.ts in\n// phase 3 (only mulmoScript code ever used it). Hosts must resolve ONE\n// hoisted `graphai` copy shared with their `mulmocast` — GraphAILogger\n// state is module-local, and a second copy would break this capture\n// silently. That's why `graphai` is a peer dependency.\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { GraphAILogger } from \"graphai\";\nimport { errorText, isRecord } from \"./support\";\nimport type { MulmoScriptServerLog } from \"./types\";\n\nconst capturedErrors = new AsyncLocalStorage<string[]>();\nlet loggerInstalled = false;\nlet captureLog: MulmoScriptServerLog | null = null;\n\n/** Route captured GraphAI errors into the host logger. Set once by\n * `createMulmoScriptServerOps`; the GraphAILogger sink is global, so the\n * last-configured host logger wins (one ops instance per process). */\nexport function setMulmoErrorCaptureLogger(log: MulmoScriptServerLog | null): void {\n captureLog = log;\n}\n\nfunction formatLogArg(arg: unknown): string {\n if (typeof arg === \"string\") return arg;\n if (arg instanceof Error) return arg.message;\n try {\n return JSON.stringify(arg);\n } catch {\n return String(arg);\n }\n}\n\n/**\n * Re-enable GraphAI's error level (everything else stays silenced) and\n * route it into the host logger + the per-operation capture store.\n * Call after every `setGraphAILogger(false)` — that helper disables all\n * levels including error. Idempotent.\n */\nexport function enableGraphAIErrorCapture(): void {\n GraphAILogger.setLevelEnabled(\"error\", true);\n if (loggerInstalled) return;\n loggerInstalled = true;\n GraphAILogger.setLogger((level, ...args) => {\n if (level !== \"error\") return;\n const message = args.map(formatLogArg).join(\" \");\n captureLog?.warn(\"mulmocast generation error\", { message });\n capturedErrors.getStore()?.push(message);\n });\n}\n\n// Structured-`cause` fields mulmocast attaches for i18n notifications\n// (mulmocast lib/utils/error_cause.js) — agent + error type identify\n// which provider failed; envVarName names a missing API key outright.\nconst CAUSE_FIELDS = [\"type\", \"agentName\", \"envVarName\", \"errorCode\", \"errorType\"] as const;\n\n/** Render mulmocast's structured error `cause` as \"field=value\" pairs. */\nexport function describeMulmoCause(err: unknown): string | null {\n if (!(err instanceof Error) || !isRecord(err.cause)) return null;\n const { cause } = err;\n const parts = CAUSE_FIELDS.flatMap((field) => {\n const value = cause[field];\n return typeof value === \"string\" && value !== \"\" ? [`${field}=${value}`] : [];\n });\n return parts.length > 0 ? parts.join(\" \") : null;\n}\n\n/**\n * Compose the enriched message for a failed mulmocast operation:\n * mulmocast's own message, then its structured cause, then the\n * captured underlying provider error(s). Deduped — GraphAI retries\n * log the same error more than once.\n */\nexport function composeMulmoErrorMessage(err: unknown, captured: readonly string[]): string {\n const base = errorText(err);\n const details = [...new Set(captured)].filter((message) => message !== \"\" && message !== base);\n return [base, describeMulmoCause(err), ...details].filter(Boolean).join(\" — \");\n}\n\n/**\n * Run a mulmocast operation, capturing GraphAI error logs emitted while\n * it executes. On failure, rethrows with the captured provider error(s)\n * appended to the message (original error kept as `cause`). Uses\n * AsyncLocalStorage so concurrent operations don't cross-attribute.\n */\nexport async function withMulmoErrorCapture<T>(operation: () => Promise<T>): Promise<T> {\n return capturedErrors.run([], async () => {\n try {\n return await operation();\n } catch (err) {\n throw new Error(composeMulmoErrorMessage(err, capturedErrors.getStore() ?? []), { cause: err });\n }\n });\n}\n","// Transport-free cores for every mulmoScript operation, moved from\n// MulmoClaude's `server/api/routes/mulmo-script-ops.ts` in phase 3 so the\n// SAME implementation backs every host surface:\n//\n// - MulmoClaude's legacy REST routes (kept for wire compat),\n// - the generic plugin dispatch (see `./dispatch`) that the package View\n// calls in both MulmoClaude and MulmoTerminal.\n//\n// Every op returns an `OpResult` — failures are data (`code` preserves the\n// HTTP mapping for REST adapters) and never exceptions. Generation ops\n// publish start/finish through the instance's edge-triggered tracker, which\n// fans out via the injected `backend.onGenerationEvent` (session channels,\n// UI pubsub — host-specific) and backs the View's mount-time\n// `pendingGenerations` snapshot.\n//\n// Host-specific transport is injected via `MulmoScriptServerBackend`; the\n// mulmocast orchestration, realpath containment, and generation-state\n// tracking all live here.\n\nimport { existsSync, mkdirSync, realpathSync, statSync, unlinkSync } from \"fs\";\nimport path from \"path\";\nimport {\n getFileObject,\n initializeContextFromFiles,\n generateBeatImage,\n getBeatPngImagePath,\n generateBeatAudio,\n getBeatAudioPathOrUrl,\n getBeatAnimatedVideoPath,\n getBeatMoviePaths,\n generateReferenceImage,\n getReferenceImagePath,\n images,\n audio,\n movie,\n movieFilePath,\n pdf,\n pdfFilePath,\n setGraphAILogger,\n addSessionProgressCallback,\n removeSessionProgressCallback,\n} from \"mulmocast\";\nimport type { MulmoBeat, MulmoImagePromptMedia, MulmoStudioContext } from \"@mulmocast/types\";\nimport type { MulmoScriptGenerationEvent } from \"../core/contract\";\nimport { normalizeStoryPath } from \"../core/paths\";\nimport { errorText, fileToDataUri, resolveWithinRoot, stripDataUri } from \"./support\";\nimport { enableGraphAIErrorCapture, setMulmoErrorCaptureLogger, withMulmoErrorCapture } from \"./mulmoErrorCapture\";\nimport type {\n GenerateOpArgs,\n MovieGenerationResult,\n MovieProgressEvent,\n MulmoScriptServerBackend,\n MulmoScriptServerLog,\n OpFailure,\n OpResult,\n PdfGenerationResult,\n} from \"./types\";\n\ntype GenerationKind = MulmoScriptGenerationEvent[\"kind\"];\n\n// We pin pdfMode=\"slide\" + pdfSize=\"a4\" — that's the configured default\n// for the storyboard editor; mulmocast's other modes (talk / handout /\n// letter) stay reachable via the CLI for power users. (#1614)\nexport const PDF_MODE = \"slide\" as const;\nexport const PDF_SIZE = \"a4\" as const;\n\nfunction opBadRequest(error: string): OpFailure {\n return { ok: false, code: \"bad_request\", error };\n}\n\nfunction opNotFound(error: string): OpFailure {\n return { ok: false, code: \"not_found\", error };\n}\n\nfunction opServerError(error: string): OpFailure {\n return { ok: false, code: \"server_error\", error };\n}\n\nconst NOOP_LOG: MulmoScriptServerLog = { info: () => {}, warn: () => {}, error: () => {} };\n\n// Helper: build mulmo context for a story file. The explicit return\n// annotation keeps declaration emit portable — the inferred type would\n// reference mulmocast's internal usage-collector path.\nexport async function buildContext(absoluteFilePath: string, force = false): Promise<MulmoStudioContext | null | undefined> {\n // setGraphAILogger(false) silences GraphAI's chatty info/debug output\n // but also its error level — re-enable error capture so a failed\n // generation surfaces the real provider error, not just mulmocast's\n // generic \"generate error\" wrapper.\n setGraphAILogger(false);\n enableGraphAIErrorCapture();\n const files = getFileObject({\n file: absoluteFilePath,\n basedir: path.dirname(absoluteFilePath),\n grouped: true,\n });\n return initializeContextFromFiles(files, true, force);\n}\n\n// Awaited context type used by every op that calls buildContext.\nexport type StoryContext = NonNullable<Awaited<ReturnType<typeof buildContext>>>;\n\nexport interface RunStoryOpDeps {\n resolveStory?: (filePath: string) => { ok: true; absolutePath: string } | OpFailure;\n buildContext?: (absoluteFilePath: string, force?: boolean) => Promise<StoryContext | undefined>;\n}\n\nexport interface RunStoryOpOptions<T> {\n force?: boolean;\n /**\n * Op-specific tag included in the failure log so dashboards can\n * distinguish which op is failing (e.g. `\"generate-beat-audio\"`).\n * Falls back to a generic `\"op failed\"` entry when omitted.\n */\n operation?: string;\n /**\n * Soft-fail override for `buildContext` returning undefined. Some\n * ops (e.g. `beatAudio`) historically returned a 200 `{ audio: null }`\n * in that case so the frontend can silently retry. If provided, this\n * callback returns the fallback result instead of the default\n * server_error \"Failed to initialize mulmo context\".\n */\n onContextMissing?: () => OpResult<T>;\n}\n\n// Map each beat to its array index, keyed by beat.id (falling back to\n// a synthetic `__index__<n>` for id-less beats). Shared by the movie\n// and PDF pipelines to translate mulmocast's per-beat progress events\n// (which carry the beat id) back into an index the UI can address.\nexport function buildBeatIdIndex(beats: MulmoBeat[]): Map<string, number> {\n const idToIndex = new Map<string, number>();\n beats.forEach((beat, index) => {\n const key = beat.id ?? `__index__${index}`;\n idToIndex.set(key, index);\n });\n return idToIndex;\n}\n\n/** Map identity for the in-flight tracker. JSON array keeps the three\n * fields unambiguous (a human-visible delimiter could collide). */\nfunction generationMapKey(kind: GenerationKind, filePath: string, key: string): string {\n return JSON.stringify([kind, filePath, key]);\n}\n\n/**\n * Build the per-host mulmoScript server ops instance. One instance per\n * process — it owns the in-flight movie/PDF dedup sets and the\n * generation-state tracker, and binds the injected host backend.\n */\nexport function createMulmoScriptServerOps(backend: MulmoScriptServerBackend) {\n const log = backend.log ?? NOOP_LOG;\n setMulmoErrorCaptureLogger(log);\n const storiesDir = path.resolve(backend.storiesDir);\n\n // ── Story path infrastructure ─────────────────────────────────\n\n // The download / status ops expect \"stories/<rel>\" (historical\n // convention, independent of the on-disk location) — the wire format\n // every endpoint keys on. Relativize against the REALPATH root when it\n // resolves: with a symlinked stories dir, mulmocast returns output\n // paths under the link's target, and relativizing against the link\n // itself would produce a traversal-like \"stories/../../…\" ref that\n // resolveStory then rejects (CodeRabbit on #2137).\n function toStoryRef(absolutePath: string): string {\n const root = ensureStoriesReal() ?? storiesDir;\n const rel = path.relative(root, absolutePath).split(path.sep).join(\"/\");\n return rel ? `stories/${rel}` : \"stories\";\n }\n\n // Lazily realpath the stories dir on first use. We can't realpath at\n // instance creation because the directory may not exist yet (it's\n // created on demand by the save route). The cache is invalidated\n // never — once the dir exists, its realpath is stable.\n let storiesRealCache: string | null = null;\n function ensureStoriesReal(): string | null {\n if (storiesRealCache) return storiesRealCache;\n try {\n mkdirSync(storiesDir, { recursive: true });\n storiesRealCache = realpathSync(storiesDir);\n return storiesRealCache;\n } catch {\n return null;\n }\n }\n\n /**\n * Resolve and validate a stories wire path to its absolute realpath.\n *\n * Uses the realpath-based resolveWithinRoot helper to defeat\n * symlink-based escapes. Callers pass wire paths like\n * \"stories/foo.json\" or \"stories/__movies__/bar.mp4\". We strip the\n * leading \"stories/\" segment and resolve the remainder against the\n * realpath of the stories directory itself — this works whether\n * stories/ is a regular directory or a legitimate symlink to another\n * location. ENOENT and traversal are distinguished (404 vs 400).\n */\n function resolveStory(filePath: string): { ok: true; absolutePath: string } | OpFailure {\n const storiesReal = ensureStoriesReal();\n if (!storiesReal) {\n return opServerError(\"stories directory not available\");\n }\n // Reject absolute paths and parent traversal at the syntactic\n // level — defense in depth on top of the realpath check below.\n if (path.isAbsolute(filePath)) {\n return opBadRequest(\"Invalid filePath\");\n }\n // Accept the workspace-relative spelling \"artifacts/stories/<rel>\"\n // the tool description historically taught (the wire form was truly\n // workspace-relative before the stories dir moved under artifacts/\n // in #284) by reducing it to the canonical \"stories/<rel>\".\n const ARTIFACTS_STORIES = \"artifacts/stories\";\n const wirePath = filePath === ARTIFACTS_STORIES || filePath.startsWith(`${ARTIFACTS_STORIES}/`) ? filePath.slice(\"artifacts/\".length) : filePath;\n // Strip the optional \"stories/\" prefix so the remainder is a path\n // relative to storiesReal. Accepts both \"stories/foo.json\" (the\n // canonical caller convention) and bare \"foo.json\".\n const STORIES_PREFIX = `stories${path.sep}`;\n const relFromStories =\n wirePath === \"stories\" ? \"\" : wirePath.startsWith(STORIES_PREFIX) || wirePath.startsWith(\"stories/\") ? wirePath.slice(\"stories/\".length) : wirePath;\n // A base path with no remainder (\"stories\", \"artifacts/stories\",\n // trailing-slash variants) would resolve to the stories directory\n // itself and hand downstream ops a directory where they expect a\n // file — reject it, mirroring normalizeStoryPath's non-empty rule.\n if (relFromStories === \"\") {\n return opBadRequest(\"Invalid filePath\");\n }\n // resolveWithinRoot enforces both the realpath boundary AND\n // existence; ENOENT and traversal both produce null. Distinguish\n // them via a follow-up existsSync so 404 vs 400 stays accurate —\n // but only consult the filesystem for lexically in-root candidates:\n // a traversal path must never touch the fs (and gets a uniform\n // bad_request so responses don't leak existence outside the root).\n const resolved = resolveWithinRoot(storiesReal, relFromStories);\n if (!resolved) {\n const candidate = path.resolve(storiesReal, relFromStories);\n const inRoot = candidate === storiesReal || candidate.startsWith(storiesReal + path.sep);\n if (inRoot && !existsSync(candidate)) {\n return opNotFound(`File not found: ${filePath}`);\n }\n return opBadRequest(\"Invalid filePath\");\n }\n return { ok: true, absolutePath: resolved };\n }\n\n /**\n * Realpath containment pre-guard for wire paths handed to the phase-1\n * core's save/reopen/update executes. The core's own path guard is\n * lexical (it runs against the generic FileOps, whose read/write follows\n * symlinks), so hosts re-assert the realpath boundary here before\n * invoking it — a symlink planted below the stories dir can't read or\n * write outside the tree (Codex P1 on MulmoClaude#2133).\n *\n * Returns null when `filePath` isn't a non-empty string — shape\n * validation (including the script-vs-filePath mode check) belongs to\n * the core.\n */\n function guardStoryWirePath(filePath: unknown): OpFailure | null {\n if (typeof filePath !== \"string\" || filePath === \"\") return null;\n const resolved = resolveStory(filePath);\n return resolved.ok ? null : resolved;\n }\n\n // mulmocast shells out to ffmpeg for movie / beat rendering. When the\n // host's probe reports it absent, intercept with a clear failure\n // instead of letting the library throw an opaque spawn ENOENT\n // mid-pipeline. `undefined` means the probe hasn't completed — assume\n // available so a brief startup window never blocks a render.\n function ffmpegGuard(): OpFailure | null {\n if (backend.isFfmpegAvailable?.() === false) {\n return {\n ok: false,\n code: \"unavailable\",\n error: \"ffmpeg is not installed — movie and beat rendering are unavailable. Install ffmpeg and restart the server.\",\n };\n }\n return null;\n }\n\n // ── Generation tracker (edge-triggered) ───────────────────────\n\n // Refcounted: two concurrent generations with the same kind/filePath/key\n // (e.g. the same beat rendered from two tabs) must not have the first\n // completion erase the second run's snapshot entry, and only the first\n // start / LAST finish reach the host channels — an early completion\n // can't clear subscribers' spinners while a duplicate run is active.\n // A finish with no tracked start (the movie/PDF pipelines' per-beat\n // completion pulses) always publishes.\n const inFlightGenerations = new Map<string, { kind: GenerationKind; filePath: string; key: string; count: number }>();\n\n /** Tracker state and events key on the canonical `stories/<rel>` wire\n * form: subscribers (the View's pubsub filter, `pendingGenerations`\n * callers) match by exact string, so the accepted alias spellings\n * (`artifacts/stories/<rel>`, bare `<rel>`) must collapse to the same\n * key as the canonical one (Codex P2 on #2139). Untrusted spellings\n * pass through unchanged — they never resolve, so they can't collide. */\n function canonicalWirePath(filePath: string): string {\n return normalizeStoryPath(filePath) ?? filePath;\n }\n\n function publishGeneration(chatSessionId: string | undefined, kind: GenerationKind, filePath: string, key: string, finished: boolean, error?: string): void {\n const wirePath = canonicalWirePath(filePath);\n const mapKey = generationMapKey(kind, wirePath, key);\n const existing = inFlightGenerations.get(mapKey);\n if (finished) {\n if (existing && existing.count > 1) {\n existing.count -= 1;\n return; // a duplicate run is still active — suppress the early finish\n }\n inFlightGenerations.delete(mapKey);\n } else {\n if (existing) {\n existing.count += 1;\n return; // already reported as started\n }\n inFlightGenerations.set(mapKey, { kind, filePath: wirePath, key, count: 1 });\n }\n const event: MulmoScriptGenerationEvent = { kind, filePath: wirePath, key, done: finished, ...(error ? { error } : {}) };\n backend.onGenerationEvent?.(chatSessionId, event);\n }\n\n /** Snapshot of generations currently in flight for one script — the\n * View's mount-time catch-up, filtered to its wire `filePath`. */\n function pendingGenerations(filePath: string): MulmoScriptGenerationEvent[] {\n const wirePath = canonicalWirePath(filePath);\n return [...inFlightGenerations.values()]\n .filter((entry) => entry.filePath === wirePath)\n .map(({ kind, key }) => ({ kind, filePath: wirePath, key, done: false }));\n }\n\n // ── Op scaffolding ────────────────────────────────────────────\n\n /**\n * Shared scaffolding for mulmoScript ops. Resolves the wire filePath,\n * builds the mulmo context, and folds unexpected handler errors into a\n * server_error failure (with a warn breadcrumb). Accepts a `deps` param\n * so unit tests can inject fakes without the full mulmocast stack.\n */\n async function runStoryOp<T>(\n filePath: string,\n options: RunStoryOpOptions<T>,\n handler: (ctx: { absoluteFilePath: string; context: StoryContext }) => Promise<OpResult<T>>,\n deps: RunStoryOpDeps = {},\n ): Promise<OpResult<T>> {\n const resolver = deps.resolveStory ?? resolveStory;\n const build = deps.buildContext ?? buildContext;\n const resolved = resolver(filePath);\n if (!resolved.ok) return resolved;\n try {\n const context = await build(resolved.absolutePath, options.force ?? false);\n if (!context) {\n if (options.onContextMissing) return options.onContextMissing();\n return opServerError(\"Failed to initialize mulmo context\");\n }\n // withMulmoErrorCapture appends the underlying provider error\n // (missing API key, quota, …) to any mulmocast failure, which\n // otherwise reaches the client as a generic \"generate error\".\n return await withMulmoErrorCapture(() => handler({ absoluteFilePath: resolved.absolutePath, context }));\n } catch (err) {\n // Log every op failure at warn so operators get a breadcrumb even\n // when the op doesn't wrap its own try/catch.\n log.warn(\"op failed\", {\n ...(options.operation ? { operation: options.operation } : {}),\n filePath,\n error: errorText(err),\n });\n return opServerError(errorText(err));\n }\n }\n\n // ── Probe ops ─────────────────────────────────────────────────\n\n async function beatImageOp(filePath: string, beatIndex: number): Promise<OpResult<{ image: string | null }>> {\n return runStoryOp<{ image: string | null }>(filePath, { operation: \"beat-image\" }, async ({ context }) => {\n const { imagePath } = getBeatPngImagePath(context, beatIndex);\n if (!existsSync(imagePath)) return { ok: true, image: null };\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n }\n\n // beatAudio is a probe — the frontend polls it expecting `{ audio: null }`\n // when nothing has been generated yet. Override the default\n // server_error-on-context-missing so the soft-fail contract is preserved.\n async function beatAudioOp(filePath: string, beatIndex: number): Promise<OpResult<{ audio: string | null }>> {\n return runStoryOp<{ audio: string | null }>(\n filePath,\n { operation: \"beat-audio\", onContextMissing: () => ({ ok: true, audio: null }) },\n async ({ context }) => {\n const beat = context.studio.script.beats[beatIndex];\n const audioPath = getBeatAudioPathOrUrl(beat.text ?? \"\", context, beat, context.lang);\n if (!audioPath || !existsSync(audioPath)) return { ok: true, audio: null };\n return { ok: true, audio: await fileToDataUri(audioPath, \"audio/mpeg\") };\n },\n );\n }\n\n // Probe for a beat's generated video clip. Preference order mirrors the\n // movie-assembly pipeline's \"most processed wins\": lip-synced > with\n // sound effect > raw movie clip > animated html_tailwind render. The\n // response is the \"stories/…\" wire path so the client can stream it\n // through the host's authenticated media download.\n async function beatMovieOp(filePath: string, beatIndex: number): Promise<OpResult<{ moviePath: string | null }>> {\n return runStoryOp<{ moviePath: string | null }>(filePath, { operation: \"beat-movie\" }, async ({ context }) => {\n const { movieFile, soundEffectFile, lipSyncFile } = getBeatMoviePaths(context, beatIndex);\n const candidates = [lipSyncFile, soundEffectFile, movieFile, getBeatAnimatedVideoPath(context, beatIndex)];\n const existing = candidates.find((candidate) => existsSync(candidate));\n return { ok: true, moviePath: existing ? toStoryRef(existing) : null };\n });\n }\n\n async function characterImageOp(filePath: string, key: string): Promise<OpResult<{ image: string | null }>> {\n return runStoryOp<{ image: string | null }>(filePath, { operation: \"character-image\" }, async ({ context }) => {\n const imagePath = getReferenceImagePath(context, key, \"png\");\n if (!existsSync(imagePath)) return { ok: true, image: null };\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n }\n\n /** Shared \"output exists and is newer than the source script\" gate for\n * movie / PDF status. A stale artifact (script edited after it was\n * generated) reports null so the UI re-offers the Generate button. */\n function freshOutputRef(outputPath: string, absoluteFilePath: string): string | null {\n if (!existsSync(outputPath)) return null;\n const outputMtime = statSync(outputPath).mtimeMs;\n const sourceMtime = statSync(absoluteFilePath).mtimeMs;\n if (outputMtime < sourceMtime) return null;\n return toStoryRef(outputPath);\n }\n\n async function movieStatusOp(filePath: string): Promise<OpResult<{ moviePath: string | null }>> {\n return runStoryOp(\n filePath,\n { operation: \"movie-status\", onContextMissing: () => ({ ok: true, moviePath: null }) },\n async ({ absoluteFilePath, context }) => ({ ok: true, moviePath: freshOutputRef(movieFilePath(context), absoluteFilePath) }),\n );\n }\n\n async function pdfStatusOp(filePath: string): Promise<OpResult<{ pdfPath: string | null }>> {\n return runStoryOp(filePath, { operation: \"pdf-status\", onContextMissing: () => ({ ok: true, pdfPath: null }) }, async ({ absoluteFilePath, context }) => ({\n ok: true,\n pdfPath: freshOutputRef(pdfFilePath(context, PDF_MODE), absoluteFilePath),\n }));\n }\n\n // ── Generation ops ────────────────────────────────────────────\n\n async function renderBeatOp(args: Required<Pick<GenerateOpArgs, \"filePath\" | \"beatIndex\">> & GenerateOpArgs): Promise<OpResult<{ image: string }>> {\n const { filePath, beatIndex, force, chatSessionId } = args;\n const ffmpeg = ffmpegGuard();\n if (ffmpeg) return ffmpeg;\n\n const mapKey = String(beatIndex);\n publishGeneration(chatSessionId, \"beatImage\", filePath, mapKey, false);\n let genError: string | undefined;\n try {\n const result = await runStoryOp<{ image: string }>(filePath, { force, operation: \"render-beat\" }, async ({ context }) => {\n await generateBeatImage({\n index: beatIndex,\n context,\n args: force ? { forceImage: true } : undefined,\n });\n const { imagePath } = getBeatPngImagePath(context, beatIndex);\n if (!existsSync(imagePath)) {\n return opServerError(\"Image was not generated\");\n }\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n if (!result.ok) genError = result.error;\n return result;\n } finally {\n publishGeneration(chatSessionId, \"beatImage\", filePath, mapKey, true, genError);\n }\n }\n\n async function generateBeatAudioOp(args: Required<Pick<GenerateOpArgs, \"filePath\" | \"beatIndex\">> & GenerateOpArgs): Promise<OpResult<{ audio: string }>> {\n const { filePath, beatIndex, force, chatSessionId } = args;\n const mapKey = String(beatIndex);\n publishGeneration(chatSessionId, \"beatAudio\", filePath, mapKey, false);\n let genError: string | undefined;\n try {\n const result = await runStoryOp<{ audio: string }>(filePath, { force, operation: \"generate-beat-audio\" }, async ({ context }) => {\n await generateBeatAudio(beatIndex, context, {\n settings: process.env as Record<string, string>,\n } as Parameters<typeof generateBeatAudio>[2]);\n\n const beat = context.studio.script.beats[beatIndex];\n const audioPath = context.studio.beats[beatIndex]?.audioFile ?? getBeatAudioPathOrUrl(beat.text ?? \"\", context, beat, context.lang);\n\n if (!audioPath || !existsSync(audioPath)) {\n // Logic-flow failure (not an exception) — emit a targeted\n // log. Don't write raw `beat.text` into persistent logs —\n // it's free-form user content and can contain sensitive\n // data.\n log.error(\"audio was not generated\", {\n beatIndex,\n audioPath,\n exists: audioPath ? existsSync(audioPath) : false,\n beatTextLength: typeof beat?.text === \"string\" ? beat.text.length : 0,\n audioFilePresent: Boolean(context.studio.beats[beatIndex]?.audioFile),\n });\n return opServerError(\"Audio was not generated\");\n }\n return { ok: true, audio: await fileToDataUri(audioPath, \"audio/mpeg\") };\n });\n if (!result.ok) genError = result.error;\n return result;\n } finally {\n publishGeneration(chatSessionId, \"beatAudio\", filePath, mapKey, true, genError);\n }\n }\n\n async function renderCharacterOp(args: Required<Pick<GenerateOpArgs, \"filePath\" | \"key\">> & GenerateOpArgs): Promise<OpResult<{ image: string }>> {\n const { filePath, key, force, chatSessionId } = args;\n publishGeneration(chatSessionId, \"characterImage\", filePath, key, false);\n let genError: string | undefined;\n try {\n const result = await runStoryOp<{ image: string }>(filePath, { force, operation: \"render-character\" }, async ({ context }) => {\n // `imageEntries` (not `images`) to avoid shadowing mulmocast's\n // imported `images()` pipeline stage.\n const imageEntries = context.studio.script.imageParams?.images ?? {};\n const imageEntry = imageEntries[key];\n if (!imageEntry || imageEntry.type !== \"imagePrompt\") {\n return opBadRequest(`No imagePrompt entry for key: ${key}`);\n }\n\n const index = Object.keys(imageEntries).indexOf(key);\n const imagePath = getReferenceImagePath(context, key, \"png\");\n mkdirSync(path.dirname(imagePath), { recursive: true });\n\n await generateReferenceImage({\n context,\n key,\n index,\n image: imageEntry as MulmoImagePromptMedia,\n force,\n });\n if (!existsSync(imagePath)) {\n return opServerError(\"Character image was not generated\");\n }\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n if (!result.ok) genError = result.error;\n return result;\n } finally {\n publishGeneration(chatSessionId, \"characterImage\", filePath, key, true, genError);\n }\n }\n\n // ── Upload ops ────────────────────────────────────────────────\n\n async function uploadBeatImageOp(filePath: string, beatIndex: number, imageData: string): Promise<OpResult<{ image: string }>> {\n return runStoryOp<{ image: string }>(filePath, { operation: \"upload-beat-image\" }, async ({ context }) => {\n const { imagePath } = getBeatPngImagePath(context, beatIndex);\n // writeFileAtomic creates parent dirs and prevents a half-\n // written PNG from surviving a crash mid-write (#881 v2).\n const base64 = stripDataUri(imageData);\n await backend.writeFileAtomic(imagePath, Buffer.from(base64, \"base64\"));\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n }\n\n async function uploadCharacterImageOp(filePath: string, key: string, imageData: string): Promise<OpResult<{ image: string }>> {\n return runStoryOp<{ image: string }>(filePath, { operation: \"upload-character-image\" }, async ({ context }) => {\n const imagePath = getReferenceImagePath(context, key, \"png\");\n const base64 = stripDataUri(imageData);\n await backend.writeFileAtomic(imagePath, Buffer.from(base64, \"base64\"));\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n }\n\n // ── Movie / PDF pipelines ─────────────────────────────────────\n\n // Per-instance dedup so a foreground call (SSE route or long-held\n // dispatch) and a fire-and-forget background call can't race on the same\n // script. Keyed by the realpath (absoluteFilePath) so two different wire\n // spellings of the same file still collide. Process-local — a\n // multi-process deployment would need an external lock; out of scope.\n const inFlightMovies = new Set<string>();\n\n // Same dedup model as inFlightMovies, scoped to PDF generation\n // (#1614). PDFs and movies don't share the lock — they write to\n // different output files and can safely run in parallel.\n const inFlightPdfs = new Set<string>();\n\n // Shared core for the SSE-streaming route, the long-held dispatch op, and\n // the fire-and-forget background path triggered by `autoGenerateMovie`.\n // Builds the mulmo context, runs audio→images→movie, and reports\n // per-beat progress through the supplied callback. Throws on\n // unexpected pipeline errors; returns a structured failure when the\n // pipeline runs to completion but the output file is missing.\n async function runMovieGeneration(absoluteFilePath: string, onProgressEvent: (event: MovieProgressEvent) => void): Promise<MovieGenerationResult> {\n return withMulmoErrorCapture(() => runMoviePipeline(absoluteFilePath, onProgressEvent));\n }\n\n async function runMoviePipeline(absoluteFilePath: string, onProgressEvent: (event: MovieProgressEvent) => void): Promise<MovieGenerationResult> {\n const context = await buildContext(absoluteFilePath);\n if (!context) return { ok: false, error: \"Failed to initialize mulmo context\" };\n\n const idToIndex = buildBeatIdIndex(context.studio.script.beats as MulmoBeat[]);\n\n // Known limitation: addSessionProgressCallback is global, so when two\n // movie generations for *different* scripts run concurrently, both\n // closures are invoked for every beat event and rely on idToIndex to\n // filter out the other run's events. That filter is reliable only\n // when each beat carries an explicit `id`. Beats without one fall\n // back to \"__index__${index}\", and identical fallback ids across\n // scripts collide → progress meant for script A surfaces on script B.\n // Fixing this properly needs mulmocast to attach a per-run identifier\n // to its progress events (or a global serialization gate); tracked\n // separately.\n const onProgress = (event: { kind: string; sessionType: string; id?: string; inSession: boolean }) => {\n if (event.kind !== \"beat\" || event.inSession || event.id === undefined) return;\n const beatIndex = idToIndex.get(event.id);\n if (beatIndex === undefined) return;\n if (event.sessionType !== \"image\" && event.sessionType !== \"audio\") return;\n onProgressEvent({ kind: event.sessionType, beatIndex });\n };\n\n addSessionProgressCallback(onProgress);\n try {\n // Order matters: audio() must run before images(). For html_tailwind\n // beats with `animation: true`, mulmocast only emits the per-beat\n // `_animated.mp4` when the beat's duration is already known (see\n // processHtmlTailwindAnimated in mulmocast). Durations are populated\n // by audio(), so running images() first leaves the .mp4 files\n // missing and movie() then fails in validateBeatSource.\n const audioContext = await audio(context);\n const imagesContext = await images(audioContext);\n await movie(imagesContext);\n\n const outputPath = movieFilePath(imagesContext);\n if (!existsSync(outputPath)) return { ok: false, error: \"Movie was not generated\" };\n return { ok: true, outputPath };\n } finally {\n removeSessionProgressCallback(onProgress);\n }\n }\n\n /**\n * Long-held foreground movie generation (the package View's\n * `generateMovie` dispatch). Resolves when the whole pipeline finishes.\n * Per-beat completions are mirrored to the generation channels so the\n * initiating View (and any other mounted View) reloads assets off disk\n * as they land — the successor of the SSE per-beat events.\n */\n async function generateMovieOp(filePath: string, chatSessionId: string | undefined): Promise<OpResult<{ moviePath: string }>> {\n const ffmpeg = ffmpegGuard();\n if (ffmpeg) return ffmpeg;\n const resolved = resolveStory(filePath);\n if (!resolved.ok) return resolved;\n const absoluteFilePath = resolved.absolutePath;\n\n if (inFlightMovies.has(absoluteFilePath)) {\n return opBadRequest(\"Movie generation is already in progress for this script\");\n }\n\n inFlightMovies.add(absoluteFilePath);\n publishGeneration(chatSessionId, \"movie\", filePath, \"\", false);\n let genError: string | undefined;\n try {\n const result = await runMovieGeneration(absoluteFilePath, (event) => {\n const eventKind = event.kind === \"image\" ? \"beatImage\" : \"beatAudio\";\n publishGeneration(chatSessionId, eventKind, filePath, String(event.beatIndex), true);\n });\n if (!result.ok) {\n genError = result.error;\n return opServerError(result.error);\n }\n return { ok: true, moviePath: toStoryRef(result.outputPath) };\n } catch (err) {\n genError = errorText(err);\n return opServerError(genError);\n } finally {\n inFlightMovies.delete(absoluteFilePath);\n publishGeneration(chatSessionId, \"movie\", filePath, \"\", true, genError);\n }\n }\n\n function triggerAutoBackgroundMovie(absoluteFilePath: string, wireFilePath: string, chatSessionId: string | undefined): void {\n if (inFlightMovies.has(absoluteFilePath)) return;\n inFlightMovies.add(absoluteFilePath);\n void runBackgroundMovieGeneration(absoluteFilePath, wireFilePath, chatSessionId);\n }\n\n // Detached movie generation. Reports progress through the generation\n // channels the View watches — so a user opening the canvas\n // mid-generation sees spinners, and a user opening it after completion\n // sees the finished movie loaded from disk by the View's normal\n // mount-time path. Errors are persisted to a `<filename>.error.txt`\n // sidecar next to the script (no synchronous client to alert); any\n // stale sidecar from a previous run is cleared on each new attempt.\n // Triggered server-side from the unified save route when the caller\n // passes `autoGenerateMovie: true`.\n async function runBackgroundMovieGeneration(absoluteFilePath: string, wireFilePath: string, chatSessionId: string | undefined): Promise<void> {\n const errorSidecarPath = `${absoluteFilePath}.error.txt`;\n // Clear stale error from a previous failed run before starting; if it\n // doesn't exist that's fine. Catch any unexpected fs errors silently —\n // the worst case is the user sees an out-of-date error file later.\n try {\n unlinkSync(errorSidecarPath);\n } catch {\n // intentional: ENOENT is the common case, others non-fatal\n }\n\n publishGeneration(chatSessionId, \"movie\", wireFilePath, \"\", false);\n let genError: string | undefined;\n try {\n const result = await runMovieGeneration(absoluteFilePath, (event) => {\n // Mirror per-beat completions through the generation channels so\n // subscribed Views reload the asset off disk. We fire start+finish\n // in two ticks — `setImmediate` lets the session SSE writer flush\n // the start event before the finish removes the entry, otherwise\n // Vue's batched reactivity could see a net \"no change\" and skip\n // the reload.\n const eventKind = event.kind === \"image\" ? \"beatImage\" : \"beatAudio\";\n const key = String(event.beatIndex);\n publishGeneration(chatSessionId, eventKind, wireFilePath, key, false);\n setImmediate(() => publishGeneration(chatSessionId, eventKind, wireFilePath, key, true));\n });\n\n if (!result.ok) {\n genError = result.error;\n await writeErrorSidecar(errorSidecarPath, result.error);\n log.warn(\"background movie generation failed\", { filePath: wireFilePath, error: result.error });\n return;\n }\n log.info(\"background movie generation done\", {\n filePath: wireFilePath,\n outputPath: result.outputPath,\n });\n } catch (err) {\n genError = errorText(err);\n await writeErrorSidecar(errorSidecarPath, genError);\n log.error(\"background movie generation crashed\", { filePath: wireFilePath, error: genError });\n } finally {\n inFlightMovies.delete(absoluteFilePath);\n publishGeneration(chatSessionId, \"movie\", wireFilePath, \"\", true, genError);\n }\n }\n\n // Atomic write so a crash mid-write can't leave a truncated sidecar.\n async function writeErrorSidecar(errorSidecarPath: string, message: string): Promise<void> {\n try {\n await backend.writeFileAtomic(errorSidecarPath, message);\n } catch (writeErr) {\n log.error(\"failed to write error sidecar\", {\n errorSidecarPath,\n error: errorText(writeErr),\n });\n }\n }\n\n // ── PDF (#1614) ───────────────────────────────────────────────\n\n // Shared core for the SSE-streaming route and the long-held dispatch op.\n // Mirrors the movie pipeline's per-beat progress reporting so the UI can\n // light spinners during the image pass; the PDF action itself doesn't\n // emit progress events, so only image events are forwarded. Returns a\n // structured failure when the pipeline completes but the output file is\n // missing.\n async function runPdfGeneration(context: StoryContext, onImageBeatDone: (beatIndex: number) => void): Promise<PdfGenerationResult> {\n return withMulmoErrorCapture(() => runPdfPipeline(context, onImageBeatDone));\n }\n\n async function runPdfPipeline(context: StoryContext, onImageBeatDone: (beatIndex: number) => void): Promise<PdfGenerationResult> {\n const idToIndex = buildBeatIdIndex(context.studio.script.beats as MulmoBeat[]);\n const onProgress = (event: { kind: string; sessionType: string; id?: string; inSession: boolean }) => {\n if (event.kind !== \"beat\" || event.inSession || event.id === undefined) return;\n const beatIndex = idToIndex.get(event.id);\n if (beatIndex === undefined) return;\n if (event.sessionType !== \"image\") return;\n onImageBeatDone(beatIndex);\n };\n addSessionProgressCallback(onProgress);\n try {\n const imagesContext = await images(context);\n await pdf(imagesContext, PDF_MODE, PDF_SIZE);\n const outputPath = pdfFilePath(imagesContext, PDF_MODE);\n if (!existsSync(outputPath)) return { ok: false, error: \"PDF was not generated\" };\n return { ok: true, outputPath };\n } finally {\n removeSessionProgressCallback(onProgress);\n }\n }\n\n /** Long-held foreground PDF generation (the package View's `generatePdf`\n * dispatch) — the PDF sibling of `generateMovieOp`. */\n async function generatePdfOp(filePath: string, chatSessionId: string | undefined): Promise<OpResult<{ pdfPath: string }>> {\n const ffmpeg = ffmpegGuard();\n if (ffmpeg) return ffmpeg;\n const resolved = resolveStory(filePath);\n if (!resolved.ok) return resolved;\n const absoluteFilePath = resolved.absolutePath;\n\n if (inFlightPdfs.has(absoluteFilePath)) {\n return opBadRequest(\"PDF generation is already in progress for this script\");\n }\n\n inFlightPdfs.add(absoluteFilePath);\n publishGeneration(chatSessionId, \"pdf\", filePath, \"\", false);\n let genError: string | undefined;\n try {\n const context = await buildContext(absoluteFilePath);\n if (!context) {\n genError = \"Failed to initialize mulmo context\";\n return opServerError(genError);\n }\n const result = await runPdfGeneration(context, (beatIndex) => {\n publishGeneration(chatSessionId, \"beatImage\", filePath, String(beatIndex), true);\n });\n if (!result.ok) {\n genError = result.error;\n return opServerError(result.error);\n }\n return { ok: true, pdfPath: toStoryRef(result.outputPath) };\n } catch (err) {\n genError = errorText(err);\n return opServerError(genError);\n } finally {\n inFlightPdfs.delete(absoluteFilePath);\n publishGeneration(chatSessionId, \"pdf\", filePath, \"\", true, genError);\n }\n }\n\n return {\n backend,\n toStoryRef,\n resolveStory,\n guardStoryWirePath,\n ffmpegGuard,\n runStoryOp,\n publishGeneration,\n pendingGenerations,\n beatImageOp,\n beatAudioOp,\n beatMovieOp,\n characterImageOp,\n movieStatusOp,\n pdfStatusOp,\n renderBeatOp,\n generateBeatAudioOp,\n renderCharacterOp,\n uploadBeatImageOp,\n uploadCharacterImageOp,\n inFlightMovies,\n inFlightPdfs,\n runMovieGeneration,\n runPdfGeneration,\n generateMovieOp,\n generatePdfOp,\n triggerAutoBackgroundMovie,\n };\n}\n\nexport type MulmoScriptServerOps = ReturnType<typeof createMulmoScriptServerOps>;\n","// The mulmoScript dispatch router, moved from MulmoClaude's\n// `server/plugins/mulmoscript-builtin.ts` in phase 3 so every host serves\n// the package View's `useRuntime().dispatch({ kind, … })` calls with the\n// SAME kind routing and validation. Hosts register the returned handler on\n// their dispatch channel (MulmoClaude: `registerBuiltinDispatch`;\n// MulmoTerminal: its `/api/plugin` interception).\n//\n// Response contract: every kind resolves to an `{ ok: … }` envelope (see\n// `../core/contract.ts`) — business failures are data, not thrown errors,\n// so user-facing messages stay free of transport prefixes.\n\nimport { executeMulmoScriptSave, executeUpdateBeat, executeUpdateScript, type MulmoScriptFailure } from \"../core/plugin\";\nimport type { MulmoScriptExecuteContext } from \"../core/types\";\nimport type { MulmoScriptServerOps } from \"./ops\";\nimport type { OpFailure } from \"./types\";\n\ninterface DispatchFailure {\n ok: false;\n code: \"bad_request\" | \"not_found\" | \"server_error\";\n error: string;\n}\n\nfunction fromOpFailure(failure: OpFailure): DispatchFailure {\n // \"unavailable\" (ffmpeg missing) has no slot in the contract's code\n // union — the View only reads `error`, so fold it into server_error\n // rather than widening the shared contract for one case.\n const code = failure.code === \"unavailable\" ? \"server_error\" : failure.code;\n return { ok: false, code, error: failure.error };\n}\n\nfunction fromPackageFailure(failure: MulmoScriptFailure): DispatchFailure {\n return { ok: false, code: failure.code, error: failure.error };\n}\n\nfunction invalidArgs(kind: string): DispatchFailure {\n return { ok: false, code: \"bad_request\", error: `invalid arguments for mulmoScript dispatch kind \"${kind}\"` };\n}\n\nfunction str(value: unknown): string | undefined {\n return typeof value === \"string\" && value !== \"\" ? value : undefined;\n}\n\n// Beat indexes must be non-negative integers — reject `-1` / `1.5` at the\n// dispatch boundary so invalid client input surfaces as a deterministic\n// bad_request instead of leaking into beat-indexed ops.\nfunction num(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0 ? value : undefined;\n}\n\ninterface BeatArgs {\n filePath: string;\n beatIndex: number;\n}\n\ninterface KeyArgs {\n filePath: string;\n key: string;\n}\n\n/** Pass ok results through untouched; normalize failures for the wire. */\nfunction envelope<T>(result: ({ ok: true } & T) | OpFailure): ({ ok: true } & T) | DispatchFailure {\n return result.ok ? result : fromOpFailure(result);\n}\n\nfunction beatArgs(args: Record<string, unknown>): BeatArgs | null {\n const filePath = str(args.filePath);\n const beatIndex = num(args.beatIndex);\n if (!filePath || beatIndex === undefined) return null;\n return { filePath, beatIndex };\n}\n\nfunction keyArgs(args: Record<string, unknown>): KeyArgs | null {\n const filePath = str(args.filePath);\n const key = str(args.key);\n if (!filePath || !key) return null;\n return { filePath, key };\n}\n\nconst PROBE_KINDS = new Set([\"beatImage\", \"beatAudio\", \"beatMovie\", \"characterImage\", \"movieStatus\", \"pdfStatus\"]);\nconst GENERATE_KINDS = new Set([\"renderBeat\", \"generateBeatAudio\", \"renderCharacter\", \"generateMovie\", \"generatePdf\"]);\nconst UPLOAD_KINDS = new Set([\"uploadBeatImage\", \"uploadCharacterImage\"]);\n\nexport type MulmoScriptDispatchHandler = (args: Record<string, unknown>) => Promise<unknown>;\n\n/**\n * Build the kind router over an ops instance. The save / reopen / update\n * kinds run the phase-1 core executes against the backend's artifacts\n * FileOps, guarded by the instance's realpath containment\n * (`guardStoryWirePath`) — the core's own guard is lexical.\n */\nexport function createMulmoScriptDispatchHandler(ops: MulmoScriptServerOps): MulmoScriptDispatchHandler {\n const executeContext: MulmoScriptExecuteContext = { files: { artifacts: ops.backend.artifacts } };\n\n async function saveKind(args: Record<string, unknown>): Promise<unknown> {\n const guard = ops.guardStoryWirePath(args.filePath);\n if (guard) return fromOpFailure(guard);\n const outcome = await executeMulmoScriptSave(executeContext, {\n script: args.script,\n filename: str(args.filename),\n filePath: str(args.filePath),\n });\n if (!outcome.ok) return fromPackageFailure(outcome);\n return { ok: true, script: outcome.script, filePath: outcome.filePath, message: outcome.message };\n }\n\n async function updateKind(kind: \"updateBeat\" | \"updateScript\", args: Record<string, unknown>): Promise<unknown> {\n const guard = ops.guardStoryWirePath(args.filePath);\n if (guard) return fromOpFailure(guard);\n const outcome = kind === \"updateBeat\" ? await executeUpdateBeat(executeContext, args) : await executeUpdateScript(executeContext, args);\n return outcome.ok ? { ok: true } : fromPackageFailure(outcome);\n }\n\n const STATUS_OPS = { movieStatus: ops.movieStatusOp, pdfStatus: ops.pdfStatusOp } as const;\n const BEAT_PROBE_OPS = { beatImage: ops.beatImageOp, beatAudio: ops.beatAudioOp, beatMovie: ops.beatMovieOp } as const;\n\n async function probeKind(kind: string, args: Record<string, unknown>): Promise<unknown> {\n const statusOp = STATUS_OPS[kind as keyof typeof STATUS_OPS];\n if (statusOp) {\n const filePath = str(args.filePath);\n return filePath ? envelope(await statusOp(filePath)) : invalidArgs(kind);\n }\n if (kind === \"characterImage\") {\n const parsed = keyArgs(args);\n return parsed ? envelope(await ops.characterImageOp(parsed.filePath, parsed.key)) : invalidArgs(kind);\n }\n const parsed = beatArgs(args);\n if (!parsed) return invalidArgs(kind);\n return envelope(await BEAT_PROBE_OPS[kind as keyof typeof BEAT_PROBE_OPS](parsed.filePath, parsed.beatIndex));\n }\n\n async function generateKind(kind: string, args: Record<string, unknown>): Promise<unknown> {\n const chatSessionId = str(args.chatSessionId);\n const force = args.force === true;\n if (kind === \"generateMovie\" || kind === \"generatePdf\") {\n const filePath = str(args.filePath);\n if (!filePath) return invalidArgs(kind);\n const result = kind === \"generateMovie\" ? await ops.generateMovieOp(filePath, chatSessionId) : await ops.generatePdfOp(filePath, chatSessionId);\n return envelope(result);\n }\n if (kind === \"renderCharacter\") {\n const parsed = keyArgs(args);\n return parsed ? envelope(await ops.renderCharacterOp({ ...parsed, force, chatSessionId })) : invalidArgs(kind);\n }\n const parsed = beatArgs(args);\n if (!parsed) return invalidArgs(kind);\n const result =\n kind === \"renderBeat\" ? await ops.renderBeatOp({ ...parsed, force, chatSessionId }) : await ops.generateBeatAudioOp({ ...parsed, force, chatSessionId });\n return envelope(result);\n }\n\n async function uploadKind(kind: string, args: Record<string, unknown>): Promise<unknown> {\n const imageData = str(args.imageData);\n if (!imageData) return invalidArgs(kind);\n if (kind === \"uploadCharacterImage\") {\n const parsed = keyArgs(args);\n return parsed ? envelope(await ops.uploadCharacterImageOp(parsed.filePath, parsed.key, imageData)) : invalidArgs(kind);\n }\n const parsed = beatArgs(args);\n if (!parsed) return invalidArgs(kind);\n return envelope(await ops.uploadBeatImageOp(parsed.filePath, parsed.beatIndex, imageData));\n }\n\n return async (args: Record<string, unknown>): Promise<unknown> => {\n const kind = str(args.kind);\n if (!kind) return invalidArgs(\"<missing>\");\n if (kind === \"save\") return saveKind(args);\n if (kind === \"updateBeat\" || kind === \"updateScript\") return updateKind(kind, args);\n if (PROBE_KINDS.has(kind)) return probeKind(kind, args);\n if (GENERATE_KINDS.has(kind)) return generateKind(kind, args);\n if (UPLOAD_KINDS.has(kind)) return uploadKind(kind, args);\n if (kind === \"pendingGenerations\") {\n const filePath = str(args.filePath);\n if (!filePath) return invalidArgs(kind);\n return { ok: true, pending: ops.pendingGenerations(filePath) };\n }\n return { ok: false, code: \"bad_request\", error: `unknown mulmoScript dispatch kind \"${kind}\"` };\n };\n}\n"],"mappings":"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"}
|
package/dist/style.css
CHANGED
|
@@ -1612,18 +1612,18 @@
|
|
|
1612
1612
|
}
|
|
1613
1613
|
}
|
|
1614
1614
|
|
|
1615
|
-
.bottom-bar-wrapper[data-v-
|
|
1615
|
+
.bottom-bar-wrapper[data-v-c28ee15a] {
|
|
1616
1616
|
position: relative;
|
|
1617
1617
|
flex-shrink: 0;
|
|
1618
1618
|
}
|
|
1619
|
-
.script-source[data-v-
|
|
1619
|
+
.script-source[data-v-c28ee15a] {
|
|
1620
1620
|
padding: 0.5rem;
|
|
1621
1621
|
background: #f5f5f5;
|
|
1622
1622
|
border-top: 1px solid #e0e0e0;
|
|
1623
1623
|
font-family: Consolas, "MS Gothic", "BIZ UDGothic", monospace;
|
|
1624
1624
|
font-size: 0.85rem;
|
|
1625
1625
|
}
|
|
1626
|
-
.script-source summary[data-v-
|
|
1626
|
+
.script-source summary[data-v-c28ee15a] {
|
|
1627
1627
|
cursor: pointer;
|
|
1628
1628
|
user-select: none;
|
|
1629
1629
|
padding: 0.5rem;
|
|
@@ -1632,13 +1632,13 @@
|
|
|
1632
1632
|
font-weight: 500;
|
|
1633
1633
|
color: #333;
|
|
1634
1634
|
}
|
|
1635
|
-
.script-source[open] summary[data-v-
|
|
1635
|
+
.script-source[open] summary[data-v-c28ee15a] {
|
|
1636
1636
|
margin-bottom: 0.5rem;
|
|
1637
1637
|
}
|
|
1638
|
-
.script-source summary[data-v-
|
|
1638
|
+
.script-source summary[data-v-c28ee15a]:hover {
|
|
1639
1639
|
background: #d8d8d8;
|
|
1640
1640
|
}
|
|
1641
|
-
.script-editor[data-v-
|
|
1641
|
+
.script-editor[data-v-c28ee15a] {
|
|
1642
1642
|
width: 100%;
|
|
1643
1643
|
height: 40vh;
|
|
1644
1644
|
padding: 1rem;
|
|
@@ -1652,23 +1652,23 @@
|
|
|
1652
1652
|
margin-bottom: 0.5rem;
|
|
1653
1653
|
line-height: 1.5;
|
|
1654
1654
|
}
|
|
1655
|
-
.script-editor[data-v-
|
|
1655
|
+
.script-editor[data-v-c28ee15a]:focus {
|
|
1656
1656
|
outline: none;
|
|
1657
1657
|
border-color: #4caf50;
|
|
1658
1658
|
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.1);
|
|
1659
1659
|
}
|
|
1660
|
-
.script-editor-invalid[data-v-
|
|
1660
|
+
.script-editor-invalid[data-v-c28ee15a] {
|
|
1661
1661
|
border-color: #ef4444;
|
|
1662
1662
|
}
|
|
1663
|
-
.script-editor-invalid[data-v-
|
|
1663
|
+
.script-editor-invalid[data-v-c28ee15a]:focus {
|
|
1664
1664
|
border-color: #ef4444;
|
|
1665
1665
|
box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.1);
|
|
1666
1666
|
}
|
|
1667
|
-
.editor-actions[data-v-
|
|
1667
|
+
.editor-actions[data-v-c28ee15a] {
|
|
1668
1668
|
display: flex;
|
|
1669
1669
|
justify-content: space-between;
|
|
1670
1670
|
}
|
|
1671
|
-
.apply-btn[data-v-
|
|
1671
|
+
.apply-btn[data-v-c28ee15a] {
|
|
1672
1672
|
padding: 0.5rem 1rem;
|
|
1673
1673
|
background: #4caf50;
|
|
1674
1674
|
color: white;
|
|
@@ -1679,16 +1679,16 @@
|
|
|
1679
1679
|
transition: background 0.2s;
|
|
1680
1680
|
font-weight: 500;
|
|
1681
1681
|
}
|
|
1682
|
-
.apply-btn[data-v-
|
|
1682
|
+
.apply-btn[data-v-c28ee15a]:hover {
|
|
1683
1683
|
background: #45a049;
|
|
1684
1684
|
}
|
|
1685
|
-
.apply-btn[data-v-
|
|
1685
|
+
.apply-btn[data-v-c28ee15a]:disabled {
|
|
1686
1686
|
background: #cccccc;
|
|
1687
1687
|
color: #666666;
|
|
1688
1688
|
cursor: not-allowed;
|
|
1689
1689
|
opacity: 0.6;
|
|
1690
1690
|
}
|
|
1691
|
-
.cancel-btn[data-v-
|
|
1691
|
+
.cancel-btn[data-v-c28ee15a] {
|
|
1692
1692
|
padding: 0.5rem 1rem;
|
|
1693
1693
|
background: #e0e0e0;
|
|
1694
1694
|
color: #333;
|
|
@@ -1699,10 +1699,10 @@
|
|
|
1699
1699
|
transition: background 0.2s;
|
|
1700
1700
|
font-weight: 500;
|
|
1701
1701
|
}
|
|
1702
|
-
.cancel-btn[data-v-
|
|
1702
|
+
.cancel-btn[data-v-c28ee15a]:hover {
|
|
1703
1703
|
background: #d0d0d0;
|
|
1704
1704
|
}
|
|
1705
|
-
.copy-btn[data-v-
|
|
1705
|
+
.copy-btn[data-v-c28ee15a] {
|
|
1706
1706
|
position: absolute;
|
|
1707
1707
|
bottom: 0.3rem;
|
|
1708
1708
|
right: 0.65rem;
|
|
@@ -1713,10 +1713,10 @@
|
|
|
1713
1713
|
cursor: pointer;
|
|
1714
1714
|
z-index: 1;
|
|
1715
1715
|
}
|
|
1716
|
-
.copy-btn[data-v-
|
|
1716
|
+
.copy-btn[data-v-c28ee15a]:hover {
|
|
1717
1717
|
color: #000;
|
|
1718
1718
|
}
|
|
1719
|
-
.copy-btn .material-icons[data-v-
|
|
1719
|
+
.copy-btn .material-icons[data-v-c28ee15a] {
|
|
1720
1720
|
font-size: 1.15rem;
|
|
1721
1721
|
}
|
|
1722
1722
|
/*$vite$:1*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"View.vue.d.ts","sourceRoot":"","sources":["../../src/vue/View.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"View.vue.d.ts","sourceRoot":"","sources":["../../src/vue/View.vue"],"names":[],"mappings":"AAm4DA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAGhE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAwDrD,KAAK,WAAW,GAAG;IACjB,cAAc,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;CACrD,CAAC;AA41FF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
package/dist/vue.cjs
CHANGED
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_plugin = require("./plugin-
|
|
5
|
+
const require_plugin = require("./plugin-Bi986Wga.cjs");
|
|
6
6
|
const require_contract = require("./contract-DxKVCRQk.cjs");
|
|
7
7
|
let _mulmocast_types = require("@mulmocast/types");
|
|
8
8
|
let vue = require("vue");
|
|
@@ -996,6 +996,7 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0, vue.def
|
|
|
996
996
|
return;
|
|
997
997
|
}
|
|
998
998
|
const prevImage = JSON.stringify(effectiveBeat(index).image);
|
|
999
|
+
const prevText = effectiveBeat(index).text;
|
|
999
1000
|
const requestedFilePath = filePath.value;
|
|
1000
1001
|
Reflect.deleteProperty(beatSaveErrors, index);
|
|
1001
1002
|
beatSaving[index] = true;
|
|
@@ -1019,6 +1020,13 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0, vue.def
|
|
|
1019
1020
|
Reflect.deleteProperty(renderedImages, index);
|
|
1020
1021
|
renderBeat(index);
|
|
1021
1022
|
}
|
|
1023
|
+
if (beat.text !== prevText) {
|
|
1024
|
+
if (playingAudio.value?.index === index) stopAllPlayback();
|
|
1025
|
+
Reflect.deleteProperty(beatAudios, index);
|
|
1026
|
+
Reflect.deleteProperty(audioState, index);
|
|
1027
|
+
Reflect.deleteProperty(audioErrors, index);
|
|
1028
|
+
if (beat.text) loadExistingBeatAudio(index);
|
|
1029
|
+
}
|
|
1022
1030
|
}
|
|
1023
1031
|
async function renderBeat(index) {
|
|
1024
1032
|
const requestedFilePath = filePath.value;
|
|
@@ -1925,7 +1933,7 @@ var _plugin_vue_export_helper_default = (sfc, props) => {
|
|
|
1925
1933
|
};
|
|
1926
1934
|
//#endregion
|
|
1927
1935
|
//#region src/vue/View.vue
|
|
1928
|
-
var View_default = /*#__PURE__*/ _plugin_vue_export_helper_default(View_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-
|
|
1936
|
+
var View_default = /*#__PURE__*/ _plugin_vue_export_helper_default(View_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-c28ee15a"]]);
|
|
1929
1937
|
//#endregion
|
|
1930
1938
|
//#region src/vue/Preview.vue?vue&type=script&setup=true&lang.ts
|
|
1931
1939
|
var _hoisted_1 = {
|