{"version":3,"file":"server.cjs","names":[],"sources":["../src/server/support.ts","../src/server/types.ts","../src/server/mulmoErrorCapture.ts","../src/server/ops.ts","../src/server/dispatch.ts"],"sourcesContent":["// Small server-side utilities. The realpath-based traversal check the ops\n// depend on used to live here as a faithful copy of the host's — it is now\n// imported from `@mulmoclaude/core/files` (#2461) so the security-critical\n// primitive cannot drift per host.\n\nimport { readFile } from \"node:fs/promises\";\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// 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","// Contracts for the server-side ops entry (`./server`). Everything the ops\n// need from a host that ISN'T generic mulmocast work is declared here as an\n// injected backend — MulmoClaude and MulmoTerminal each supply their own\n// implementation (phase 3 of plans/done/feat-mulmoscript-plugin.md).\n\nimport type { MinimalLogger } from \"@mulmoclaude/common\";\nimport type { FileOps } from \"gui-chat-protocol\";\nimport type { MulmoScriptChangedEvent, MulmoScriptGenerationEvent } from \"../core/contract\";\n\nexport interface OpFailure {\n  ok: false;\n  /** REST adapter mapping: bad_request→400, not_found→404,\n   *  unavailable→503, server_error→500. */\n  code: \"bad_request\" | \"not_found\" | \"server_error\" | \"unavailable\";\n  error: string;\n}\n\nexport type OpResult<T> = ({ ok: true } & T) | OpFailure;\n\nexport interface GenerateOpArgs {\n  filePath: string;\n  beatIndex?: number | undefined;\n  key?: string | undefined;\n  force?: boolean | undefined;\n  chatSessionId?: string | undefined;\n  /** Which registered stories root `filePath` is relative to (#3014).\n   *  Absent = the host's default root. */\n  root?: string | undefined;\n}\n\n/** `GenerateOpArgs` with `K` promoted to genuinely required. `Required<Pick<…>>`\n *  does NOT work here: under `exactOptionalPropertyTypes` the `-?` modifier drops\n *  only the `?`, leaving the explicitly declared `| undefined` in place, so the\n *  op body still sees `T | undefined`. `Omit` for the rest, because intersecting\n *  the whole interface would re-introduce the optional declaration. */\nexport type GenerateOpArgsWith<K extends keyof GenerateOpArgs> = { [P in K]-?: Exclude<GenerateOpArgs[P], undefined> } & Omit<GenerateOpArgs, K>;\n\nexport type MovieGenerationResult = { ok: true; outputPath: string } | { ok: false; error: string };\nexport type PdfGenerationResult = { ok: true; outputPath: string } | { ok: false; error: string };\n\nexport interface MovieProgressEvent {\n  kind: \"image\" | \"audio\";\n  beatIndex: number;\n}\n\n/** Host logger; every entry is already namespaced to mulmoScript by the package, so hosts just bind their own prefix/transport. */\nexport type MulmoScriptServerLog = MinimalLogger;\n\n/**\n * Host capabilities the server ops run against. Only genuinely\n * host-specific transport lives here — the mulmocast orchestration, path\n * containment, and generation-state tracking are all in-package.\n */\n/**\n * Which named-root capabilities a host has NOT wired.\n *\n * Pure and exported so the rule can be tested without building a server: the\n * condition used to key on the root COUNT alone, so a host that HAD passed\n * `artifactsFor` was told at every boot that its writes land in the default\n * root — the opposite of what the code then did (#3022).\n *\n * Reads and uploads need nothing from the host, so they never appear here.\n */\nexport function missingRootCapabilities(backend: Pick<MulmoScriptServerBackend, \"rootScopedGenerationState\" | \"artifactsFor\">): string[] {\n  const missing: string[] = [];\n  // The pair identity is complete INSIDE this package and stops at the host\n  // boundary: a host's per-session store keys pending work on\n  // `(kind, filePath, key)` — `generationKey` in `@mulmobridge/protocol` — so\n  // two roots generating the same beat in one session collapse to one entry\n  // (Codex P1 on #3015). A host that keeps no such store declares it.\n  if (backend.rootScopedGenerationState !== true) {\n    missing.push(\n      \"GENERATION is refused until this host declares `rootScopedGenerationState` (its pending-generation state must carry the root, or it must keep none)\",\n    );\n  }\n  // The save/update executors run against one FileOps; without a per-root one\n  // they would rewrite the DEFAULT root's identically-named script.\n  if (backend.artifactsFor === undefined) {\n    missing.push(\"save/update land in the DEFAULT root until this host passes `artifactsFor`\");\n  }\n  return missing;\n}\n\nexport interface MulmoScriptServerBackend {\n  /** Absolute path of the DEFAULT stories directory\n   *  (`<workspace>/artifacts/stories`). May not exist yet — the ops lazily\n   *  create + realpath it. A wire path with no `root` resolves here, which\n   *  is what makes every pre-`root` caller keep its exact behaviour. */\n  storiesDir: string;\n  /**\n   * Additional stories roots, keyed by the id the wire `root` names\n   * (#3014). Absent or empty = the single-root world this package shipped\n   * with, byte for byte.\n   *\n   * The KEY IS OPAQUE TO THIS PACKAGE. It is looked up here and never\n   * parsed, so the host owns what a root id means — a declared name, a\n   * hash of the directory, an assigned handle. That decision is persisted\n   * in the host's cards, so it belongs to whoever has to keep it stable,\n   * and pinning it here would freeze it for every host at once.\n   *\n   * Registration is the containment boundary. `filePath` is a field the\n   * MODEL fills (`core/definition.ts`), so the agent must never be able to\n   * name a root: it may only address what the host already registered. The\n   * tool definition is unchanged for exactly this reason — a host adds a\n   * root, an agent cannot.\n   */\n  extraRoots?: Record<string, string>;\n  /**\n   * Whether this host can keep pending generations apart by root.\n   *\n   * Generation in a named root was refused outright (#3015) because\n   * MulmoClaude's session store keys pending work by `(kind, filePath, key)` —\n   * `generationKey` in `@mulmobridge/protocol` — so two roots generating the\n   * same beat in one session collapse to one entry and either completion\n   * clears the other root's indicator.\n   *\n   * That hazard is the HOST's, not this package's, and not every host has it:\n   * MulmoTerminal ignores `chatSessionId` and publishes straight to a pubsub\n   * channel the View filters by the pair, so it was being refused for a\n   * collision it cannot have (#3019). The question was never \"is this root the\n   * default\" but \"can this host tell two roots' generations apart\".\n   *\n   * Default `false` — absent means the refusal stays exactly as it shipped, so\n   * a host that has not thought about this is not quietly opened up. A host\n   * sets it only once its own pending-generation state carries the root (or it\n   * keeps none at all).\n   */\n  rootScopedGenerationState?: boolean;\n  /** Shared artifacts FileOps (rooted at `<workspace>/artifacts`) for the\n   *  save / reopen / update dispatch kinds (phase-1 core executes). */\n  artifacts: FileOps;\n  /**\n   * The FileOps for a named root's artifacts area, or null when the host does\n   * not serve that root.\n   *\n   * `artifacts` above is ONE FileOps bound to the default root, and the\n   * save / update executors run against it. So a write naming another root\n   * rewrote the DEFAULT root's identically-named script and then announced the\n   * other one as changed — which is why those kinds were refused outright\n   * (#3015 review G1). Reads and uploads never had the problem: they go\n   * through `resolveStory` and take the absolute path it returns.\n   *\n   * A resolver rather than a map, because the host already has one: it knows\n   * which directory an opaque root id names, and building a FileOps for it is\n   * a closure per root (MulmoTerminal's `createFileOps(rootFor, label)` takes\n   * a root getter for exactly this). Absent means named-root writes stay\n   * refused, so a host that has not wired it keeps the shipped behaviour.\n   */\n  artifactsFor?: (root: string) => FileOps | null;\n  /**\n   * FileOps over caller-supplied ABSOLUTE paths — what makes `filePath` able\n   * to name a script outside `artifacts/stories/`, the same capability\n   * presentDocument / presentHtml take for their `path` argument\n   * (`@mulmoclaude/core/files`' byPath ops). A RELATIVE `filePath` never\n   * reaches it.\n   *\n   * Optional so a host that has not opted in keeps the stories-only behaviour\n   * byte for byte: without it the core's `locate` refuses an absolute path\n   * rather than resolving it somewhere else.\n   */\n  byPath?: FileOps;\n  /** Atomic file write (tmp alongside destination + rename; parent dirs\n   *  created). Hosts inject their hardened implementation. */\n  writeFileAtomic: (absolutePath: string, data: string | Uint8Array) => Promise<void>;\n  /** ffmpeg availability probe. `false` blocks render/movie/PDF ops with a\n   *  clear message; `true`/`undefined` proceeds (a boot probe may not have\n   *  completed yet — never block on the startup window). */\n  isFfmpegAvailable?: () => boolean | undefined;\n  /**\n   * Generation fan-out (session channels, UI pubsub). Called on EDGE\n   * transitions only — first start / last finish of concurrent same-key\n   * runs — plus the finish-only per-beat pulses from the movie/PDF\n   * pipelines. `chatSessionId` is undefined for callers outside a chat\n   * session. The package keeps the in-flight snapshot itself.\n   */\n  onGenerationEvent?: (chatSessionId: string | undefined, event: MulmoScriptGenerationEvent) => void;\n  /**\n   * A script was written. Every open View reloads from disk, which is what makes an agent's\n   * edit appear without the user reopening the canvas.\n   */\n  onScriptChanged?: (event: MulmoScriptChangedEvent) => void;\n  log?: MulmoScriptServerLog;\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 { errorMessage } from \"@mulmoclaude/common\";\nimport { 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 = errorMessage(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 type { FileOps } from \"gui-chat-protocol\";\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 { DEFAULT_ROOT, normalizeRoot } from \"../core/contract\";\nimport type { MulmoScriptGenerationEvent } from \"../core/contract\";\nimport { isAbsoluteStoryPath, normalizeStoryPath, STORY_TARGET_EXTENSIONS, storyRefWithin, storiesRelativePath } from \"../core/paths\";\nimport { errorMessage } from \"@mulmoclaude/common\";\nimport { resolveWithinRoot } from \"@mulmoclaude/core/files\";\nimport { fileToDataUri, stripDataUri } from \"./support\";\nimport { missingRootCapabilities } from \"./types\";\nimport { enableGraphAIErrorCapture, setMulmoErrorCaptureLogger, withMulmoErrorCapture } from \"./mulmoErrorCapture\";\nimport type {\n  GenerateOpArgsWith,\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, root?: string) => { ok: true; absolutePath: string } | OpFailure;\n  buildContext?: (absoluteFilePath: string, force?: boolean) => Promise<StoryContext | undefined>;\n}\n\nexport interface RunStoryOpOptions<T> {\n  force?: boolean | undefined;\n  /** Which registered stories root `filePath` is relative to (#3014).\n   *  Absent = the host's default root, i.e. exactly the pre-roots path. */\n  root?: string | undefined;\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// Run `body` with a mulmocast per-beat progress callback registered.\n// `onBeat` receives each beat event's sessionType + resolved index; the\n// caller decides which sessionTypes to forward. The callback is always\n// unregistered, even when `body` throws.\n//\n// Known limitation: addSessionProgressCallback is global, so when two\n// generations for *different* scripts run concurrently, both closures\n// are invoked for every beat event and rely on idToIndex to filter out\n// the other run's events. That filter is reliable only when each beat\n// carries an explicit `id`. Beats without one fall back to\n// \"__index__${index}\", and identical fallback ids across scripts collide\n// → progress meant for script A surfaces on script B. Fixing this\n// properly needs mulmocast to attach a per-run identifier to its\n// progress events (or a global serialization gate); tracked separately.\nasync function withBeatProgress<T>(beats: MulmoBeat[], onBeat: (sessionType: string, beatIndex: number) => void, body: () => Promise<T>): Promise<T> {\n  const idToIndex = buildBeatIdIndex(beats);\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    onBeat(event.sessionType, beatIndex);\n  };\n  addSessionProgressCallback(onProgress);\n  try {\n    return await body();\n  } finally {\n    removeSessionProgressCallback(onProgress);\n  }\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, root?: string): string {\n  // Normalised HERE rather than by the caller. A start keyed `\" repoA \"` and a\n  // finish keyed `\"repoA\"` never match, and the tracker entry then leaks for\n  // the life of the process — so the one place that builds the key is the one\n  // place that must not be able to get the spelling wrong (Codex on #3015).\n  const normalized = normalizeRoot(root);\n  // `root` is the LAST element so a call site that omits it produces exactly\n  // the pre-#3014 key — the default root's entries keep their identity across\n  // an upgrade, and a running generation is not orphaned by one.\n  return normalized === DEFAULT_ROOT ? JSON.stringify([kind, filePath, key]) : JSON.stringify([kind, filePath, key, normalized]);\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  // Root registry: the default (pre-#3014, wire `root` absent) plus whatever\n  // the host registered. Resolved once — the host owns the ids, this package\n  // only ever looks them up.\n  const rootDirs = new Map<string, string>([[DEFAULT_ROOT, path.resolve(backend.storiesDir)]]);\n  for (const [id, dir] of Object.entries(backend.extraRoots ?? {})) {\n    // An empty id is the default root's own key: accepting it would re-point\n    // every pre-roots caller at someone else's directory. Dropping it quietly\n    // would hide the host's misconfiguration until a read returned the wrong\n    // file, and this runs at boot, where throwing is the cheap failure.\n    // Trim on registration too: a lookup normalizes, so an untrimmed key would\n    // be unreachable.\n    const trimmed = id.trim();\n    if (trimmed === DEFAULT_ROOT) {\n      throw new Error(\"mulmoScript: extraRoots key must not be empty — the empty id is reserved for the default stories root\");\n    }\n    // Two ids that trim to one key: `set` would silently keep the LAST\n    // directory, so every read for `repoA` would answer from a directory the\n    // card never named, with the host given no signal (CodeRabbit on #3015).\n    // Same reasoning as the empty id above — a misconfiguration is cheapest to\n    // fail on at boot, and silently resolving the wrong directory is the\n    // failure this package refuses everywhere else.\n    if (rootDirs.has(trimmed)) {\n      throw new Error(`mulmoScript: extraRoots keys must be distinct after trimming — \"${trimmed}\" is registered twice`);\n    }\n    rootDirs.set(trimmed, path.resolve(dir));\n  }\n  warnAboutUnwiredRoots();\n\n  /**\n   * Tell a host which named-root capabilities it has not wired — and nothing\n   * when it has wired them all.\n   *\n   * The condition used to be the root COUNT alone, so a host that had passed\n   * `artifactsFor` was still told, at every boot, that its writes land in the\n   * default root. That is not a stale wording: it is the opposite of what the\n   * code then does, read by the hosts that got the wiring RIGHT (#3022, from\n   * the consuming host).\n   *\n   * Silence when nothing is missing, because a warning that always fires is\n   * one people learn to skip — and then the host that really did forget\n   * `artifactsFor` cannot tell either. Each clause is emitted only when that\n   * capability is actually absent, so the message says what is true for THIS\n   * host rather than what was true when it was written.\n   */\n  function warnAboutUnwiredRoots(): void {\n    if (rootDirs.size <= 1) return;\n    const missing = missingRootCapabilities(backend);\n    if (missing.length === 0) return;\n    log.warn(`extra stories roots registered — reads and uploads work, but ${missing.join(\", and \")} (#3019)`, {\n      roots: [...rootDirs.keys()].filter((id) => id !== DEFAULT_ROOT),\n    });\n  }\n\n  /** The registered directory for a wire `root`, or null when the host never\n   *  registered it. Null is a REJECTION, not a fallback to the default: an\n   *  unknown root must not quietly read the workspace's file of the same\n   *  name. */\n  function rootDir(root: string | undefined): string | null {\n    return rootDirs.get(normalizeRoot(root)) ?? null;\n  }\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, root?: string): string | null {\n    // An unregistered root gets null, not the default root's base. Relativizing\n    // against the default would mint a wire ref that READS BACK as a different\n    // file — the same silent-substitution failure `resolveStory` rejects, and\n    // this function is on the ops object, so a host can reach it directly\n    // without passing through that guard (#3015 review F2).\n    const dir = rootDir(root);\n    if (dir === null) return null;\n    const base = ensureStoriesReal(root) ?? dir;\n    // The relativizing rule is a pure function in `core/paths.ts` so it can be\n    // driven with `path.win32` from a POSIX machine — the case it guards\n    // (no relative route across drives) is unreachable here.\n    return storyRefWithin(base, absolutePath, path);\n  }\n\n  /**\n   * The wire ref for an artifact mulmocast generated FROM `wireFilePath`\n   * (movie, per-beat clip, PDF).\n   *\n   * mulmocast derives every output path from the script's own directory\n   * (`buildContext` passes `basedir: path.dirname(absoluteFilePath)`), so an\n   * absolute script's outputs live outside the stories dir and have no\n   * `stories/<rel>` spelling. They travel as absolute paths — the same form\n   * their script arrived in, which `resolveStory` reads back unchanged.\n   * A relative script keeps minting relative refs, byte for byte as before.\n   */\n  function outputRef(outputPath: string, wireFilePath: string, root?: string): string | null {\n    return path.isAbsolute(wireFilePath) ? outputPath : toStoryRef(outputPath, root);\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  //\n  // Keyed by the resolved DIRECTORY rather than the root id, so there is no\n  // normalisation left to forget: `rootDir` already collapses every spelling of\n  // a root to one absolute path, and two ids pointing at the same directory\n  // share one entry instead of allocating a second. Keying on the raw id let\n  // `\" repoA \"` and `\"repoA\"` both resolve and then cache separately, in a map\n  // that is never evicted (Codex P2 on #3015).\n  const storiesRealCache = new Map<string, string>();\n  function ensureStoriesReal(root?: string): string | null {\n    const dir = rootDir(root);\n    if (dir === null) return null;\n    const cached = storiesRealCache.get(dir);\n    if (cached) return cached;\n    try {\n      // Only the DEFAULT root is created on demand. An extra root is a\n      // directory the user already owns — often a git worktree — and creating\n      // it here would grow `artifacts/stories/` inside their repository as a\n      // side effect of a status poll. A host that registers a root is\n      // responsible for it existing (#3015 review F3).\n      if (normalizeRoot(root) === DEFAULT_ROOT) mkdirSync(dir, { recursive: true });\n      const real = realpathSync(dir);\n      storiesRealCache.set(dir, real);\n      return real;\n    } catch {\n      return null;\n    }\n  }\n\n  /**\n   * Resolve an ABSOLUTE wire path — a script the caller named outside the\n   * stories dir, or one of the artifacts mulmocast generated beside it.\n   *\n   * Deliberately NOT containment-checked, for the reason spelled out on\n   * `isAbsoluteStoryPath`: opening a file the caller named is the purpose of\n   * the form, and the agent can already read and write those files directly.\n   * What IS checked mirrors `@mulmoclaude/core/files`' byPath ops:\n   *   - the lexical shape (no NUL, no `.` / `..` / empty segment, and one of\n   *     the extensions this package mints or accepts), so a vetted path cannot\n   *     be re-pointed later;\n   *   - a real REGULAR FILE, judged through `realpath` so a symlink is\n   *     assessed by what it points at and a directory named `deck.json` cannot\n   *     masquerade as a script.\n   *\n   * The media extensions widen the same way, which is what makes the download\n   * routes able to serve an absolute script's movie / clip / PDF. That is a\n   * deliberate consequence, not an oversight: those routes sit behind the same\n   * bearer auth as every other `/api` route, and a caller holding that token\n   * can already have presentDocument read any `.md` and presentHtml any\n   * `.html`. The boundary is the token, not the directory.\n   */\n  function resolveAbsoluteStory(filePath: string): { ok: true; absolutePath: string } | OpFailure {\n    // `backend.byPath` is the host's OPT-IN to the absolute form, and it gates\n    // the whole of it — not just the core's read/write. Without it these ops\n    // (generation, status, probes, download) would keep serving absolute paths\n    // that the core itself refuses, so a host that never opted in would have\n    // the capability anyway through the half of the package that does not\n    // consult it (Sourcery on #3042). Refusing here restores the pre-existing\n    // behaviour EXACTLY: absolute paths were `bad_request \"Invalid filePath\"`.\n    if (!backend.byPath) {\n      return opBadRequest(\"Invalid filePath\");\n    }\n    if (!isAbsoluteStoryPath(filePath, STORY_TARGET_EXTENSIONS)) {\n      return opBadRequest(\"Invalid filePath\");\n    }\n    let target: string;\n    try {\n      target = realpathSync(path.resolve(filePath));\n    } catch {\n      return opNotFound(`File not found: ${filePath}`);\n    }\n    try {\n      if (!statSync(target).isFile()) return opBadRequest(\"Invalid filePath\");\n    } catch {\n      return opNotFound(`File not found: ${filePath}`);\n    }\n    // The extension is re-checked on the RESOLVED target, not just the\n    // spelling that arrived: a symlink named `deck.json` may point at any\n    // regular file, and the download routes stream `absolutePath` straight to\n    // the client — so checking only the link's own name would let `deck.json`\n    // → `/etc/passwd` through the very gate that exists to stop it\n    // (CodeRabbit CWE-59 on #3042). Judging the link by what it points at is\n    // the same rule the regular-file check above already applies.\n    if (!isAbsoluteStoryPath(target, STORY_TARGET_EXTENSIONS)) {\n      return opBadRequest(\"Invalid filePath\");\n    }\n    return { ok: true, absolutePath: target };\n  }\n\n  /**\n   * Resolve and validate a RELATIVE stories wire path to its absolute\n   * realpath (absolute wire paths are handed to `resolveAbsoluteStory`).\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, root?: string): { ok: true; absolutePath: string } | OpFailure {\n    // An ABSOLUTE path names a script (or one of its generated artifacts)\n    // living outside the stories dir, and is taken as named — the same rule\n    // `presentDocument` / `presentHtml` apply to their `path` argument\n    // (`@mulmoclaude/core/files`). Answered BEFORE the root checks below\n    // because an absolute path is relative to nothing: there is no root for it\n    // to be resolved against, so neither an unregistered root nor a stories\n    // dir that cannot be realpathed has any bearing on it.\n    //\n    // A value that is absolute only under ANOTHER platform's rules\n    // (`C:\\\\proj\\\\x.json` on POSIX) is NOT absolute here, and falls through to\n    // the relative rules, which reject it — resolving it would land it under\n    // the stories dir, a file nobody named.\n    if (path.isAbsolute(filePath)) {\n      return resolveAbsoluteStory(filePath);\n    }\n    // An unregistered root is a bad request, not a fall back to the default:\n    // resolving it against the workspace would hand the caller a DIFFERENT\n    // file that happens to share the name.\n    if (rootDir(root) === null) {\n      return opBadRequest(\"Unknown stories root\");\n    }\n    const storiesReal = ensureStoriesReal(root);\n    if (!storiesReal) {\n      return opServerError(\"stories directory not available\");\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   * Whether this root is one the host registered.\n   *\n   * Every other root-aware op learns this from `resolveStory`, which needs a\n   * file. `pendingGenerations` needs none — it only filters an in-memory map —\n   * so an unregistered root produced `{ ok: true, pending: [] }`, and a host\n   * typo or a stale card read back as \"no work is running\" (Codex P2 on\n   * #3015). An unknown root is a question this package cannot answer, and the\n   * answer it must not give is a confident empty one.\n   */\n  function guardStoryRootRegistered(root: string | undefined): OpFailure | null {\n    return rootDir(root) === null ? opBadRequest(`unknown stories root \"${normalizeRoot(root)}\"`) : null;\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   * For an ABSOLUTE `filePath` there is no tree to stay inside — the form\n   * exists precisely to name a file elsewhere — so what this asserts there is\n   * `resolveAbsoluteStory`'s pair: the lexical shape, and a real regular file\n   * behind the realpath. The core's `locate` reaches the same verdict through\n   * `isAbsoluteStoryPath`, which is why the two are one function.\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, root?: string): OpFailure | null {\n    if (typeof filePath !== \"string\" || filePath === \"\") return null;\n    const resolved = resolveStory(filePath, root);\n    return resolved.ok ? null : resolved;\n  }\n\n  /**\n   * Whether a GENERATION may run in this root.\n   *\n   * The pair identity is complete inside this package but stops at the host\n   * boundary: a host's per-session store keys pending work by\n   * `(kind, filePath, key)` — `generationKey` in `@mulmobridge/protocol`,\n   * which bridges also consume. Two roots running the same generation in one\n   * session would collapse to one entry, and either completion would clear the\n   * other root's indicator (Codex P1 on #3015).\n   *\n   * Whose hazard it is decides who answers: a host declares\n   * `rootScopedGenerationState` when its own pending state carries the root —\n   * or when it keeps none, which is MulmoTerminal's case. It was refused for a\n   * collision it cannot have (#3019).\n   *\n   * Absent still refuses, so a host that has not thought about this keeps the\n   * shipped behaviour rather than being quietly opened up.\n   */\n  function guardStoryGenerationRoot(root: string | undefined): OpFailure | null {\n    if (normalizeRoot(root) === DEFAULT_ROOT) return null;\n    // Registration BEFORE the host's opt-in. The flag says \"this host can tell\n    // two roots apart\", not \"any id is addressable\" — and the generation ops\n    // publish their start event before `runStoryOp` reaches `resolveStory`, so\n    // an unregistered root emitted a start/finish pair for work that never\n    // existed (Codex + CodeRabbit on #3020).\n    const registered = guardStoryRootRegistered(root);\n    if (registered) return registered;\n    if (backend.rootScopedGenerationState === true) return null;\n    return opBadRequest(\"generating in a non-default stories root is not supported yet\");\n  }\n\n  /**\n   * The write half of the same fail-closed rule.\n   *\n   * It lives in the ops that write rather than at their dispatch sites,\n   * because a per-site guard is a list someone has to remember to extend:\n   * `save` / `updateBeat` / `updateScript` were guarded and the two upload\n   * kinds were not, so an image could still land in a named root\n   * (CodeRabbit on #3015). `save` / `update* ` cannot follow suit — they run\n   * through the package executors, not through these ops — so\n   * `test_server_roots.ts` walks the whole ops surface and fails on any op\n   * that is neither in the read-only allowlist nor refusing.\n   */\n  /**\n   * Whether a WRITE may target this root.\n   *\n   * Reads are root-aware; writes are not. `executeMulmoScriptSave` and the\n   * update executors run against one `FileOps`, bound by the host to the\n   * default root, so a write naming another root would rewrite the DEFAULT\n   * root's identically-named file and then announce the other one as changed\n   * (#3015 review G1). Closing it was fail-closed: \"readable but not yet\n   * writable\" beats \"wrote somewhere else and said so\".\n   *\n   * It opens per root, not globally: the host answers `artifactsFor` for the\n   * roots it can serve, and a root it cannot is still refused. A host that\n   * wires nothing keeps the shipped refusal (#3019).\n   *\n   * The two refusals say different things because they are fixed in different\n   * places. No `artifactsFor` at all is a capability this host has not turned\n   * on. `artifactsFor` present but answering `null` for a REGISTERED root is a\n   * wiring mistake inside that host — and it is the quiet one, because the\n   * boot warning stays silent (the resolver WAS passed) while every write is\n   * refused. One message for both read as \"the plugin cannot do this yet\",\n   * which sends the host looking in the wrong place (#3024).\n   */\n  function guardStoryWriteRoot(root: string | undefined): OpFailure | null {\n    if (normalizeRoot(root) === DEFAULT_ROOT) return null;\n    const registered = guardStoryRootRegistered(root);\n    if (registered) return registered;\n    if (artifactsForRoot(root) !== null) return null;\n    return backend.artifactsFor === undefined\n      ? opBadRequest(\"writing to a non-default stories root is not supported yet\")\n      : opBadRequest(`this host's \\`artifactsFor\\` returned no FileOps for the registered stories root \"${normalizeRoot(root)}\"`);\n  }\n\n  /**\n   * The FileOps a write to this root must go through.\n   *\n   * The default root keeps the single `artifacts` it always had, byte for\n   * byte. A named root is served only when the host both REGISTERED it\n   * (`extraRoots` — the containment boundary, so a host cannot widen the\n   * addressable set through this back door) and can supply a FileOps for it.\n   */\n  function artifactsForRoot(root: string | undefined): FileOps | null {\n    const normalized = normalizeRoot(root);\n    if (normalized === DEFAULT_ROOT) return backend.artifacts;\n    if (rootDir(normalized) === null) return null;\n    const hostOps = backend.artifactsFor?.(normalized);\n    return hostOps === undefined || hostOps === null ? null : storiesScoped(hostOps);\n  }\n\n  /**\n   * Address a named root's FileOps the way the READ side addresses it.\n   *\n   * The two sides disagreed about one path segment, and the disagreement was\n   * silent. A read strips `stories/` and resolves under the registered\n   * directory (`<root>/<rel>`); a write handed the executors' wire path\n   * straight to the host's FileOps, which is rooted at that same registered\n   * directory, so the bytes landed in `<root>/stories/<rel>`. `save` then\n   * REPORTED SUCCESS and returned a wire path that could not be read back —\n   * a card pointing at nothing (#3020 review H1, from the consuming host).\n   *\n   * Stripping here makes \"the directory you registered is the directory your\n   * FileOps is rooted at\" true, which is what a host writes without being\n   * told. The default root is untouched: its FileOps is rooted one level up,\n   * at `<workspace>/artifacts`, and is shared with other plugins.\n   *\n   * A path that is not a stories wire path throws rather than passing\n   * through. Everything reaching here has been through `normalizeStoryPath`\n   * or `storyFilePath`, so one that has not is a bug — and letting it through\n   * would write it somewhere nobody can read, which is the failure this whole\n   * wrapper exists to end.\n   */\n  function storiesScoped(inner: FileOps): FileOps {\n    const within = (wirePath: string): string => {\n      const relative = storiesRelativePath(wirePath);\n      if (relative === null) throw new Error(`mulmoScript: \"${wirePath}\" is not a stories path — a named root's FileOps takes stories paths only`);\n      return relative;\n    };\n    // Every member is `async` so the refusal arrives as a REJECTED PROMISE,\n    // the way every other FileOps failure does. Throwing synchronously out of\n    // a method the caller only ever awaits would skip its `try`.\n    return {\n      read: async (wirePath) => inner.read(within(wirePath)),\n      readBytes: async (wirePath) => inner.readBytes(within(wirePath)),\n      write: async (wirePath, data) => inner.write(within(wirePath), data),\n      readDir: async (wirePath) => inner.readDir(within(wirePath)),\n      stat: async (wirePath) => inner.stat(within(wirePath)),\n      exists: async (wirePath) => inner.exists(within(wirePath)),\n      unlink: async (wirePath) => inner.unlink(within(wirePath)),\n    };\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; root: string; count: number }>();\n\n  /** Emit `root` only when it names a non-default one: an event carrying\n   *  `root: \"\"` and one carrying nothing must stay indistinguishable to every\n   *  pre-#3014 consumer. */\n  function rootField(root: string | undefined): { root?: string } {\n    const normalized = normalizeRoot(root);\n    return normalized === DEFAULT_ROOT ? {} : { root: normalized };\n  }\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). An ABSOLUTE path is\n   *  already its own canonical form — `normalizeStoryPath` refuses it, and\n   *  passing it through unchanged is exactly right, because it has no\n   *  stories-relative spelling to collapse to. Untrusted spellings pass\n   *  through too: they never resolve, so they can't collide. */\n  function canonicalWirePath(filePath: string): string {\n    return normalizeStoryPath(filePath) ?? filePath;\n  }\n\n  /**\n   * `error` and `root` travel in an options object, NOT as two trailing\n   * optional positionals.\n   *\n   * They were positional once, and appending `root` to the sixteen call sites\n   * put it in `error`'s slot at the nine that pass no error: every start event\n   * carried `error: \"<root>\"`, the root was dropped from the key, the tracker\n   * entry was filed under the default root — and because the matching finish\n   * DID pass both, its key differed and the entry was never deleted, leaking\n   * one row per generation for the life of the process. Two adjacent optional\n   * strings cannot be told apart by the type checker, so the shape is the only\n   * thing that can prevent it (#3015 review).\n   */\n  function publishGeneration(\n    chatSessionId: string | undefined,\n    kind: GenerationKind,\n    filePath: string,\n    key: string,\n    finished: boolean,\n    opts: { error?: string | undefined; root?: string | undefined } = {},\n  ): void {\n    const { error, root } = opts;\n    const wirePath = canonicalWirePath(filePath);\n    // The NORMALIZED root, not the raw spelling. The tracker value and the\n    // emitted event both normalize, so keying on the raw text made a start\n    // written `\" repoA \"` and its finish written `\"repoA\"` two entries: the\n    // finish deleted nothing and the start leaked (Codex P2 on #3015).\n    const mapKey = generationMapKey(kind, wirePath, key, root);\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, root: normalizeRoot(root), count: 1 });\n    }\n    const event: MulmoScriptGenerationEvent = {\n      kind,\n      filePath: wirePath,\n      key,\n      done: finished,\n      ...(error ? { error } : {}),\n      ...rootField(root),\n    };\n    backend.onGenerationEvent?.(chatSessionId, event);\n  }\n\n  /**\n   * Tell every open View that this script was written.\n   *\n   * `origin` is the writer. A View passes its own id so it can ignore the echo of its own\n   * save — reloading there would rebuild the element the caret is in, mid-keystroke. A write\n   * from the agent carries no origin, so everyone reloads.\n   */\n  function publishScriptChanged(filePath: string, origin?: string, root?: string): void {\n    backend.onScriptChanged?.({\n      filePath: canonicalWirePath(filePath),\n      ...(origin === undefined ? {} : { origin }),\n      ...rootField(root),\n    });\n  }\n\n  /**\n   * Snapshot of generations currently in flight for one script — the View's\n   * mount-time catch-up.\n   *\n   * Filtered on the PAIR. Filtering on `filePath` alone hands a View watching\n   * `repoB/stories/deck.json` the run started for `repoA`'s identically-named\n   * deck, and the caller cannot discard it because the returned event would\n   * carry no root either (Codex P1 / CodeRabbit on #3015).\n   */\n  function pendingGenerations(filePath: string, root?: string): MulmoScriptGenerationEvent[] {\n    const wirePath = canonicalWirePath(filePath);\n    const wanted = normalizeRoot(root);\n    return [...inFlightGenerations.values()]\n      .filter((entry) => entry.filePath === wirePath && entry.root === wanted)\n      .map(({ kind, key }) => ({ kind, filePath: wirePath, key, done: false, ...rootField(root) }));\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, options.root);\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: errorMessage(err),\n      });\n      return opServerError(errorMessage(err));\n    }\n  }\n\n  // ── Probe ops ─────────────────────────────────────────────────\n\n  async function beatImageOp(filePath: string, beatIndex: number, root?: string): Promise<OpResult<{ image: string | null }>> {\n    return runStoryOp<{ image: string | null }>(filePath, { operation: \"beat-image\", root }, 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, root?: string): Promise<OpResult<{ audio: string | null }>> {\n    return runStoryOp<{ audio: string | null }>(\n      filePath,\n      { operation: \"beat-audio\", root, onContextMissing: () => ({ ok: true, audio: null }) },\n      async ({ context }) => {\n        const beat = context.studio.script.beats[beatIndex];\n        // Probe contract: a beat index the script doesn't have soft-fails\n        // like a beat with nothing generated yet, never a server error.\n        if (!beat) return { ok: true, audio: null };\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, root?: string): Promise<OpResult<{ moviePath: string | null }>> {\n    return runStoryOp<{ moviePath: string | null }>(filePath, { operation: \"beat-movie\", root }, 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 ? outputRef(existing, filePath, root) : null };\n    });\n  }\n\n  async function characterImageOp(filePath: string, key: string, root?: string): Promise<OpResult<{ image: string | null }>> {\n    return runStoryOp<{ image: string | null }>(filePath, { operation: \"character-image\", root }, 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, wireFilePath: string, root?: 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 outputRef(outputPath, wireFilePath, root);\n  }\n\n  async function movieStatusOp(filePath: string, root?: string): Promise<OpResult<{ moviePath: string | null }>> {\n    return runStoryOp(\n      filePath,\n      { operation: \"movie-status\", root, onContextMissing: () => ({ ok: true, moviePath: null }) },\n      async ({ absoluteFilePath, context }) => ({ ok: true, moviePath: freshOutputRef(movieFilePath(context), absoluteFilePath, filePath, root) }),\n    );\n  }\n\n  async function pdfStatusOp(filePath: string, root?: string): Promise<OpResult<{ pdfPath: string | null }>> {\n    return runStoryOp(\n      filePath,\n      { operation: \"pdf-status\", root, onContextMissing: () => ({ ok: true, pdfPath: null }) },\n      async ({ absoluteFilePath, context }) => ({\n        ok: true,\n        pdfPath: freshOutputRef(pdfFilePath(context, PDF_MODE), absoluteFilePath, filePath, root),\n      }),\n    );\n  }\n\n  // ── Generation ops ────────────────────────────────────────────\n\n  async function renderBeatOp(args: GenerateOpArgsWith<\"filePath\" | \"beatIndex\">): Promise<OpResult<{ image: string }>> {\n    const { filePath, beatIndex, force, chatSessionId, root } = args;\n    const rootGuard = guardStoryGenerationRoot(root);\n    if (rootGuard) return rootGuard;\n    const ffmpeg = ffmpegGuard();\n    if (ffmpeg) return ffmpeg;\n\n    const mapKey = String(beatIndex);\n    publishGeneration(chatSessionId, \"beatImage\", filePath, mapKey, false, { root });\n    let genError: string | undefined;\n    try {\n      const result = await runStoryOp<{ image: string }>(filePath, { force, operation: \"render-beat\", root }, async ({ context }) => {\n        await generateBeatImage({\n          index: beatIndex,\n          context,\n          ...(force ? { args: { forceImage: true } } : {}),\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, { error: genError, root });\n    }\n  }\n\n  async function generateBeatAudioOp(args: GenerateOpArgsWith<\"filePath\" | \"beatIndex\">): Promise<OpResult<{ audio: string }>> {\n    const { filePath, beatIndex, force, chatSessionId, root } = args;\n    const rootGuard = guardStoryGenerationRoot(root);\n    if (rootGuard) return rootGuard;\n    const mapKey = String(beatIndex);\n    publishGeneration(chatSessionId, \"beatAudio\", filePath, mapKey, false, { root });\n    let genError: string | undefined;\n    try {\n      const result = await runStoryOp<{ audio: string }>(filePath, { force, operation: \"generate-beat-audio\", root }, 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        // The generated file still wins when present, so a beat index the\n        // script doesn't have only skips the path-derivation fallback and\n        // lands on the \"audio was not generated\" branch below.\n        const generatedFile = context.studio.beats[beatIndex]?.audioFile;\n        const audioPath = generatedFile ?? (beat ? getBeatAudioPathOrUrl(beat.text ?? \"\", context, beat, context.lang) : undefined);\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, { error: genError, root });\n    }\n  }\n\n  async function renderCharacterOp(args: GenerateOpArgsWith<\"filePath\" | \"key\">): Promise<OpResult<{ image: string }>> {\n    const { filePath, key, force, chatSessionId, root } = args;\n    const rootGuard = guardStoryGenerationRoot(root);\n    if (rootGuard) return rootGuard;\n    publishGeneration(chatSessionId, \"characterImage\", filePath, key, false, { root });\n    let genError: string | undefined;\n    try {\n      const result = await runStoryOp<{ image: string }>(filePath, { force, operation: \"render-character\", root }, 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 !== undefined ? { 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, { error: genError, root });\n    }\n  }\n\n  // ── Upload ops ────────────────────────────────────────────────\n\n  async function uploadBeatImageOp(filePath: string, beatIndex: number, imageData: string, root?: string): Promise<OpResult<{ image: string }>> {\n    // No root guard: the write is already in the right root. `runStoryOp`\n    // resolves through `resolveStory` — realpath containment, per root — and\n    // hands the executor an ABSOLUTE path, from which `buildContext` derives\n    // its `basedir`. The guard here was added defensively during #3015's\n    // review and refused a write that was never wrong (#3019).\n    return runStoryOp<{ image: string }>(filePath, { operation: \"upload-beat-image\", root }, 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, root?: string): Promise<OpResult<{ image: string }>> {\n    // No root guard: the write is already in the right root. `runStoryOp`\n    // resolves through `resolveStory` — realpath containment, per root — and\n    // hands the executor an ABSOLUTE path, from which `buildContext` derives\n    // its `basedir`. The guard here was added defensively during #3015's\n    // review and refused a write that was never wrong (#3019).\n    return runStoryOp<{ image: string }>(filePath, { operation: \"upload-character-image\", root }, 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    return withBeatProgress(\n      context.studio.script.beats as MulmoBeat[],\n      (sessionType, beatIndex) => {\n        if (sessionType !== \"image\" && sessionType !== \"audio\") return;\n        onProgressEvent({ kind: sessionType, beatIndex });\n      },\n      async () => {\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      },\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, root?: string): Promise<OpResult<{ moviePath: string }>> {\n    const rootGuard = guardStoryGenerationRoot(root);\n    if (rootGuard) return rootGuard;\n    const ffmpeg = ffmpegGuard();\n    if (ffmpeg) return ffmpeg;\n    const resolved = resolveStory(filePath, root);\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, { root });\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, { root });\n      });\n      if (!result.ok) {\n        genError = result.error;\n        return opServerError(result.error);\n      }\n      const movieRef = outputRef(result.outputPath, filePath, root);\n      if (movieRef === null) return opServerError(\"generated movie is outside the registered stories root\");\n      return { ok: true, moviePath: movieRef };\n    } catch (err) {\n      genError = errorMessage(err);\n      return opServerError(genError);\n    } finally {\n      inFlightMovies.delete(absoluteFilePath);\n      publishGeneration(chatSessionId, \"movie\", filePath, \"\", true, { error: genError, root });\n    }\n  }\n\n  function triggerAutoBackgroundMovie(absoluteFilePath: string, wireFilePath: string, chatSessionId: string | undefined, root?: string): void {\n    // The same refusal as the foreground generation ops, reached the only way\n    // a `void` entry point can reach it. This one takes a `root` but never\n    // returned an `OpResult`, so the guard that covers every other generation\n    // did not cover the one generation nobody is waiting on — which is the\n    // worse half: a detached run corrupting the other root's pending state\n    // has no caller to see the failure (Codex P1 on #3015).\n    if (guardStoryGenerationRoot(root)) {\n      log.warn(\"refused an auto background movie in a non-default stories root\", { filePath: wireFilePath, root });\n      return;\n    }\n    if (inFlightMovies.has(absoluteFilePath)) return;\n    inFlightMovies.add(absoluteFilePath);\n    void runBackgroundMovieGeneration(absoluteFilePath, wireFilePath, chatSessionId, root);\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, root?: string): 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, { root });\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, { root });\n        setImmediate(() => publishGeneration(chatSessionId, eventKind, wireFilePath, key, true, { root }));\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 = errorMessage(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, { error: genError, root });\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: errorMessage(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    return withBeatProgress(\n      context.studio.script.beats as MulmoBeat[],\n      (sessionType, beatIndex) => {\n        if (sessionType !== \"image\") return;\n        onImageBeatDone(beatIndex);\n      },\n      async () => {\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      },\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, root?: string): Promise<OpResult<{ pdfPath: string }>> {\n    const rootGuard = guardStoryGenerationRoot(root);\n    if (rootGuard) return rootGuard;\n    const ffmpeg = ffmpegGuard();\n    if (ffmpeg) return ffmpeg;\n    const resolved = resolveStory(filePath, root);\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, { root });\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, { root });\n      });\n      if (!result.ok) {\n        genError = result.error;\n        return opServerError(result.error);\n      }\n      const pdfRef = outputRef(result.outputPath, filePath, root);\n      if (pdfRef === null) return opServerError(\"generated PDF is outside the registered stories root\");\n      return { ok: true, pdfPath: pdfRef };\n    } catch (err) {\n      genError = errorMessage(err);\n      return opServerError(genError);\n    } finally {\n      inFlightPdfs.delete(absoluteFilePath);\n      publishGeneration(chatSessionId, \"pdf\", filePath, \"\", true, { error: genError, root });\n    }\n  }\n\n  return {\n    backend,\n    toStoryRef,\n    outputRef,\n    resolveStory,\n    guardStoryWirePath,\n    guardStoryRootRegistered,\n    guardStoryWriteRoot,\n    artifactsForRoot,\n    guardStoryGenerationRoot,\n    ffmpegGuard,\n    runStoryOp,\n    publishGeneration,\n    publishScriptChanged,\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 { DEFAULT_ROOT, normalizeRoot } from \"../core/contract\";\nimport type { MulmoScriptExecuteContext } from \"../core/types\";\nimport type { MulmoScriptServerOps } from \"./ops\";\nimport { isRecord } from \"./support\";\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 */\n/**\n * Stamp the root a successful result acted in.\n *\n * Only on success: a failure carries `code` and `error`, and adding a root to\n * it would invite a reader to treat the pair as addressable when the call did\n * not happen. Only when NON-default, so a result for a call that named no root\n * stays byte-identical to what this package returned before roots existed —\n * which is what keeps every existing card working untouched.\n */\nfunction withRoot(result: unknown, root: string | undefined): unknown {\n  const normalized = normalizeRoot(root);\n  if (normalized === DEFAULT_ROOT) return result;\n  if (!isRecord(result) || result.ok !== true) return result;\n  return { ...result, root: normalized };\n}\n\n/** `undefined` when `root` is absent or a string; a failure envelope otherwise. */\nfunction guardSuppliedRoot(root: unknown): { ok: false; code: string; error: string } | undefined {\n  if (root === undefined || typeof root === \"string\") return undefined;\n  return { ok: false, code: \"bad_request\", error: `mulmoScript root must be a string, got ${typeof root}` };\n}\n\nexport function createMulmoScriptDispatchHandler(ops: MulmoScriptServerOps): MulmoScriptDispatchHandler {\n  /**\n   * The executor context for a write, bound to the root it names.\n   *\n   * One `FileOps` was held for the whole handler, so the executors — which\n   * take a WIRE path (`stories/…`) and resolve it against whatever FileOps\n   * they are given — wrote a named root's script into the DEFAULT root's\n   * identically-named file. Choosing here keeps `MulmoScriptExecuteContext`\n   * and every executor unchanged: the root never reaches them, only the right\n   * FileOps does (#3019).\n   */\n  function executeContextFor(root: string | undefined): MulmoScriptExecuteContext | null {\n    const artifacts = ops.artifactsForRoot(root);\n    if (artifacts === null) return null;\n    // `byPath` rides along when the host supplies it, so the dispatch route\n    // accepts the absolute `filePath` form on exactly the same terms as the\n    // host's REST route — one tool call must not mean two things depending on\n    // whether the View or the agent made it. It is root-independent: an\n    // absolute path is relative to nothing, so no root selects it.\n    return { files: { artifacts, ...(ops.backend.byPath ? { byPath: ops.backend.byPath } : {}) } };\n  }\n\n  async function saveKind(args: Record<string, unknown>): Promise<unknown> {\n    // Which root this write lands in — see `guardStoryWriteRoot` and\n    // `executeContextFor`.\n    const rootGuard = ops.guardStoryWriteRoot(str(args.root));\n    if (rootGuard) return fromOpFailure(rootGuard);\n    const guard = ops.guardStoryWirePath(args.filePath, str(args.root));\n    if (guard) return fromOpFailure(guard);\n    const context = executeContextFor(str(args.root));\n    if (context === null) return invalidArgs(\"save\");\n    const outcome = await executeMulmoScriptSave(context, {\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 rootGuard = ops.guardStoryWriteRoot(str(args.root));\n    if (rootGuard) return fromOpFailure(rootGuard);\n    const guard = ops.guardStoryWirePath(args.filePath, str(args.root));\n    if (guard) return fromOpFailure(guard);\n    const context = executeContextFor(str(args.root));\n    if (context === null) return invalidArgs(kind);\n    const outcome = kind === \"updateBeat\" ? await executeUpdateBeat(context, args) : await executeUpdateScript(context, args);\n    if (!outcome.ok) return fromPackageFailure(outcome);\n    // After the write landed, never before: a View that reloads on a failed write would\n    // discard the user's edit and show the old file back.\n    ops.publishScriptChanged(str(args.filePath) ?? \"\", str(args.origin), str(args.root));\n    return { ok: true };\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, str(args.root))) : invalidArgs(kind);\n    }\n    if (kind === \"characterImage\") {\n      const parsed = keyArgs(args);\n      return parsed ? envelope(await ops.characterImageOp(parsed.filePath, parsed.key, str(args.root))) : 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, str(args.root)));\n  }\n\n  /** Movie and PDF take the whole script; the other generate kinds take a beat\n   *  or a character within it. */\n  async function wholeScriptGenerationKind(kind: \"generateMovie\" | \"generatePdf\", args: Record<string, unknown>): Promise<unknown> {\n    const filePath = str(args.filePath);\n    if (!filePath) return invalidArgs(kind);\n    const chatSessionId = str(args.chatSessionId);\n    const root = str(args.root);\n    const result = kind === \"generateMovie\" ? await ops.generateMovieOp(filePath, chatSessionId, root) : await ops.generatePdfOp(filePath, chatSessionId, root);\n    return envelope(result);\n  }\n\n  async function generateKind(kind: string, args: Record<string, unknown>): Promise<unknown> {\n    if (kind === \"generateMovie\" || kind === \"generatePdf\") return wholeScriptGenerationKind(kind, args);\n    const chatSessionId = str(args.chatSessionId);\n    const root = str(args.root);\n    const force = args.force === true;\n    if (kind === \"renderCharacter\") {\n      const parsed = keyArgs(args);\n      return parsed ? envelope(await ops.renderCharacterOp({ ...parsed, force, chatSessionId, root })) : invalidArgs(kind);\n    }\n    const parsed = beatArgs(args);\n    if (!parsed) return invalidArgs(kind);\n    const result =\n      kind === \"renderBeat\"\n        ? await ops.renderBeatOp({ ...parsed, force, chatSessionId, root })\n        : await ops.generateBeatAudioOp({ ...parsed, force, chatSessionId, root });\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, str(args.root))) : 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, str(args.root)));\n  }\n\n  async function pendingKind(kind: string, args: Record<string, unknown>): Promise<unknown> {\n    const filePath = str(args.filePath);\n    if (!filePath) return invalidArgs(kind);\n    const root = str(args.root);\n    // An empty snapshot for an unknown root is indistinguishable from \"no\n    // work is running\" — see `guardStoryRootRegistered`.\n    const rootGuard = ops.guardStoryRootRegistered(root);\n    if (rootGuard) return fromOpFailure(rootGuard);\n    return { ok: true, pending: ops.pendingGenerations(filePath, root) };\n  }\n\n  // Nothing but routing below: every kind resolves to one named handler, so\n  // reading it answers \"where does this kind go\" without also having to read\n  // what any of them do.\n  /** Which handler serves this kind. Routing only — the caller tags the answer. */\n  async function route(kind: string, args: Record<string, unknown>): Promise<unknown> {\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\") return pendingKind(kind, args);\n    return { ok: false, code: \"bad_request\", error: `unknown mulmoScript dispatch kind \"${kind}\"` };\n  }\n\n  return async (args: Record<string, unknown>): Promise<unknown> => {\n    const kind = str(args.kind);\n    if (!kind) return invalidArgs(\"<missing>\");\n    // Once, at the only entry, so no per-kind reader can forget it. `str()`\n    // answers `undefined` for a number, `null`, an object — indistinguishable\n    // from a root that was never supplied, which every reader below then takes\n    // as the DEFAULT root. A host that serialises a root wrongly would have\n    // written to, and read from, the default root's identically-named script\n    // while believing it named another (Codex P2 on #3015). Absent stays\n    // default; present must be a string.\n    const malformedRoot = guardSuppliedRoot(args.root);\n    if (malformedRoot) return malformedRoot;\n    // Tagged HERE, once, rather than by each of the seventeen kinds. A host\n    // builds its cards from these results and a card's identity is the pair\n    // `(root, filePath)`, so a kind that forgot the tag would quietly collapse\n    // two repositories' identically-named decks onto one card. Threading it\n    // per-kind is exactly the shape #3015 got wrong over and over.\n    return withRoot(await route(kind, args), str(args.root));\n  };\n}\n"],"mappings":"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"}