@mulmoclaude/mulmoscript-plugin 1.1.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contract-DxKVCRQk.cjs.map +1 -1
- package/dist/contract-vY0niHkh.js.map +1 -1
- package/dist/core/contract.d.ts +1 -1
- package/dist/core/contract.d.ts.map +1 -1
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.d.ts.map +1 -1
- package/dist/server/ops.d.ts +5 -5
- package/dist/server/ops.d.ts.map +1 -1
- package/dist/server/types.d.ts +8 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server.cjs +7 -3
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +7 -3
- package/dist/server.js.map +1 -1
- package/dist/style.css +18 -18
- package/dist/vue/View.vue.d.ts +1 -1
- package/dist/vue/View.vue.d.ts.map +1 -1
- package/dist/vue/components/BeatLightbox.vue.d.ts +4 -4
- package/dist/vue/components/CharacterStrip.vue.d.ts +6 -6
- package/dist/vue/components/MulmoScriptToolbar.vue.d.ts +5 -5
- package/dist/vue/viewTypes.d.ts +2 -2
- package/dist/vue/viewTypes.d.ts.map +1 -1
- package/dist/vue.cjs +4 -4
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.js +4 -4
- package/dist/vue.js.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-DxKVCRQk.cjs","names":[],"sources":["../src/core/contract.ts"],"sourcesContent":["// Host-agnostic dispatch envelope for the presentMulmoScript View. The Vue\n// View is decoupled from any one host's REST surface: it calls\n// `useRuntime().dispatch({ kind, … })`, the host routes that to its\n// mulmoScript dispatch handler, and every response is an `{ ok: … }`\n// envelope so failures travel as data (no HTTP-status coupling, no\n// \"dispatch failed (500)\" prefixes in user-facing errors).\n//\n// Long-running generation (movie / PDF) is a single long-held dispatch that\n// resolves when the pipeline finishes; per-beat progress arrives on the\n// plugin pubsub channel (`GENERATION_EVENT`) instead of an SSE stream, which\n// also covers generations started elsewhere (background autoGenerateMovie,\n// another tab, the agent).\n\n/** One in-flight or per-beat generation notice, published on the plugin\n * pubsub `generation` channel and returned by the `pendingGenerations`\n * snapshot. Value strings mirror @mulmobridge/protocol's GENERATION_KINDS\n * so MulmoClaude's host bridge maps 1:1 without a lookup table. */\nexport interface MulmoScriptGenerationEvent {\n kind: \"beatImage\" | \"beatAudio\" | \"characterImage\" | \"movie\" | \"pdf\";\n /** Wire `stories/…` path of the script the generation belongs to. */\n filePath: string;\n /** beatIndex (as string) for beat*, character key for characterImage, \"\" for movie/pdf. */\n key: string;\n /** false = started, true = finished (reload the asset off disk). */\n done: boolean;\n /** Only set on done=true when the work failed. */\n error?: string;\n}\n\n/** Plugin pubsub event name the host publishes generation events on\n * (full channel: `plugin:<scope>:generation`). */\nexport const GENERATION_EVENT = \"generation\";\n\ninterface BeatRef {\n filePath: string;\n beatIndex: number;\n}\n\ninterface CharacterRef {\n filePath: string;\n key: string;\n}\n\n/** Session tag for hosts that surface per-session generation indicators\n * (MulmoClaude's sidebar). Optional everywhere; hosts without sessions\n * ignore it. */\ninterface SessionTag {\n chatSessionId?: string;\n}\n\nexport type MulmoScriptDispatchArgs =\n | ({ kind: \"save\" } & { filePath?: string; script?: unknown; filename?: string })\n | { kind: \"updateBeat\"; filePath: string; beatIndex: number; beat: unknown }\n | { kind: \"updateScript\"; filePath: string; script: unknown }\n | ({ kind: \"beatImage\" } & BeatRef)\n | ({ kind: \"beatAudio\" } & BeatRef)\n | ({ kind: \"beatMovie\" } & BeatRef)\n | ({ kind: \"renderBeat\" } & BeatRef & SessionTag & { force?: boolean })\n | ({ kind: \"generateBeatAudio\" } & BeatRef & SessionTag & { force?: boolean })\n | ({ kind: \"uploadBeatImage\" } & BeatRef & { imageData: string })\n | ({ kind: \"characterImage\" } & CharacterRef)\n | ({ kind: \"renderCharacter\" } & CharacterRef & SessionTag & { force?: boolean })\n | ({ kind: \"uploadCharacterImage\" } & CharacterRef & { imageData: string })\n | ({ kind: \"movieStatus\" } & { filePath: string })\n | ({ kind: \"pdfStatus\" } & { filePath: string })\n | ({ kind: \"generateMovie\" } & { filePath: string } & SessionTag)\n | ({ kind: \"generatePdf\" } & { filePath: string } & SessionTag)\n | { kind: \"pendingGenerations\"; filePath: string };\n\nexport type MulmoScriptDispatchKind = MulmoScriptDispatchArgs[\"kind\"];\n\n/** Failure half of every dispatch response. `code` mirrors the phase-1\n * outcome codes so a host can log/telemetry on it; the View only reads\n * `error`. */\nexport interface DispatchFailure {\n ok: false;\n code?: \"bad_request\" | \"not_found\" | \"server_error\";\n error: string;\n}\n\nexport type DispatchEnvelope<T> = ({ ok: true } & T) | DispatchFailure;\n\n/** Maps a dispatch `kind` to its success payload so the View's transport\n * can call `dispatch` without casts at every site. */\nexport interface MulmoScriptDispatchResult {\n save: { script: Record<string, unknown>; filePath: string; message: string };\n updateBeat: Record<string, never>;\n updateScript: Record<string, never>;\n beatImage: { image: string | null };\n beatAudio: { audio: string | null };\n beatMovie: { moviePath: string | null };\n renderBeat: { image: string };\n generateBeatAudio: { audio: string };\n uploadBeatImage: { image: string };\n characterImage: { image: string | null };\n renderCharacter: { image: string };\n uploadCharacterImage: { image: string };\n movieStatus: { moviePath: string | null };\n pdfStatus: { pdfPath: string | null };\n generateMovie: { moviePath: string };\n generatePdf: { pdfPath: string };\n pendingGenerations: { pending: MulmoScriptGenerationEvent[] };\n}\n"],"mappings":";;;AA+BA,IAAa,mBAAmB"}
|
|
1
|
+
{"version":3,"file":"contract-DxKVCRQk.cjs","names":[],"sources":["../src/core/contract.ts"],"sourcesContent":["// Host-agnostic dispatch envelope for the presentMulmoScript View. The Vue\n// View is decoupled from any one host's REST surface: it calls\n// `useRuntime().dispatch({ kind, … })`, the host routes that to its\n// mulmoScript dispatch handler, and every response is an `{ ok: … }`\n// envelope so failures travel as data (no HTTP-status coupling, no\n// \"dispatch failed (500)\" prefixes in user-facing errors).\n//\n// Long-running generation (movie / PDF) is a single long-held dispatch that\n// resolves when the pipeline finishes; per-beat progress arrives on the\n// plugin pubsub channel (`GENERATION_EVENT`) instead of an SSE stream, which\n// also covers generations started elsewhere (background autoGenerateMovie,\n// another tab, the agent).\n\n/** One in-flight or per-beat generation notice, published on the plugin\n * pubsub `generation` channel and returned by the `pendingGenerations`\n * snapshot. Value strings mirror @mulmobridge/protocol's GENERATION_KINDS\n * so MulmoClaude's host bridge maps 1:1 without a lookup table. */\nexport interface MulmoScriptGenerationEvent {\n kind: \"beatImage\" | \"beatAudio\" | \"characterImage\" | \"movie\" | \"pdf\";\n /** Wire `stories/…` path of the script the generation belongs to. */\n filePath: string;\n /** beatIndex (as string) for beat*, character key for characterImage, \"\" for movie/pdf. */\n key: string;\n /** false = started, true = finished (reload the asset off disk). */\n done: boolean;\n /** Only set on done=true when the work failed. */\n error?: string;\n}\n\n/** Plugin pubsub event name the host publishes generation events on\n * (full channel: `plugin:<scope>:generation`). */\nexport const GENERATION_EVENT = \"generation\";\n\ninterface BeatRef {\n filePath: string;\n beatIndex: number;\n}\n\ninterface CharacterRef {\n filePath: string;\n key: string;\n}\n\n/** Session tag for hosts that surface per-session generation indicators\n * (MulmoClaude's sidebar). Optional everywhere; hosts without sessions\n * ignore it. */\ninterface SessionTag {\n chatSessionId?: string | undefined;\n}\n\nexport type MulmoScriptDispatchArgs =\n | ({ kind: \"save\" } & { filePath?: string; script?: unknown; filename?: string })\n | { kind: \"updateBeat\"; filePath: string; beatIndex: number; beat: unknown }\n | { kind: \"updateScript\"; filePath: string; script: unknown }\n | ({ kind: \"beatImage\" } & BeatRef)\n | ({ kind: \"beatAudio\" } & BeatRef)\n | ({ kind: \"beatMovie\" } & BeatRef)\n | ({ kind: \"renderBeat\" } & BeatRef & SessionTag & { force?: boolean })\n | ({ kind: \"generateBeatAudio\" } & BeatRef & SessionTag & { force?: boolean })\n | ({ kind: \"uploadBeatImage\" } & BeatRef & { imageData: string })\n | ({ kind: \"characterImage\" } & CharacterRef)\n | ({ kind: \"renderCharacter\" } & CharacterRef & SessionTag & { force?: boolean })\n | ({ kind: \"uploadCharacterImage\" } & CharacterRef & { imageData: string })\n | ({ kind: \"movieStatus\" } & { filePath: string })\n | ({ kind: \"pdfStatus\" } & { filePath: string })\n | ({ kind: \"generateMovie\" } & { filePath: string } & SessionTag)\n | ({ kind: \"generatePdf\" } & { filePath: string } & SessionTag)\n | { kind: \"pendingGenerations\"; filePath: string };\n\nexport type MulmoScriptDispatchKind = MulmoScriptDispatchArgs[\"kind\"];\n\n/** Failure half of every dispatch response. `code` mirrors the phase-1\n * outcome codes so a host can log/telemetry on it; the View only reads\n * `error`. */\nexport interface DispatchFailure {\n ok: false;\n code?: \"bad_request\" | \"not_found\" | \"server_error\";\n error: string;\n}\n\nexport type DispatchEnvelope<T> = ({ ok: true } & T) | DispatchFailure;\n\n/** Maps a dispatch `kind` to its success payload so the View's transport\n * can call `dispatch` without casts at every site. */\nexport interface MulmoScriptDispatchResult {\n save: { script: Record<string, unknown>; filePath: string; message: string };\n updateBeat: Record<string, never>;\n updateScript: Record<string, never>;\n beatImage: { image: string | null };\n beatAudio: { audio: string | null };\n beatMovie: { moviePath: string | null };\n renderBeat: { image: string };\n generateBeatAudio: { audio: string };\n uploadBeatImage: { image: string };\n characterImage: { image: string | null };\n renderCharacter: { image: string };\n uploadCharacterImage: { image: string };\n movieStatus: { moviePath: string | null };\n pdfStatus: { pdfPath: string | null };\n generateMovie: { moviePath: string };\n generatePdf: { pdfPath: string };\n pendingGenerations: { pending: MulmoScriptGenerationEvent[] };\n}\n"],"mappings":";;;AA+BA,IAAa,mBAAmB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-vY0niHkh.js","names":[],"sources":["../src/core/contract.ts"],"sourcesContent":["// Host-agnostic dispatch envelope for the presentMulmoScript View. The Vue\n// View is decoupled from any one host's REST surface: it calls\n// `useRuntime().dispatch({ kind, … })`, the host routes that to its\n// mulmoScript dispatch handler, and every response is an `{ ok: … }`\n// envelope so failures travel as data (no HTTP-status coupling, no\n// \"dispatch failed (500)\" prefixes in user-facing errors).\n//\n// Long-running generation (movie / PDF) is a single long-held dispatch that\n// resolves when the pipeline finishes; per-beat progress arrives on the\n// plugin pubsub channel (`GENERATION_EVENT`) instead of an SSE stream, which\n// also covers generations started elsewhere (background autoGenerateMovie,\n// another tab, the agent).\n\n/** One in-flight or per-beat generation notice, published on the plugin\n * pubsub `generation` channel and returned by the `pendingGenerations`\n * snapshot. Value strings mirror @mulmobridge/protocol's GENERATION_KINDS\n * so MulmoClaude's host bridge maps 1:1 without a lookup table. */\nexport interface MulmoScriptGenerationEvent {\n kind: \"beatImage\" | \"beatAudio\" | \"characterImage\" | \"movie\" | \"pdf\";\n /** Wire `stories/…` path of the script the generation belongs to. */\n filePath: string;\n /** beatIndex (as string) for beat*, character key for characterImage, \"\" for movie/pdf. */\n key: string;\n /** false = started, true = finished (reload the asset off disk). */\n done: boolean;\n /** Only set on done=true when the work failed. */\n error?: string;\n}\n\n/** Plugin pubsub event name the host publishes generation events on\n * (full channel: `plugin:<scope>:generation`). */\nexport const GENERATION_EVENT = \"generation\";\n\ninterface BeatRef {\n filePath: string;\n beatIndex: number;\n}\n\ninterface CharacterRef {\n filePath: string;\n key: string;\n}\n\n/** Session tag for hosts that surface per-session generation indicators\n * (MulmoClaude's sidebar). Optional everywhere; hosts without sessions\n * ignore it. */\ninterface SessionTag {\n chatSessionId?: string;\n}\n\nexport type MulmoScriptDispatchArgs =\n | ({ kind: \"save\" } & { filePath?: string; script?: unknown; filename?: string })\n | { kind: \"updateBeat\"; filePath: string; beatIndex: number; beat: unknown }\n | { kind: \"updateScript\"; filePath: string; script: unknown }\n | ({ kind: \"beatImage\" } & BeatRef)\n | ({ kind: \"beatAudio\" } & BeatRef)\n | ({ kind: \"beatMovie\" } & BeatRef)\n | ({ kind: \"renderBeat\" } & BeatRef & SessionTag & { force?: boolean })\n | ({ kind: \"generateBeatAudio\" } & BeatRef & SessionTag & { force?: boolean })\n | ({ kind: \"uploadBeatImage\" } & BeatRef & { imageData: string })\n | ({ kind: \"characterImage\" } & CharacterRef)\n | ({ kind: \"renderCharacter\" } & CharacterRef & SessionTag & { force?: boolean })\n | ({ kind: \"uploadCharacterImage\" } & CharacterRef & { imageData: string })\n | ({ kind: \"movieStatus\" } & { filePath: string })\n | ({ kind: \"pdfStatus\" } & { filePath: string })\n | ({ kind: \"generateMovie\" } & { filePath: string } & SessionTag)\n | ({ kind: \"generatePdf\" } & { filePath: string } & SessionTag)\n | { kind: \"pendingGenerations\"; filePath: string };\n\nexport type MulmoScriptDispatchKind = MulmoScriptDispatchArgs[\"kind\"];\n\n/** Failure half of every dispatch response. `code` mirrors the phase-1\n * outcome codes so a host can log/telemetry on it; the View only reads\n * `error`. */\nexport interface DispatchFailure {\n ok: false;\n code?: \"bad_request\" | \"not_found\" | \"server_error\";\n error: string;\n}\n\nexport type DispatchEnvelope<T> = ({ ok: true } & T) | DispatchFailure;\n\n/** Maps a dispatch `kind` to its success payload so the View's transport\n * can call `dispatch` without casts at every site. */\nexport interface MulmoScriptDispatchResult {\n save: { script: Record<string, unknown>; filePath: string; message: string };\n updateBeat: Record<string, never>;\n updateScript: Record<string, never>;\n beatImage: { image: string | null };\n beatAudio: { audio: string | null };\n beatMovie: { moviePath: string | null };\n renderBeat: { image: string };\n generateBeatAudio: { audio: string };\n uploadBeatImage: { image: string };\n characterImage: { image: string | null };\n renderCharacter: { image: string };\n uploadCharacterImage: { image: string };\n movieStatus: { moviePath: string | null };\n pdfStatus: { pdfPath: string | null };\n generateMovie: { moviePath: string };\n generatePdf: { pdfPath: string };\n pendingGenerations: { pending: MulmoScriptGenerationEvent[] };\n}\n"],"mappings":";;;AA+BA,IAAa,mBAAmB"}
|
|
1
|
+
{"version":3,"file":"contract-vY0niHkh.js","names":[],"sources":["../src/core/contract.ts"],"sourcesContent":["// Host-agnostic dispatch envelope for the presentMulmoScript View. The Vue\n// View is decoupled from any one host's REST surface: it calls\n// `useRuntime().dispatch({ kind, … })`, the host routes that to its\n// mulmoScript dispatch handler, and every response is an `{ ok: … }`\n// envelope so failures travel as data (no HTTP-status coupling, no\n// \"dispatch failed (500)\" prefixes in user-facing errors).\n//\n// Long-running generation (movie / PDF) is a single long-held dispatch that\n// resolves when the pipeline finishes; per-beat progress arrives on the\n// plugin pubsub channel (`GENERATION_EVENT`) instead of an SSE stream, which\n// also covers generations started elsewhere (background autoGenerateMovie,\n// another tab, the agent).\n\n/** One in-flight or per-beat generation notice, published on the plugin\n * pubsub `generation` channel and returned by the `pendingGenerations`\n * snapshot. Value strings mirror @mulmobridge/protocol's GENERATION_KINDS\n * so MulmoClaude's host bridge maps 1:1 without a lookup table. */\nexport interface MulmoScriptGenerationEvent {\n kind: \"beatImage\" | \"beatAudio\" | \"characterImage\" | \"movie\" | \"pdf\";\n /** Wire `stories/…` path of the script the generation belongs to. */\n filePath: string;\n /** beatIndex (as string) for beat*, character key for characterImage, \"\" for movie/pdf. */\n key: string;\n /** false = started, true = finished (reload the asset off disk). */\n done: boolean;\n /** Only set on done=true when the work failed. */\n error?: string;\n}\n\n/** Plugin pubsub event name the host publishes generation events on\n * (full channel: `plugin:<scope>:generation`). */\nexport const GENERATION_EVENT = \"generation\";\n\ninterface BeatRef {\n filePath: string;\n beatIndex: number;\n}\n\ninterface CharacterRef {\n filePath: string;\n key: string;\n}\n\n/** Session tag for hosts that surface per-session generation indicators\n * (MulmoClaude's sidebar). Optional everywhere; hosts without sessions\n * ignore it. */\ninterface SessionTag {\n chatSessionId?: string | undefined;\n}\n\nexport type MulmoScriptDispatchArgs =\n | ({ kind: \"save\" } & { filePath?: string; script?: unknown; filename?: string })\n | { kind: \"updateBeat\"; filePath: string; beatIndex: number; beat: unknown }\n | { kind: \"updateScript\"; filePath: string; script: unknown }\n | ({ kind: \"beatImage\" } & BeatRef)\n | ({ kind: \"beatAudio\" } & BeatRef)\n | ({ kind: \"beatMovie\" } & BeatRef)\n | ({ kind: \"renderBeat\" } & BeatRef & SessionTag & { force?: boolean })\n | ({ kind: \"generateBeatAudio\" } & BeatRef & SessionTag & { force?: boolean })\n | ({ kind: \"uploadBeatImage\" } & BeatRef & { imageData: string })\n | ({ kind: \"characterImage\" } & CharacterRef)\n | ({ kind: \"renderCharacter\" } & CharacterRef & SessionTag & { force?: boolean })\n | ({ kind: \"uploadCharacterImage\" } & CharacterRef & { imageData: string })\n | ({ kind: \"movieStatus\" } & { filePath: string })\n | ({ kind: \"pdfStatus\" } & { filePath: string })\n | ({ kind: \"generateMovie\" } & { filePath: string } & SessionTag)\n | ({ kind: \"generatePdf\" } & { filePath: string } & SessionTag)\n | { kind: \"pendingGenerations\"; filePath: string };\n\nexport type MulmoScriptDispatchKind = MulmoScriptDispatchArgs[\"kind\"];\n\n/** Failure half of every dispatch response. `code` mirrors the phase-1\n * outcome codes so a host can log/telemetry on it; the View only reads\n * `error`. */\nexport interface DispatchFailure {\n ok: false;\n code?: \"bad_request\" | \"not_found\" | \"server_error\";\n error: string;\n}\n\nexport type DispatchEnvelope<T> = ({ ok: true } & T) | DispatchFailure;\n\n/** Maps a dispatch `kind` to its success payload so the View's transport\n * can call `dispatch` without casts at every site. */\nexport interface MulmoScriptDispatchResult {\n save: { script: Record<string, unknown>; filePath: string; message: string };\n updateBeat: Record<string, never>;\n updateScript: Record<string, never>;\n beatImage: { image: string | null };\n beatAudio: { audio: string | null };\n beatMovie: { moviePath: string | null };\n renderBeat: { image: string };\n generateBeatAudio: { audio: string };\n uploadBeatImage: { image: string };\n characterImage: { image: string | null };\n renderCharacter: { image: string };\n uploadCharacterImage: { image: string };\n movieStatus: { moviePath: string | null };\n pdfStatus: { pdfPath: string | null };\n generateMovie: { moviePath: string };\n generatePdf: { pdfPath: string };\n pendingGenerations: { pending: MulmoScriptGenerationEvent[] };\n}\n"],"mappings":";;;AA+BA,IAAa,mBAAmB"}
|
package/dist/core/contract.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ interface CharacterRef {
|
|
|
28
28
|
* (MulmoClaude's sidebar). Optional everywhere; hosts without sessions
|
|
29
29
|
* ignore it. */
|
|
30
30
|
interface SessionTag {
|
|
31
|
-
chatSessionId?: string;
|
|
31
|
+
chatSessionId?: string | undefined;
|
|
32
32
|
}
|
|
33
33
|
export type MulmoScriptDispatchArgs = ({
|
|
34
34
|
kind: "save";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/core/contract.ts"],"names":[],"mappings":"AAaA;;;oEAGoE;AACpE,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,WAAW,GAAG,WAAW,GAAG,gBAAgB,GAAG,OAAO,GAAG,KAAK,CAAC;IACrE,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB,2FAA2F;IAC3F,GAAG,EAAE,MAAM,CAAC;IACZ,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAC;IACd,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;mDACmD;AACnD,eAAO,MAAM,gBAAgB,eAAe,CAAC;AAE7C,UAAU,OAAO;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,YAAY;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;iBAEiB;AACjB,UAAU,UAAU;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/core/contract.ts"],"names":[],"mappings":"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"}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -6,9 +6,9 @@ import type { MulmoScript } from "@mulmocast/types";
|
|
|
6
6
|
* by hosts that have a movie backend (the package core ignores it). */
|
|
7
7
|
export interface SaveMulmoScriptArgs {
|
|
8
8
|
script?: unknown;
|
|
9
|
-
filename?: string;
|
|
10
|
-
filePath?: string;
|
|
11
|
-
autoGenerateMovie?: boolean;
|
|
9
|
+
filename?: string | undefined;
|
|
10
|
+
filePath?: string | undefined;
|
|
11
|
+
autoGenerateMovie?: boolean | undefined;
|
|
12
12
|
}
|
|
13
13
|
/** Result payload that drives the View. `filePath` is the historical
|
|
14
14
|
* `stories/<name>.json` wire form every mulmoScript endpoint keys on. */
|
package/dist/core/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;wEAGwE;AACxE,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;wEAGwE;AACxE,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,iBAAiB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACzC;AAED;0EAC0E;AAC1E,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;iEAIiE;AACjE,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;CAC/B"}
|
package/dist/server/ops.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MulmoBeat, MulmoStudioContext } from "@mulmocast/types";
|
|
2
2
|
import type { MulmoScriptGenerationEvent } from "../core/contract";
|
|
3
|
-
import type {
|
|
3
|
+
import type { GenerateOpArgsWith, MovieGenerationResult, MovieProgressEvent, MulmoScriptServerBackend, OpFailure, OpResult, PdfGenerationResult } from "./types";
|
|
4
4
|
type GenerationKind = MulmoScriptGenerationEvent["kind"];
|
|
5
5
|
export declare const PDF_MODE: "slide";
|
|
6
6
|
export declare const PDF_SIZE: "a4";
|
|
@@ -14,7 +14,7 @@ export interface RunStoryOpDeps {
|
|
|
14
14
|
buildContext?: (absoluteFilePath: string, force?: boolean) => Promise<StoryContext | undefined>;
|
|
15
15
|
}
|
|
16
16
|
export interface RunStoryOpOptions<T> {
|
|
17
|
-
force?: boolean;
|
|
17
|
+
force?: boolean | undefined;
|
|
18
18
|
/**
|
|
19
19
|
* Op-specific tag included in the failure log so dashboards can
|
|
20
20
|
* distinguish which op is failing (e.g. `"generate-beat-audio"`).
|
|
@@ -69,13 +69,13 @@ export declare function createMulmoScriptServerOps(backend: MulmoScriptServerBac
|
|
|
69
69
|
pdfStatusOp: (filePath: string) => Promise<OpResult<{
|
|
70
70
|
pdfPath: string | null;
|
|
71
71
|
}>>;
|
|
72
|
-
renderBeatOp: (args:
|
|
72
|
+
renderBeatOp: (args: GenerateOpArgsWith<"filePath" | "beatIndex">) => Promise<OpResult<{
|
|
73
73
|
image: string;
|
|
74
74
|
}>>;
|
|
75
|
-
generateBeatAudioOp: (args:
|
|
75
|
+
generateBeatAudioOp: (args: GenerateOpArgsWith<"filePath" | "beatIndex">) => Promise<OpResult<{
|
|
76
76
|
audio: string;
|
|
77
77
|
}>>;
|
|
78
|
-
renderCharacterOp: (args:
|
|
78
|
+
renderCharacterOp: (args: GenerateOpArgsWith<"filePath" | "key">) => Promise<OpResult<{
|
|
79
79
|
image: string;
|
|
80
80
|
}>>;
|
|
81
81
|
uploadBeatImageOp: (filePath: string, beatIndex: number, imageData: string) => Promise<OpResult<{
|
package/dist/server/ops.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ops.d.ts","sourceRoot":"","sources":["../../src/server/ops.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,SAAS,EAAyB,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC7F,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAMnE,OAAO,KAAK,EACV,
|
|
1
|
+
{"version":3,"file":"ops.d.ts","sourceRoot":"","sources":["../../src/server/ops.ts"],"names":[],"mappings":"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"}
|
package/dist/server/types.d.ts
CHANGED
|
@@ -18,6 +18,14 @@ export interface GenerateOpArgs {
|
|
|
18
18
|
force?: boolean | undefined;
|
|
19
19
|
chatSessionId?: string | undefined;
|
|
20
20
|
}
|
|
21
|
+
/** `GenerateOpArgs` with `K` promoted to genuinely required. `Required<Pick<…>>`
|
|
22
|
+
* does NOT work here: under `exactOptionalPropertyTypes` the `-?` modifier drops
|
|
23
|
+
* only the `?`, leaving the explicitly declared `| undefined` in place, so the
|
|
24
|
+
* op body still sees `T | undefined`. `Omit` for the rest, because intersecting
|
|
25
|
+
* the whole interface would re-introduce the optional declaration. */
|
|
26
|
+
export type GenerateOpArgsWith<K extends keyof GenerateOpArgs> = {
|
|
27
|
+
[P in K]-?: Exclude<GenerateOpArgs[P], undefined>;
|
|
28
|
+
} & Omit<GenerateOpArgs, K>;
|
|
21
29
|
export type MovieGenerationResult = {
|
|
22
30
|
ok: true;
|
|
23
31
|
outputPath: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAEnE,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,KAAK,CAAC;IACV;6CACyC;IACzC,IAAI,EAAE,aAAa,GAAG,WAAW,GAAG,cAAc,GAAG,aAAa,CAAC;IACnE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC;AAEzD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED,MAAM,MAAM,qBAAqB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AACpG,MAAM,MAAM,mBAAmB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAElG,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,mIAAmI;AACnI,MAAM,MAAM,oBAAoB,GAAG,aAAa,CAAC;AAEjD;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC;mEAC+D;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB;yEACqE;IACrE,SAAS,EAAE,OAAO,CAAC;IACnB;gEAC4D;IAC5D,eAAe,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpF;;8DAE0D;IAC1D,iBAAiB,CAAC,EAAE,MAAM,OAAO,GAAG,SAAS,CAAC;IAC9C;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;IACnG,GAAG,CAAC,EAAE,oBAAoB,CAAC;CAC5B"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAEnE,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,KAAK,CAAC;IACV;6CACyC;IACzC,IAAI,EAAE,aAAa,GAAG,WAAW,GAAG,cAAc,GAAG,aAAa,CAAC;IACnE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC;AAEzD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED;;;;uEAIuE;AACvE,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,MAAM,cAAc,IAAI;KAAG,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC;CAAE,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;AAEjJ,MAAM,MAAM,qBAAqB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AACpG,MAAM,MAAM,mBAAmB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAElG,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,mIAAmI;AACnI,MAAM,MAAM,oBAAoB,GAAG,aAAa,CAAC;AAEjD;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC;mEAC+D;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB;yEACqE;IACrE,SAAS,EAAE,OAAO,CAAC;IACnB;gEAC4D;IAC5D,eAAe,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpF;;8DAE0D;IAC1D,iBAAiB,CAAC,EAAE,MAAM,OAAO,GAAG,SAAS,CAAC;IAC9C;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;IACnG,GAAG,CAAC,EAAE,oBAAoB,CAAC;CAC5B"}
|
package/dist/server.cjs
CHANGED
|
@@ -381,6 +381,10 @@ function createMulmoScriptServerOps(backend) {
|
|
|
381
381
|
})
|
|
382
382
|
}, async ({ context }) => {
|
|
383
383
|
const beat = context.studio.script.beats[beatIndex];
|
|
384
|
+
if (!beat) return {
|
|
385
|
+
ok: true,
|
|
386
|
+
audio: null
|
|
387
|
+
};
|
|
384
388
|
const audioPath = (0, mulmocast.getBeatAudioPathOrUrl)(beat.text ?? "", context, beat, context.lang);
|
|
385
389
|
if (!audioPath || !(0, fs.existsSync)(audioPath)) return {
|
|
386
390
|
ok: true,
|
|
@@ -467,7 +471,7 @@ function createMulmoScriptServerOps(backend) {
|
|
|
467
471
|
await (0, mulmocast.generateBeatImage)({
|
|
468
472
|
index: beatIndex,
|
|
469
473
|
context,
|
|
470
|
-
|
|
474
|
+
...force ? { args: { forceImage: true } } : {}
|
|
471
475
|
});
|
|
472
476
|
const { imagePath } = (0, mulmocast.getBeatPngImagePath)(context, beatIndex);
|
|
473
477
|
if (!(0, fs.existsSync)(imagePath)) return opServerError("Image was not generated");
|
|
@@ -494,7 +498,7 @@ function createMulmoScriptServerOps(backend) {
|
|
|
494
498
|
}, async ({ context }) => {
|
|
495
499
|
await (0, mulmocast.generateBeatAudio)(beatIndex, context, { settings: process.env });
|
|
496
500
|
const beat = context.studio.script.beats[beatIndex];
|
|
497
|
-
const audioPath = context.studio.beats[beatIndex]?.audioFile ?? (0, mulmocast.getBeatAudioPathOrUrl)(beat.text ?? "", context, beat, context.lang);
|
|
501
|
+
const audioPath = context.studio.beats[beatIndex]?.audioFile ?? (beat ? (0, mulmocast.getBeatAudioPathOrUrl)(beat.text ?? "", context, beat, context.lang) : void 0);
|
|
498
502
|
if (!audioPath || !(0, fs.existsSync)(audioPath)) {
|
|
499
503
|
log.error("audio was not generated", {
|
|
500
504
|
beatIndex,
|
|
@@ -536,7 +540,7 @@ function createMulmoScriptServerOps(backend) {
|
|
|
536
540
|
key,
|
|
537
541
|
index,
|
|
538
542
|
image: imageEntry,
|
|
539
|
-
force
|
|
543
|
+
...force !== void 0 ? { force } : {}
|
|
540
544
|
});
|
|
541
545
|
if (!(0, fs.existsSync)(imagePath)) return opServerError("Character image was not generated");
|
|
542
546
|
return {
|
package/dist/server.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.cjs","names":[],"sources":["../src/server/support.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","// 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 {\n getFileObject,\n initializeContextFromFiles,\n generateBeatImage,\n getBeatPngImagePath,\n generateBeatAudio,\n getBeatAudioPathOrUrl,\n getBeatAnimatedVideoPath,\n getBeatMoviePaths,\n generateReferenceImage,\n getReferenceImagePath,\n images,\n audio,\n movie,\n movieFilePath,\n pdf,\n pdfFilePath,\n setGraphAILogger,\n addSessionProgressCallback,\n removeSessionProgressCallback,\n} from \"mulmocast\";\nimport type { MulmoBeat, MulmoImagePromptMedia, MulmoStudioContext } from \"@mulmocast/types\";\nimport type { MulmoScriptGenerationEvent } from \"../core/contract\";\nimport { normalizeStoryPath } from \"../core/paths\";\nimport { errorMessage } from \"@mulmoclaude/common\";\nimport { resolveWithinRoot } from \"@mulmoclaude/core/files\";\nimport { fileToDataUri, stripDataUri } from \"./support\";\nimport { enableGraphAIErrorCapture, setMulmoErrorCaptureLogger, withMulmoErrorCapture } from \"./mulmoErrorCapture\";\nimport type {\n GenerateOpArgs,\n MovieGenerationResult,\n MovieProgressEvent,\n MulmoScriptServerBackend,\n MulmoScriptServerLog,\n OpFailure,\n OpResult,\n PdfGenerationResult,\n} from \"./types\";\n\ntype GenerationKind = MulmoScriptGenerationEvent[\"kind\"];\n\n// We pin pdfMode=\"slide\" + pdfSize=\"a4\" — that's the configured default\n// for the storyboard editor; mulmocast's other modes (talk / handout /\n// letter) stay reachable via the CLI for power users. (#1614)\nexport const PDF_MODE = \"slide\" as const;\nexport const PDF_SIZE = \"a4\" as const;\n\nfunction opBadRequest(error: string): OpFailure {\n return { ok: false, code: \"bad_request\", error };\n}\n\nfunction opNotFound(error: string): OpFailure {\n return { ok: false, code: \"not_found\", error };\n}\n\nfunction opServerError(error: string): OpFailure {\n return { ok: false, code: \"server_error\", error };\n}\n\nconst NOOP_LOG: MulmoScriptServerLog = { info: () => {}, warn: () => {}, error: () => {} };\n\n// Helper: build mulmo context for a story file. The explicit return\n// annotation keeps declaration emit portable — the inferred type would\n// reference mulmocast's internal usage-collector path.\nexport async function buildContext(absoluteFilePath: string, force = false): Promise<MulmoStudioContext | null | undefined> {\n // setGraphAILogger(false) silences GraphAI's chatty info/debug output\n // but also its error level — re-enable error capture so a failed\n // generation surfaces the real provider error, not just mulmocast's\n // generic \"generate error\" wrapper.\n setGraphAILogger(false);\n enableGraphAIErrorCapture();\n const files = getFileObject({\n file: absoluteFilePath,\n basedir: path.dirname(absoluteFilePath),\n grouped: true,\n });\n return initializeContextFromFiles(files, true, force);\n}\n\n// Awaited context type used by every op that calls buildContext.\nexport type StoryContext = NonNullable<Awaited<ReturnType<typeof buildContext>>>;\n\nexport interface RunStoryOpDeps {\n resolveStory?: (filePath: string) => { ok: true; absolutePath: string } | OpFailure;\n buildContext?: (absoluteFilePath: string, force?: boolean) => Promise<StoryContext | undefined>;\n}\n\nexport interface RunStoryOpOptions<T> {\n force?: boolean;\n /**\n * Op-specific tag included in the failure log so dashboards can\n * distinguish which op is failing (e.g. `\"generate-beat-audio\"`).\n * Falls back to a generic `\"op failed\"` entry when omitted.\n */\n operation?: string;\n /**\n * Soft-fail override for `buildContext` returning undefined. Some\n * ops (e.g. `beatAudio`) historically returned a 200 `{ audio: null }`\n * in that case so the frontend can silently retry. If provided, this\n * callback returns the fallback result instead of the default\n * server_error \"Failed to initialize mulmo context\".\n */\n onContextMissing?: () => OpResult<T>;\n}\n\n// Map each beat to its array index, keyed by beat.id (falling back to\n// a synthetic `__index__<n>` for id-less beats). Shared by the movie\n// and PDF pipelines to translate mulmocast's per-beat progress events\n// (which carry the beat id) back into an index the UI can address.\nexport function buildBeatIdIndex(beats: MulmoBeat[]): Map<string, number> {\n const idToIndex = new Map<string, number>();\n beats.forEach((beat, index) => {\n const key = beat.id ?? `__index__${index}`;\n idToIndex.set(key, index);\n });\n return idToIndex;\n}\n\n// 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): string {\n return JSON.stringify([kind, filePath, key]);\n}\n\n/**\n * Build the per-host mulmoScript server ops instance. One instance per\n * process — it owns the in-flight movie/PDF dedup sets and the\n * generation-state tracker, and binds the injected host backend.\n */\nexport function createMulmoScriptServerOps(backend: MulmoScriptServerBackend) {\n const log = backend.log ?? NOOP_LOG;\n setMulmoErrorCaptureLogger(log);\n const storiesDir = path.resolve(backend.storiesDir);\n\n // ── Story path infrastructure ─────────────────────────────────\n\n // The download / status ops expect \"stories/<rel>\" (historical\n // convention, independent of the on-disk location) — the wire format\n // every endpoint keys on. Relativize against the REALPATH root when it\n // resolves: with a symlinked stories dir, mulmocast returns output\n // paths under the link's target, and relativizing against the link\n // itself would produce a traversal-like \"stories/../../…\" ref that\n // resolveStory then rejects (CodeRabbit on #2137).\n function toStoryRef(absolutePath: string): string {\n const root = ensureStoriesReal() ?? storiesDir;\n const rel = path.relative(root, absolutePath).split(path.sep).join(\"/\");\n return rel ? `stories/${rel}` : \"stories\";\n }\n\n // Lazily realpath the stories dir on first use. We can't realpath at\n // instance creation because the directory may not exist yet (it's\n // created on demand by the save route). The cache is invalidated\n // never — once the dir exists, its realpath is stable.\n let storiesRealCache: string | null = null;\n function ensureStoriesReal(): string | null {\n if (storiesRealCache) return storiesRealCache;\n try {\n mkdirSync(storiesDir, { recursive: true });\n storiesRealCache = realpathSync(storiesDir);\n return storiesRealCache;\n } catch {\n return null;\n }\n }\n\n /**\n * Resolve and validate a stories wire path to its absolute realpath.\n *\n * Uses the realpath-based resolveWithinRoot helper to defeat\n * symlink-based escapes. Callers pass wire paths like\n * \"stories/foo.json\" or \"stories/__movies__/bar.mp4\". We strip the\n * leading \"stories/\" segment and resolve the remainder against the\n * realpath of the stories directory itself — this works whether\n * stories/ is a regular directory or a legitimate symlink to another\n * location. ENOENT and traversal are distinguished (404 vs 400).\n */\n function resolveStory(filePath: string): { ok: true; absolutePath: string } | OpFailure {\n const storiesReal = ensureStoriesReal();\n if (!storiesReal) {\n return opServerError(\"stories directory not available\");\n }\n // Reject absolute paths and parent traversal at the syntactic\n // level — defense in depth on top of the realpath check below.\n if (path.isAbsolute(filePath)) {\n return opBadRequest(\"Invalid filePath\");\n }\n // Accept the workspace-relative spelling \"artifacts/stories/<rel>\"\n // the tool description historically taught (the wire form was truly\n // workspace-relative before the stories dir moved under artifacts/\n // in #284) by reducing it to the canonical \"stories/<rel>\".\n const ARTIFACTS_STORIES = \"artifacts/stories\";\n const wirePath = filePath === ARTIFACTS_STORIES || filePath.startsWith(`${ARTIFACTS_STORIES}/`) ? filePath.slice(\"artifacts/\".length) : filePath;\n // Strip the optional \"stories/\" prefix so the remainder is a path\n // relative to storiesReal. Accepts both \"stories/foo.json\" (the\n // canonical caller convention) and bare \"foo.json\".\n const STORIES_PREFIX = `stories${path.sep}`;\n const relFromStories =\n wirePath === \"stories\" ? \"\" : wirePath.startsWith(STORIES_PREFIX) || wirePath.startsWith(\"stories/\") ? wirePath.slice(\"stories/\".length) : wirePath;\n // A base path with no remainder (\"stories\", \"artifacts/stories\",\n // trailing-slash variants) would resolve to the stories directory\n // itself and hand downstream ops a directory where they expect a\n // file — reject it, mirroring normalizeStoryPath's non-empty rule.\n if (relFromStories === \"\") {\n return opBadRequest(\"Invalid filePath\");\n }\n // resolveWithinRoot enforces both the realpath boundary AND\n // existence; ENOENT and traversal both produce null. Distinguish\n // them via a follow-up existsSync so 404 vs 400 stays accurate —\n // but only consult the filesystem for lexically in-root candidates:\n // a traversal path must never touch the fs (and gets a uniform\n // bad_request so responses don't leak existence outside the root).\n const resolved = resolveWithinRoot(storiesReal, relFromStories);\n if (!resolved) {\n const candidate = path.resolve(storiesReal, relFromStories);\n const inRoot = candidate === storiesReal || candidate.startsWith(storiesReal + path.sep);\n if (inRoot && !existsSync(candidate)) {\n return opNotFound(`File not found: ${filePath}`);\n }\n return opBadRequest(\"Invalid filePath\");\n }\n return { ok: true, absolutePath: resolved };\n }\n\n /**\n * Realpath containment pre-guard for wire paths handed to the phase-1\n * core's save/reopen/update executes. The core's own path guard is\n * lexical (it runs against the generic FileOps, whose read/write follows\n * symlinks), so hosts re-assert the realpath boundary here before\n * invoking it — a symlink planted below the stories dir can't read or\n * write outside the tree (Codex P1 on MulmoClaude#2133).\n *\n * Returns null when `filePath` isn't a non-empty string — shape\n * validation (including the script-vs-filePath mode check) belongs to\n * the core.\n */\n function guardStoryWirePath(filePath: unknown): OpFailure | null {\n if (typeof filePath !== \"string\" || filePath === \"\") return null;\n const resolved = resolveStory(filePath);\n return resolved.ok ? null : resolved;\n }\n\n // mulmocast shells out to ffmpeg for movie / beat rendering. When the\n // host's probe reports it absent, intercept with a clear failure\n // instead of letting the library throw an opaque spawn ENOENT\n // mid-pipeline. `undefined` means the probe hasn't completed — assume\n // available so a brief startup window never blocks a render.\n function ffmpegGuard(): OpFailure | null {\n if (backend.isFfmpegAvailable?.() === false) {\n return {\n ok: false,\n code: \"unavailable\",\n error: \"ffmpeg is not installed — movie and beat rendering are unavailable. Install ffmpeg and restart the server.\",\n };\n }\n return null;\n }\n\n // ── Generation tracker (edge-triggered) ───────────────────────\n\n // Refcounted: two concurrent generations with the same kind/filePath/key\n // (e.g. the same beat rendered from two tabs) must not have the first\n // completion erase the second run's snapshot entry, and only the first\n // start / LAST finish reach the host channels — an early completion\n // can't clear subscribers' spinners while a duplicate run is active.\n // A finish with no tracked start (the movie/PDF pipelines' per-beat\n // completion pulses) always publishes.\n const inFlightGenerations = new Map<string, { kind: GenerationKind; filePath: string; key: string; count: number }>();\n\n /** Tracker state and events key on the canonical `stories/<rel>` wire\n * form: subscribers (the View's pubsub filter, `pendingGenerations`\n * callers) match by exact string, so the accepted alias spellings\n * (`artifacts/stories/<rel>`, bare `<rel>`) must collapse to the same\n * key as the canonical one (Codex P2 on #2139). Untrusted spellings\n * pass through unchanged — they never resolve, so they can't collide. */\n function canonicalWirePath(filePath: string): string {\n return normalizeStoryPath(filePath) ?? filePath;\n }\n\n function publishGeneration(chatSessionId: string | undefined, kind: GenerationKind, filePath: string, key: string, finished: boolean, error?: string): void {\n const wirePath = canonicalWirePath(filePath);\n const mapKey = generationMapKey(kind, wirePath, key);\n const existing = inFlightGenerations.get(mapKey);\n if (finished) {\n if (existing && existing.count > 1) {\n existing.count -= 1;\n return; // a duplicate run is still active — suppress the early finish\n }\n inFlightGenerations.delete(mapKey);\n } else {\n if (existing) {\n existing.count += 1;\n return; // already reported as started\n }\n inFlightGenerations.set(mapKey, { kind, filePath: wirePath, key, count: 1 });\n }\n const event: MulmoScriptGenerationEvent = { kind, filePath: wirePath, key, done: finished, ...(error ? { error } : {}) };\n backend.onGenerationEvent?.(chatSessionId, event);\n }\n\n /** Snapshot of generations currently in flight for one script — the\n * View's mount-time catch-up, filtered to its wire `filePath`. */\n function pendingGenerations(filePath: string): MulmoScriptGenerationEvent[] {\n const wirePath = canonicalWirePath(filePath);\n return [...inFlightGenerations.values()]\n .filter((entry) => entry.filePath === wirePath)\n .map(({ kind, key }) => ({ kind, filePath: wirePath, key, done: false }));\n }\n\n // ── Op scaffolding ────────────────────────────────────────────\n\n /**\n * Shared scaffolding for mulmoScript ops. Resolves the wire filePath,\n * builds the mulmo context, and folds unexpected handler errors into a\n * server_error failure (with a warn breadcrumb). Accepts a `deps` param\n * so unit tests can inject fakes without the full mulmocast stack.\n */\n async function runStoryOp<T>(\n filePath: string,\n options: RunStoryOpOptions<T>,\n handler: (ctx: { absoluteFilePath: string; context: StoryContext }) => Promise<OpResult<T>>,\n deps: RunStoryOpDeps = {},\n ): Promise<OpResult<T>> {\n const resolver = deps.resolveStory ?? resolveStory;\n const build = deps.buildContext ?? buildContext;\n const resolved = resolver(filePath);\n if (!resolved.ok) return resolved;\n try {\n const context = await build(resolved.absolutePath, options.force ?? false);\n if (!context) {\n if (options.onContextMissing) return options.onContextMissing();\n return opServerError(\"Failed to initialize mulmo context\");\n }\n // withMulmoErrorCapture appends the underlying provider error\n // (missing API key, quota, …) to any mulmocast failure, which\n // otherwise reaches the client as a generic \"generate error\".\n return await withMulmoErrorCapture(() => handler({ absoluteFilePath: resolved.absolutePath, context }));\n } catch (err) {\n // Log every op failure at warn so operators get a breadcrumb even\n // when the op doesn't wrap its own try/catch.\n log.warn(\"op failed\", {\n ...(options.operation ? { operation: options.operation } : {}),\n filePath,\n error: errorMessage(err),\n });\n return opServerError(errorMessage(err));\n }\n }\n\n // ── Probe ops ─────────────────────────────────────────────────\n\n async function beatImageOp(filePath: string, beatIndex: number): Promise<OpResult<{ image: string | null }>> {\n return runStoryOp<{ image: string | null }>(filePath, { operation: \"beat-image\" }, async ({ context }) => {\n const { imagePath } = getBeatPngImagePath(context, beatIndex);\n if (!existsSync(imagePath)) return { ok: true, image: null };\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n }\n\n // beatAudio is a probe — the frontend polls it expecting `{ audio: null }`\n // when nothing has been generated yet. Override the default\n // server_error-on-context-missing so the soft-fail contract is preserved.\n async function beatAudioOp(filePath: string, beatIndex: number): Promise<OpResult<{ audio: string | null }>> {\n return runStoryOp<{ audio: string | null }>(\n filePath,\n { operation: \"beat-audio\", onContextMissing: () => ({ ok: true, audio: null }) },\n async ({ context }) => {\n const beat = context.studio.script.beats[beatIndex];\n const audioPath = getBeatAudioPathOrUrl(beat.text ?? \"\", context, beat, context.lang);\n if (!audioPath || !existsSync(audioPath)) return { ok: true, audio: null };\n return { ok: true, audio: await fileToDataUri(audioPath, \"audio/mpeg\") };\n },\n );\n }\n\n // Probe for a beat's generated video clip. Preference order mirrors the\n // movie-assembly pipeline's \"most processed wins\": lip-synced > with\n // sound effect > raw movie clip > animated html_tailwind render. The\n // response is the \"stories/…\" wire path so the client can stream it\n // through the host's authenticated media download.\n async function beatMovieOp(filePath: string, beatIndex: number): Promise<OpResult<{ moviePath: string | null }>> {\n return runStoryOp<{ moviePath: string | null }>(filePath, { operation: \"beat-movie\" }, async ({ context }) => {\n const { movieFile, soundEffectFile, lipSyncFile } = getBeatMoviePaths(context, beatIndex);\n const candidates = [lipSyncFile, soundEffectFile, movieFile, getBeatAnimatedVideoPath(context, beatIndex)];\n const existing = candidates.find((candidate) => existsSync(candidate));\n return { ok: true, moviePath: existing ? toStoryRef(existing) : null };\n });\n }\n\n async function characterImageOp(filePath: string, key: string): Promise<OpResult<{ image: string | null }>> {\n return runStoryOp<{ image: string | null }>(filePath, { operation: \"character-image\" }, async ({ context }) => {\n const imagePath = getReferenceImagePath(context, key, \"png\");\n if (!existsSync(imagePath)) return { ok: true, image: null };\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n }\n\n /** Shared \"output exists and is newer than the source script\" gate for\n * movie / PDF status. A stale artifact (script edited after it was\n * generated) reports null so the UI re-offers the Generate button. */\n function freshOutputRef(outputPath: string, absoluteFilePath: string): string | null {\n if (!existsSync(outputPath)) return null;\n const outputMtime = statSync(outputPath).mtimeMs;\n const sourceMtime = statSync(absoluteFilePath).mtimeMs;\n if (outputMtime < sourceMtime) return null;\n return toStoryRef(outputPath);\n }\n\n async function movieStatusOp(filePath: string): Promise<OpResult<{ moviePath: string | null }>> {\n return runStoryOp(\n filePath,\n { operation: \"movie-status\", onContextMissing: () => ({ ok: true, moviePath: null }) },\n async ({ absoluteFilePath, context }) => ({ ok: true, moviePath: freshOutputRef(movieFilePath(context), absoluteFilePath) }),\n );\n }\n\n async function pdfStatusOp(filePath: string): Promise<OpResult<{ pdfPath: string | null }>> {\n return runStoryOp(filePath, { operation: \"pdf-status\", onContextMissing: () => ({ ok: true, pdfPath: null }) }, async ({ absoluteFilePath, context }) => ({\n ok: true,\n pdfPath: freshOutputRef(pdfFilePath(context, PDF_MODE), absoluteFilePath),\n }));\n }\n\n // ── Generation ops ────────────────────────────────────────────\n\n async function renderBeatOp(args: Required<Pick<GenerateOpArgs, \"filePath\" | \"beatIndex\">> & GenerateOpArgs): Promise<OpResult<{ image: string }>> {\n const { filePath, beatIndex, force, chatSessionId } = args;\n const ffmpeg = ffmpegGuard();\n if (ffmpeg) return ffmpeg;\n\n const mapKey = String(beatIndex);\n publishGeneration(chatSessionId, \"beatImage\", filePath, mapKey, false);\n let genError: string | undefined;\n try {\n const result = await runStoryOp<{ image: string }>(filePath, { force, operation: \"render-beat\" }, async ({ context }) => {\n await generateBeatImage({\n index: beatIndex,\n context,\n args: force ? { forceImage: true } : undefined,\n });\n const { imagePath } = getBeatPngImagePath(context, beatIndex);\n if (!existsSync(imagePath)) {\n return opServerError(\"Image was not generated\");\n }\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n if (!result.ok) genError = result.error;\n return result;\n } finally {\n publishGeneration(chatSessionId, \"beatImage\", filePath, mapKey, true, genError);\n }\n }\n\n async function generateBeatAudioOp(args: Required<Pick<GenerateOpArgs, \"filePath\" | \"beatIndex\">> & GenerateOpArgs): Promise<OpResult<{ audio: string }>> {\n const { filePath, beatIndex, force, chatSessionId } = args;\n const mapKey = String(beatIndex);\n publishGeneration(chatSessionId, \"beatAudio\", filePath, mapKey, false);\n let genError: string | undefined;\n try {\n const result = await runStoryOp<{ audio: string }>(filePath, { force, operation: \"generate-beat-audio\" }, async ({ context }) => {\n await generateBeatAudio(beatIndex, context, {\n settings: process.env as Record<string, string>,\n } as Parameters<typeof generateBeatAudio>[2]);\n\n const beat = context.studio.script.beats[beatIndex];\n const audioPath = context.studio.beats[beatIndex]?.audioFile ?? getBeatAudioPathOrUrl(beat.text ?? \"\", context, beat, context.lang);\n\n if (!audioPath || !existsSync(audioPath)) {\n // Logic-flow failure (not an exception) — emit a targeted\n // log. Don't write raw `beat.text` into persistent logs —\n // it's free-form user content and can contain sensitive\n // data.\n log.error(\"audio was not generated\", {\n beatIndex,\n audioPath,\n exists: audioPath ? existsSync(audioPath) : false,\n beatTextLength: typeof beat?.text === \"string\" ? beat.text.length : 0,\n audioFilePresent: Boolean(context.studio.beats[beatIndex]?.audioFile),\n });\n return opServerError(\"Audio was not generated\");\n }\n return { ok: true, audio: await fileToDataUri(audioPath, \"audio/mpeg\") };\n });\n if (!result.ok) genError = result.error;\n return result;\n } finally {\n publishGeneration(chatSessionId, \"beatAudio\", filePath, mapKey, true, genError);\n }\n }\n\n async function renderCharacterOp(args: Required<Pick<GenerateOpArgs, \"filePath\" | \"key\">> & GenerateOpArgs): Promise<OpResult<{ image: string }>> {\n const { filePath, key, force, chatSessionId } = args;\n publishGeneration(chatSessionId, \"characterImage\", filePath, key, false);\n let genError: string | undefined;\n try {\n const result = await runStoryOp<{ image: string }>(filePath, { force, operation: \"render-character\" }, async ({ context }) => {\n // `imageEntries` (not `images`) to avoid shadowing mulmocast's\n // imported `images()` pipeline stage.\n const imageEntries = context.studio.script.imageParams?.images ?? {};\n const imageEntry = imageEntries[key];\n if (!imageEntry || imageEntry.type !== \"imagePrompt\") {\n return opBadRequest(`No imagePrompt entry for key: ${key}`);\n }\n\n const index = Object.keys(imageEntries).indexOf(key);\n const imagePath = getReferenceImagePath(context, key, \"png\");\n mkdirSync(path.dirname(imagePath), { recursive: true });\n\n await generateReferenceImage({\n context,\n key,\n index,\n image: imageEntry as MulmoImagePromptMedia,\n force,\n });\n if (!existsSync(imagePath)) {\n return opServerError(\"Character image was not generated\");\n }\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n if (!result.ok) genError = result.error;\n return result;\n } finally {\n publishGeneration(chatSessionId, \"characterImage\", filePath, key, true, genError);\n }\n }\n\n // ── Upload ops ────────────────────────────────────────────────\n\n async function uploadBeatImageOp(filePath: string, beatIndex: number, imageData: string): Promise<OpResult<{ image: string }>> {\n return runStoryOp<{ image: string }>(filePath, { operation: \"upload-beat-image\" }, async ({ context }) => {\n const { imagePath } = getBeatPngImagePath(context, beatIndex);\n // writeFileAtomic creates parent dirs and prevents a half-\n // written PNG from surviving a crash mid-write (#881 v2).\n const base64 = stripDataUri(imageData);\n await backend.writeFileAtomic(imagePath, Buffer.from(base64, \"base64\"));\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n }\n\n async function uploadCharacterImageOp(filePath: string, key: string, imageData: string): Promise<OpResult<{ image: string }>> {\n return runStoryOp<{ image: string }>(filePath, { operation: \"upload-character-image\" }, async ({ context }) => {\n const imagePath = getReferenceImagePath(context, key, \"png\");\n const base64 = stripDataUri(imageData);\n await backend.writeFileAtomic(imagePath, Buffer.from(base64, \"base64\"));\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n }\n\n // ── Movie / PDF pipelines ─────────────────────────────────────\n\n // Per-instance dedup so a foreground call (SSE route or long-held\n // dispatch) and a fire-and-forget background call can't race on the same\n // script. Keyed by the realpath (absoluteFilePath) so two different wire\n // spellings of the same file still collide. Process-local — a\n // multi-process deployment would need an external lock; out of scope.\n const inFlightMovies = new Set<string>();\n\n // Same dedup model as inFlightMovies, scoped to PDF generation\n // (#1614). PDFs and movies don't share the lock — they write to\n // different output files and can safely run in parallel.\n const inFlightPdfs = new Set<string>();\n\n // Shared core for the SSE-streaming route, the long-held dispatch op, and\n // the fire-and-forget background path triggered by `autoGenerateMovie`.\n // Builds the mulmo context, runs audio→images→movie, and reports\n // per-beat progress through the supplied callback. Throws on\n // unexpected pipeline errors; returns a structured failure when the\n // pipeline runs to completion but the output file is missing.\n async function runMovieGeneration(absoluteFilePath: string, onProgressEvent: (event: MovieProgressEvent) => void): Promise<MovieGenerationResult> {\n return withMulmoErrorCapture(() => runMoviePipeline(absoluteFilePath, onProgressEvent));\n }\n\n async function runMoviePipeline(absoluteFilePath: string, onProgressEvent: (event: MovieProgressEvent) => void): Promise<MovieGenerationResult> {\n const context = await buildContext(absoluteFilePath);\n if (!context) return { ok: false, error: \"Failed to initialize mulmo context\" };\n\n 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): Promise<OpResult<{ moviePath: string }>> {\n const ffmpeg = ffmpegGuard();\n if (ffmpeg) return ffmpeg;\n const resolved = resolveStory(filePath);\n if (!resolved.ok) return resolved;\n const absoluteFilePath = resolved.absolutePath;\n\n if (inFlightMovies.has(absoluteFilePath)) {\n return opBadRequest(\"Movie generation is already in progress for this script\");\n }\n\n inFlightMovies.add(absoluteFilePath);\n publishGeneration(chatSessionId, \"movie\", filePath, \"\", false);\n let genError: string | undefined;\n try {\n const result = await runMovieGeneration(absoluteFilePath, (event) => {\n const eventKind = event.kind === \"image\" ? \"beatImage\" : \"beatAudio\";\n publishGeneration(chatSessionId, eventKind, filePath, String(event.beatIndex), true);\n });\n if (!result.ok) {\n genError = result.error;\n return opServerError(result.error);\n }\n return { ok: true, moviePath: toStoryRef(result.outputPath) };\n } catch (err) {\n genError = errorMessage(err);\n return opServerError(genError);\n } finally {\n inFlightMovies.delete(absoluteFilePath);\n publishGeneration(chatSessionId, \"movie\", filePath, \"\", true, genError);\n }\n }\n\n function triggerAutoBackgroundMovie(absoluteFilePath: string, wireFilePath: string, chatSessionId: string | undefined): void {\n if (inFlightMovies.has(absoluteFilePath)) return;\n inFlightMovies.add(absoluteFilePath);\n void runBackgroundMovieGeneration(absoluteFilePath, wireFilePath, chatSessionId);\n }\n\n // Detached movie generation. Reports progress through the generation\n // channels the View watches — so a user opening the canvas\n // mid-generation sees spinners, and a user opening it after completion\n // sees the finished movie loaded from disk by the View's normal\n // mount-time path. Errors are persisted to a `<filename>.error.txt`\n // sidecar next to the script (no synchronous client to alert); any\n // stale sidecar from a previous run is cleared on each new attempt.\n // Triggered server-side from the unified save route when the caller\n // passes `autoGenerateMovie: true`.\n async function runBackgroundMovieGeneration(absoluteFilePath: string, wireFilePath: string, chatSessionId: string | undefined): Promise<void> {\n const errorSidecarPath = `${absoluteFilePath}.error.txt`;\n // Clear stale error from a previous failed run before starting; if it\n // doesn't exist that's fine. Catch any unexpected fs errors silently —\n // the worst case is the user sees an out-of-date error file later.\n try {\n unlinkSync(errorSidecarPath);\n } catch {\n // intentional: ENOENT is the common case, others non-fatal\n }\n\n publishGeneration(chatSessionId, \"movie\", wireFilePath, \"\", false);\n let genError: string | undefined;\n try {\n const result = await runMovieGeneration(absoluteFilePath, (event) => {\n // Mirror per-beat completions through the generation channels so\n // subscribed Views reload the asset off disk. We fire start+finish\n // in two ticks — `setImmediate` lets the session SSE writer flush\n // the start event before the finish removes the entry, otherwise\n // Vue's batched reactivity could see a net \"no change\" and skip\n // the reload.\n const eventKind = event.kind === \"image\" ? \"beatImage\" : \"beatAudio\";\n const key = String(event.beatIndex);\n publishGeneration(chatSessionId, eventKind, wireFilePath, key, false);\n setImmediate(() => publishGeneration(chatSessionId, eventKind, wireFilePath, key, true));\n });\n\n if (!result.ok) {\n genError = result.error;\n await writeErrorSidecar(errorSidecarPath, result.error);\n log.warn(\"background movie generation failed\", { filePath: wireFilePath, error: result.error });\n return;\n }\n log.info(\"background movie generation done\", {\n filePath: wireFilePath,\n outputPath: result.outputPath,\n });\n } catch (err) {\n genError = 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, genError);\n }\n }\n\n // Atomic write so a crash mid-write can't leave a truncated sidecar.\n async function writeErrorSidecar(errorSidecarPath: string, message: string): Promise<void> {\n try {\n await backend.writeFileAtomic(errorSidecarPath, message);\n } catch (writeErr) {\n log.error(\"failed to write error sidecar\", {\n errorSidecarPath,\n error: 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): Promise<OpResult<{ pdfPath: string }>> {\n const ffmpeg = ffmpegGuard();\n if (ffmpeg) return ffmpeg;\n const resolved = resolveStory(filePath);\n if (!resolved.ok) return resolved;\n const absoluteFilePath = resolved.absolutePath;\n\n if (inFlightPdfs.has(absoluteFilePath)) {\n return opBadRequest(\"PDF generation is already in progress for this script\");\n }\n\n inFlightPdfs.add(absoluteFilePath);\n publishGeneration(chatSessionId, \"pdf\", filePath, \"\", false);\n let genError: string | undefined;\n try {\n const context = await buildContext(absoluteFilePath);\n if (!context) {\n genError = \"Failed to initialize mulmo context\";\n return opServerError(genError);\n }\n const result = await runPdfGeneration(context, (beatIndex) => {\n publishGeneration(chatSessionId, \"beatImage\", filePath, String(beatIndex), true);\n });\n if (!result.ok) {\n genError = result.error;\n return opServerError(result.error);\n }\n return { ok: true, pdfPath: toStoryRef(result.outputPath) };\n } catch (err) {\n genError = errorMessage(err);\n return opServerError(genError);\n } finally {\n inFlightPdfs.delete(absoluteFilePath);\n publishGeneration(chatSessionId, \"pdf\", filePath, \"\", true, genError);\n }\n }\n\n return {\n backend,\n toStoryRef,\n resolveStory,\n guardStoryWirePath,\n ffmpegGuard,\n runStoryOp,\n publishGeneration,\n pendingGenerations,\n beatImageOp,\n beatAudioOp,\n beatMovieOp,\n characterImageOp,\n movieStatusOp,\n pdfStatusOp,\n renderBeatOp,\n generateBeatAudioOp,\n renderCharacterOp,\n uploadBeatImageOp,\n uploadCharacterImageOp,\n inFlightMovies,\n inFlightPdfs,\n runMovieGeneration,\n runPdfGeneration,\n generateMovieOp,\n generatePdfOp,\n triggerAutoBackgroundMovie,\n };\n}\n\nexport type MulmoScriptServerOps = ReturnType<typeof createMulmoScriptServerOps>;\n","// The mulmoScript dispatch router, moved from MulmoClaude's\n// `server/plugins/mulmoscript-builtin.ts` in phase 3 so every host serves\n// the package View's `useRuntime().dispatch({ kind, … })` calls with the\n// SAME kind routing and validation. Hosts register the returned handler on\n// their dispatch channel (MulmoClaude: `registerBuiltinDispatch`;\n// MulmoTerminal: its `/api/plugin` interception).\n//\n// Response contract: every kind resolves to an `{ ok: … }` envelope (see\n// `../core/contract.ts`) — business failures are data, not thrown errors,\n// so user-facing messages stay free of transport prefixes.\n\nimport { executeMulmoScriptSave, executeUpdateBeat, executeUpdateScript, type MulmoScriptFailure } from \"../core/plugin\";\nimport type { MulmoScriptExecuteContext } from \"../core/types\";\nimport type { MulmoScriptServerOps } from \"./ops\";\nimport type { OpFailure } from \"./types\";\n\ninterface DispatchFailure {\n ok: false;\n code: \"bad_request\" | \"not_found\" | \"server_error\";\n error: string;\n}\n\nfunction fromOpFailure(failure: OpFailure): DispatchFailure {\n // \"unavailable\" (ffmpeg missing) has no slot in the contract's code\n // union — the View only reads `error`, so fold it into server_error\n // rather than widening the shared contract for one case.\n const code = failure.code === \"unavailable\" ? \"server_error\" : failure.code;\n return { ok: false, code, error: failure.error };\n}\n\nfunction fromPackageFailure(failure: MulmoScriptFailure): DispatchFailure {\n return { ok: false, code: failure.code, error: failure.error };\n}\n\nfunction invalidArgs(kind: string): DispatchFailure {\n return { ok: false, code: \"bad_request\", error: `invalid arguments for mulmoScript dispatch kind \"${kind}\"` };\n}\n\nfunction str(value: unknown): string | undefined {\n return typeof value === \"string\" && value !== \"\" ? value : undefined;\n}\n\n// Beat indexes must be non-negative integers — reject `-1` / `1.5` at the\n// dispatch boundary so invalid client input surfaces as a deterministic\n// bad_request instead of leaking into beat-indexed ops.\nfunction num(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0 ? value : undefined;\n}\n\ninterface BeatArgs {\n filePath: string;\n beatIndex: number;\n}\n\ninterface KeyArgs {\n filePath: string;\n key: string;\n}\n\n/** Pass ok results through untouched; normalize failures for the wire. */\nfunction envelope<T>(result: ({ ok: true } & T) | OpFailure): ({ ok: true } & T) | DispatchFailure {\n return result.ok ? result : fromOpFailure(result);\n}\n\nfunction beatArgs(args: Record<string, unknown>): BeatArgs | null {\n const filePath = str(args.filePath);\n const beatIndex = num(args.beatIndex);\n if (!filePath || beatIndex === undefined) return null;\n return { filePath, beatIndex };\n}\n\nfunction keyArgs(args: Record<string, unknown>): KeyArgs | null {\n const filePath = str(args.filePath);\n const key = str(args.key);\n if (!filePath || !key) return null;\n return { filePath, key };\n}\n\nconst PROBE_KINDS = new Set([\"beatImage\", \"beatAudio\", \"beatMovie\", \"characterImage\", \"movieStatus\", \"pdfStatus\"]);\nconst GENERATE_KINDS = new Set([\"renderBeat\", \"generateBeatAudio\", \"renderCharacter\", \"generateMovie\", \"generatePdf\"]);\nconst UPLOAD_KINDS = new Set([\"uploadBeatImage\", \"uploadCharacterImage\"]);\n\nexport type MulmoScriptDispatchHandler = (args: Record<string, unknown>) => Promise<unknown>;\n\n/**\n * Build the kind router over an ops instance. The save / reopen / update\n * kinds run the phase-1 core executes against the backend's artifacts\n * FileOps, guarded by the instance's realpath containment\n * (`guardStoryWirePath`) — the core's own guard is lexical.\n */\nexport function createMulmoScriptDispatchHandler(ops: MulmoScriptServerOps): MulmoScriptDispatchHandler {\n const executeContext: MulmoScriptExecuteContext = { files: { artifacts: ops.backend.artifacts } };\n\n async function saveKind(args: Record<string, unknown>): Promise<unknown> {\n const guard = ops.guardStoryWirePath(args.filePath);\n if (guard) return fromOpFailure(guard);\n const outcome = await executeMulmoScriptSave(executeContext, {\n script: args.script,\n filename: str(args.filename),\n filePath: str(args.filePath),\n });\n if (!outcome.ok) return fromPackageFailure(outcome);\n return { ok: true, script: outcome.script, filePath: outcome.filePath, message: outcome.message };\n }\n\n async function updateKind(kind: \"updateBeat\" | \"updateScript\", args: Record<string, unknown>): Promise<unknown> {\n const guard = ops.guardStoryWirePath(args.filePath);\n if (guard) return fromOpFailure(guard);\n const outcome = kind === \"updateBeat\" ? await executeUpdateBeat(executeContext, args) : await executeUpdateScript(executeContext, args);\n return outcome.ok ? { ok: true } : fromPackageFailure(outcome);\n }\n\n const STATUS_OPS = { movieStatus: ops.movieStatusOp, pdfStatus: ops.pdfStatusOp } as const;\n const BEAT_PROBE_OPS = { beatImage: ops.beatImageOp, beatAudio: ops.beatAudioOp, beatMovie: ops.beatMovieOp } as const;\n\n async function probeKind(kind: string, args: Record<string, unknown>): Promise<unknown> {\n const statusOp = STATUS_OPS[kind as keyof typeof STATUS_OPS];\n if (statusOp) {\n const filePath = str(args.filePath);\n return filePath ? envelope(await statusOp(filePath)) : invalidArgs(kind);\n }\n if (kind === \"characterImage\") {\n const parsed = keyArgs(args);\n return parsed ? envelope(await ops.characterImageOp(parsed.filePath, parsed.key)) : invalidArgs(kind);\n }\n const parsed = beatArgs(args);\n if (!parsed) return invalidArgs(kind);\n return envelope(await BEAT_PROBE_OPS[kind as keyof typeof BEAT_PROBE_OPS](parsed.filePath, parsed.beatIndex));\n }\n\n async function generateKind(kind: string, args: Record<string, unknown>): Promise<unknown> {\n const chatSessionId = str(args.chatSessionId);\n const force = args.force === true;\n if (kind === \"generateMovie\" || kind === \"generatePdf\") {\n const filePath = str(args.filePath);\n if (!filePath) return invalidArgs(kind);\n const result = kind === \"generateMovie\" ? await ops.generateMovieOp(filePath, chatSessionId) : await ops.generatePdfOp(filePath, chatSessionId);\n return envelope(result);\n }\n if (kind === \"renderCharacter\") {\n const parsed = keyArgs(args);\n return parsed ? envelope(await ops.renderCharacterOp({ ...parsed, force, chatSessionId })) : invalidArgs(kind);\n }\n const parsed = beatArgs(args);\n if (!parsed) return invalidArgs(kind);\n const result =\n kind === \"renderBeat\" ? await ops.renderBeatOp({ ...parsed, force, chatSessionId }) : await ops.generateBeatAudioOp({ ...parsed, force, chatSessionId });\n return envelope(result);\n }\n\n async function uploadKind(kind: string, args: Record<string, unknown>): Promise<unknown> {\n const imageData = str(args.imageData);\n if (!imageData) return invalidArgs(kind);\n if (kind === \"uploadCharacterImage\") {\n const parsed = keyArgs(args);\n return parsed ? envelope(await ops.uploadCharacterImageOp(parsed.filePath, parsed.key, imageData)) : invalidArgs(kind);\n }\n const parsed = beatArgs(args);\n if (!parsed) return invalidArgs(kind);\n return envelope(await ops.uploadBeatImageOp(parsed.filePath, parsed.beatIndex, imageData));\n }\n\n return async (args: Record<string, unknown>): Promise<unknown> => {\n const kind = str(args.kind);\n if (!kind) return invalidArgs(\"<missing>\");\n if (kind === \"save\") return saveKind(args);\n if (kind === \"updateBeat\" || kind === \"updateScript\") return updateKind(kind, args);\n if (PROBE_KINDS.has(kind)) return probeKind(kind, args);\n if (GENERATE_KINDS.has(kind)) return generateKind(kind, args);\n if (UPLOAD_KINDS.has(kind)) return uploadKind(kind, args);\n if (kind === \"pendingGenerations\") {\n const filePath = str(args.filePath);\n if (!filePath) return invalidArgs(kind);\n return { ok: true, pending: ops.pendingGenerations(filePath) };\n }\n return { ok: false, code: \"bad_request\", error: `unknown mulmoScript dispatch kind \"${kind}\"` };\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOA,SAAgB,SAAS,OAAkD;CACzE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAgB,aAAa,SAAyB;CACpD,OAAO,QAAQ,QAAQ,8BAA8B,EAAE;AACzD;AAIA,eAAsB,cAAc,UAAkB,UAAmC;CAEvF,OAAO,QAAQ,SAAS,WAAU,OAAA,GAAA,iBAAA,SAAA,CADN,QAAQ,EAAA,CACG,SAAS,QAAQ;AAC1D;;;ACAA,IAAM,iBAAiB,IAAI,iBAAA,kBAA4B;AACvD,IAAI,kBAAkB;AACtB,IAAI,aAA0C;;;;AAK9C,SAAgB,2BAA2B,KAAwC;CACjF,aAAa;AACf;AAEA,SAAS,aAAa,KAAsB;CAC1C,IAAI,OAAO,QAAQ,UAAU,OAAO;CACpC,IAAI,eAAe,OAAO,OAAO,IAAI;CACrC,IAAI;EACF,OAAO,KAAK,UAAU,GAAG;CAC3B,QAAQ;EACN,OAAO,OAAO,GAAG;CACnB;AACF;;;;;;;AAQA,SAAgB,4BAAkC;CAChD,QAAA,cAAc,gBAAgB,SAAS,IAAI;CAC3C,IAAI,iBAAiB;CACrB,kBAAkB;CAClB,QAAA,cAAc,WAAW,OAAO,GAAG,SAAS;EAC1C,IAAI,UAAU,SAAS;EACvB,MAAM,UAAU,KAAK,IAAI,YAAY,CAAC,CAAC,KAAK,GAAG;EAC/C,YAAY,KAAK,8BAA8B,EAAE,QAAQ,CAAC;EAC1D,eAAe,SAAS,CAAC,EAAE,KAAK,OAAO;CACzC,CAAC;AACH;AAKA,IAAM,eAAe;CAAC;CAAQ;CAAa;CAAc;CAAa;AAAW;;AAGjF,SAAgB,mBAAmB,KAA6B;CAC9D,IAAI,EAAE,eAAe,UAAU,CAAC,SAAS,IAAI,KAAK,GAAG,OAAO;CAC5D,MAAM,EAAE,UAAU;CAClB,MAAM,QAAQ,aAAa,SAAS,UAAU;EAC5C,MAAM,QAAQ,MAAM;EACpB,OAAO,OAAO,UAAU,YAAY,UAAU,KAAK,CAAC,GAAG,MAAM,GAAG,OAAO,IAAI,CAAC;CAC9E,CAAC;CACD,OAAO,MAAM,SAAS,IAAI,MAAM,KAAK,GAAG,IAAI;AAC9C;;;;;;;AAQA,SAAgB,yBAAyB,KAAc,UAAqC;CAC1F,MAAM,OAAO,eAAA,aAAa,GAAG;CAC7B,MAAM,UAAU,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC,CAAC,CAAC,QAAQ,YAAY,YAAY,MAAM,YAAY,IAAI;CAC7F,OAAO;EAAC;EAAM,mBAAmB,GAAG;EAAG,GAAG;CAAO,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,KAAK;AAC/E;;;;;;;AAQA,eAAsB,sBAAyB,WAAyC;CACtF,OAAO,eAAe,IAAI,CAAC,GAAG,YAAY;EACxC,IAAI;GACF,OAAO,MAAM,UAAU;EACzB,SAAS,KAAK;GACZ,MAAM,IAAI,MAAM,yBAAyB,KAAK,eAAe,SAAS,KAAK,CAAC,CAAC,GAAG,EAAE,OAAO,IAAI,CAAC;EAChG;CACF,CAAC;AACH;;;ACpCA,IAAa,WAAW;AACxB,IAAa,WAAW;AAExB,SAAS,aAAa,OAA0B;CAC9C,OAAO;EAAE,IAAI;EAAO,MAAM;EAAe;CAAM;AACjD;AAEA,SAAS,WAAW,OAA0B;CAC5C,OAAO;EAAE,IAAI;EAAO,MAAM;EAAa;CAAM;AAC/C;AAEA,SAAS,cAAc,OAA0B;CAC/C,OAAO;EAAE,IAAI;EAAO,MAAM;EAAgB;CAAM;AAClD;AAEA,IAAM,WAAiC;CAAE,YAAY,CAAC;CAAG,YAAY,CAAC;CAAG,aAAa,CAAC;AAAE;AAKzF,eAAsB,aAAa,kBAA0B,QAAQ,OAAuD;CAK1H,CAAA,GAAA,UAAA,iBAAA,CAAiB,KAAK;CACtB,0BAA0B;CAM1B,QAAA,GAAA,UAAA,2BAAA,EAAA,GAAA,UAAA,cAAA,CAL4B;EAC1B,MAAM;EACN,SAAS,KAAA,QAAK,QAAQ,gBAAgB;EACtC,SAAS;CACX,CACkC,GAAO,MAAM,KAAK;AACtD;AAgCA,SAAgB,iBAAiB,OAAyC;CACxE,MAAM,4BAAY,IAAI,IAAoB;CAC1C,MAAM,SAAS,MAAM,UAAU;EAC7B,MAAM,MAAM,KAAK,MAAM,YAAY;EACnC,UAAU,IAAI,KAAK,KAAK;CAC1B,CAAC;CACD,OAAO;AACT;AAgBA,eAAe,iBAAoB,OAAoB,QAA0D,MAAoC;CACnJ,MAAM,YAAY,iBAAiB,KAAK;CACxC,MAAM,cAAc,UAAkF;EACpG,IAAI,MAAM,SAAS,UAAU,MAAM,aAAa,MAAM,OAAO,KAAA,GAAW;EACxE,MAAM,YAAY,UAAU,IAAI,MAAM,EAAE;EACxC,IAAI,cAAc,KAAA,GAAW;EAC7B,OAAO,MAAM,aAAa,SAAS;CACrC;CACA,CAAA,GAAA,UAAA,2BAAA,CAA2B,UAAU;CACrC,IAAI;EACF,OAAO,MAAM,KAAK;CACpB,UAAU;EACR,CAAA,GAAA,UAAA,8BAAA,CAA8B,UAAU;CAC1C;AACF;;;AAIA,SAAS,iBAAiB,MAAsB,UAAkB,KAAqB;CACrF,OAAO,KAAK,UAAU;EAAC;EAAM;EAAU;CAAG,CAAC;AAC7C;;;;;;AAOA,SAAgB,2BAA2B,SAAmC;CAC5E,MAAM,MAAM,QAAQ,OAAO;CAC3B,2BAA2B,GAAG;CAC9B,MAAM,aAAa,KAAA,QAAK,QAAQ,QAAQ,UAAU;CAWlD,SAAS,WAAW,cAA8B;EAChD,MAAM,OAAO,kBAAkB,KAAK;EACpC,MAAM,MAAM,KAAA,QAAK,SAAS,MAAM,YAAY,CAAC,CAAC,MAAM,KAAA,QAAK,GAAG,CAAC,CAAC,KAAK,GAAG;EACtE,OAAO,MAAM,WAAW,QAAQ;CAClC;CAMA,IAAI,mBAAkC;CACtC,SAAS,oBAAmC;EAC1C,IAAI,kBAAkB,OAAO;EAC7B,IAAI;GACF,CAAA,GAAA,GAAA,UAAA,CAAU,YAAY,EAAE,WAAW,KAAK,CAAC;GACzC,oBAAA,GAAA,GAAA,aAAA,CAAgC,UAAU;GAC1C,OAAO;EACT,QAAQ;GACN,OAAO;EACT;CACF;;;;;;;;;;;;CAaA,SAAS,aAAa,UAAkE;EACtF,MAAM,cAAc,kBAAkB;EACtC,IAAI,CAAC,aACH,OAAO,cAAc,iCAAiC;EAIxD,IAAI,KAAA,QAAK,WAAW,QAAQ,GAC1B,OAAO,aAAa,kBAAkB;EAMxC,MAAM,oBAAoB;EAC1B,MAAM,WAAW,aAAa,qBAAqB,SAAS,WAAW,GAAG,kBAAkB,EAAE,IAAI,SAAS,MAAM,EAAmB,IAAI;EAIxI,MAAM,iBAAiB,UAAU,KAAA,QAAK;EACtC,MAAM,iBACJ,aAAa,YAAY,KAAK,SAAS,WAAW,cAAc,KAAK,SAAS,WAAW,UAAU,IAAI,SAAS,MAAM,CAAiB,IAAI;EAK7I,IAAI,mBAAmB,IACrB,OAAO,aAAa,kBAAkB;EAQxC,MAAM,YAAA,GAAA,wBAAA,kBAAA,CAA6B,aAAa,cAAc;EAC9D,IAAI,CAAC,UAAU;GACb,MAAM,YAAY,KAAA,QAAK,QAAQ,aAAa,cAAc;GAE1D,KADe,cAAc,eAAe,UAAU,WAAW,cAAc,KAAA,QAAK,GAAG,MACzE,EAAA,GAAA,GAAA,WAAA,CAAY,SAAS,GACjC,OAAO,WAAW,mBAAmB,UAAU;GAEjD,OAAO,aAAa,kBAAkB;EACxC;EACA,OAAO;GAAE,IAAI;GAAM,cAAc;EAAS;CAC5C;;;;;;;;;;;;;CAcA,SAAS,mBAAmB,UAAqC;EAC/D,IAAI,OAAO,aAAa,YAAY,aAAa,IAAI,OAAO;EAC5D,MAAM,WAAW,aAAa,QAAQ;EACtC,OAAO,SAAS,KAAK,OAAO;CAC9B;CAOA,SAAS,cAAgC;EACvC,IAAI,QAAQ,oBAAoB,MAAM,OACpC,OAAO;GACL,IAAI;GACJ,MAAM;GACN,OAAO;EACT;EAEF,OAAO;CACT;CAWA,MAAM,sCAAsB,IAAI,IAAoF;;;;;;;CAQpH,SAAS,kBAAkB,UAA0B;EACnD,OAAO,eAAA,mBAAmB,QAAQ,KAAK;CACzC;CAEA,SAAS,kBAAkB,eAAmC,MAAsB,UAAkB,KAAa,UAAmB,OAAsB;EAC1J,MAAM,WAAW,kBAAkB,QAAQ;EAC3C,MAAM,SAAS,iBAAiB,MAAM,UAAU,GAAG;EACnD,MAAM,WAAW,oBAAoB,IAAI,MAAM;EAC/C,IAAI,UAAU;GACZ,IAAI,YAAY,SAAS,QAAQ,GAAG;IAClC,SAAS,SAAS;IAClB;GACF;GACA,oBAAoB,OAAO,MAAM;EACnC,OAAO;GACL,IAAI,UAAU;IACZ,SAAS,SAAS;IAClB;GACF;GACA,oBAAoB,IAAI,QAAQ;IAAE;IAAM,UAAU;IAAU;IAAK,OAAO;GAAE,CAAC;EAC7E;EACA,MAAM,QAAoC;GAAE;GAAM,UAAU;GAAU;GAAK,MAAM;GAAU,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;EAAG;EACvH,QAAQ,oBAAoB,eAAe,KAAK;CAClD;;;CAIA,SAAS,mBAAmB,UAAgD;EAC1E,MAAM,WAAW,kBAAkB,QAAQ;EAC3C,OAAO,CAAC,GAAG,oBAAoB,OAAO,CAAC,CAAC,CACrC,QAAQ,UAAU,MAAM,aAAa,QAAQ,CAAC,CAC9C,KAAK,EAAE,MAAM,WAAW;GAAE;GAAM,UAAU;GAAU;GAAK,MAAM;EAAM,EAAE;CAC5E;;;;;;;CAUA,eAAe,WACb,UACA,SACA,SACA,OAAuB,CAAC,GACF;EACtB,MAAM,WAAW,KAAK,gBAAgB;EACtC,MAAM,QAAQ,KAAK,gBAAgB;EACnC,MAAM,WAAW,SAAS,QAAQ;EAClC,IAAI,CAAC,SAAS,IAAI,OAAO;EACzB,IAAI;GACF,MAAM,UAAU,MAAM,MAAM,SAAS,cAAc,QAAQ,SAAS,KAAK;GACzE,IAAI,CAAC,SAAS;IACZ,IAAI,QAAQ,kBAAkB,OAAO,QAAQ,iBAAiB;IAC9D,OAAO,cAAc,oCAAoC;GAC3D;GAIA,OAAO,MAAM,4BAA4B,QAAQ;IAAE,kBAAkB,SAAS;IAAc;GAAQ,CAAC,CAAC;EACxG,SAAS,KAAK;GAGZ,IAAI,KAAK,aAAa;IACpB,GAAI,QAAQ,YAAY,EAAE,WAAW,QAAQ,UAAU,IAAI,CAAC;IAC5D;IACA,OAAO,eAAA,aAAa,GAAG;GACzB,CAAC;GACD,OAAO,cAAc,eAAA,aAAa,GAAG,CAAC;EACxC;CACF;CAIA,eAAe,YAAY,UAAkB,WAAgE;EAC3G,OAAO,WAAqC,UAAU,EAAE,WAAW,aAAa,GAAG,OAAO,EAAE,cAAc;GACxG,MAAM,EAAE,eAAA,GAAA,UAAA,oBAAA,CAAkC,SAAS,SAAS;GAC5D,IAAI,EAAA,GAAA,GAAA,WAAA,CAAY,SAAS,GAAG,OAAO;IAAE,IAAI;IAAM,OAAO;GAAK;GAC3D,OAAO;IAAE,IAAI;IAAM,OAAO,MAAM,cAAc,WAAW,WAAW;GAAE;EACxE,CAAC;CACH;CAKA,eAAe,YAAY,UAAkB,WAAgE;EAC3G,OAAO,WACL,UACA;GAAE,WAAW;GAAc,yBAAyB;IAAE,IAAI;IAAM,OAAO;GAAK;EAAG,GAC/E,OAAO,EAAE,cAAc;GACrB,MAAM,OAAO,QAAQ,OAAO,OAAO,MAAM;GACzC,MAAM,aAAA,GAAA,UAAA,sBAAA,CAAkC,KAAK,QAAQ,IAAI,SAAS,MAAM,QAAQ,IAAI;GACpF,IAAI,CAAC,aAAa,EAAA,GAAA,GAAA,WAAA,CAAY,SAAS,GAAG,OAAO;IAAE,IAAI;IAAM,OAAO;GAAK;GACzE,OAAO;IAAE,IAAI;IAAM,OAAO,MAAM,cAAc,WAAW,YAAY;GAAE;EACzE,CACF;CACF;CAOA,eAAe,YAAY,UAAkB,WAAoE;EAC/G,OAAO,WAAyC,UAAU,EAAE,WAAW,aAAa,GAAG,OAAO,EAAE,cAAc;GAC5G,MAAM,EAAE,WAAW,iBAAiB,iBAAA,GAAA,UAAA,kBAAA,CAAkC,SAAS,SAAS;GAExF,MAAM,WAAW;IADG;IAAa;IAAiB;4CAAoC,SAAS,SAAS;GACvF,CAAA,CAAW,MAAM,eAAA,GAAA,GAAA,WAAA,CAAyB,SAAS,CAAC;GACrE,OAAO;IAAE,IAAI;IAAM,WAAW,WAAW,WAAW,QAAQ,IAAI;GAAK;EACvE,CAAC;CACH;CAEA,eAAe,iBAAiB,UAAkB,KAA0D;EAC1G,OAAO,WAAqC,UAAU,EAAE,WAAW,kBAAkB,GAAG,OAAO,EAAE,cAAc;GAC7G,MAAM,aAAA,GAAA,UAAA,sBAAA,CAAkC,SAAS,KAAK,KAAK;GAC3D,IAAI,EAAA,GAAA,GAAA,WAAA,CAAY,SAAS,GAAG,OAAO;IAAE,IAAI;IAAM,OAAO;GAAK;GAC3D,OAAO;IAAE,IAAI;IAAM,OAAO,MAAM,cAAc,WAAW,WAAW;GAAE;EACxE,CAAC;CACH;;;;CAKA,SAAS,eAAe,YAAoB,kBAAyC;EACnF,IAAI,EAAA,GAAA,GAAA,WAAA,CAAY,UAAU,GAAG,OAAO;EAGpC,KAAA,GAAA,GAAA,SAAA,CAF6B,UAAU,CAAC,CAAC,WAAA,GAAA,GAAA,SAAA,CACZ,gBAAgB,CAAC,CAAC,SAChB,OAAO;EACtC,OAAO,WAAW,UAAU;CAC9B;CAEA,eAAe,cAAc,UAAmE;EAC9F,OAAO,WACL,UACA;GAAE,WAAW;GAAgB,yBAAyB;IAAE,IAAI;IAAM,WAAW;GAAK;EAAG,GACrF,OAAO,EAAE,kBAAkB,eAAe;GAAE,IAAI;GAAM,WAAW,gBAAA,GAAA,UAAA,cAAA,CAA6B,OAAO,GAAG,gBAAgB;EAAE,EAC5H;CACF;CAEA,eAAe,YAAY,UAAiE;EAC1F,OAAO,WAAW,UAAU;GAAE,WAAW;GAAc,yBAAyB;IAAE,IAAI;IAAM,SAAS;GAAK;EAAG,GAAG,OAAO,EAAE,kBAAkB,eAAe;GACxJ,IAAI;GACJ,SAAS,gBAAA,GAAA,UAAA,YAAA,CAA2B,SAAS,QAAQ,GAAG,gBAAgB;EAC1E,EAAE;CACJ;CAIA,eAAe,aAAa,MAAuH;EACjJ,MAAM,EAAE,UAAU,WAAW,OAAO,kBAAkB;EACtD,MAAM,SAAS,YAAY;EAC3B,IAAI,QAAQ,OAAO;EAEnB,MAAM,SAAS,OAAO,SAAS;EAC/B,kBAAkB,eAAe,aAAa,UAAU,QAAQ,KAAK;EACrE,IAAI;EACJ,IAAI;GACF,MAAM,SAAS,MAAM,WAA8B,UAAU;IAAE;IAAO,WAAW;GAAc,GAAG,OAAO,EAAE,cAAc;IACvH,OAAA,GAAA,UAAA,kBAAA,CAAwB;KACtB,OAAO;KACP;KACA,MAAM,QAAQ,EAAE,YAAY,KAAK,IAAI,KAAA;IACvC,CAAC;IACD,MAAM,EAAE,eAAA,GAAA,UAAA,oBAAA,CAAkC,SAAS,SAAS;IAC5D,IAAI,EAAA,GAAA,GAAA,WAAA,CAAY,SAAS,GACvB,OAAO,cAAc,yBAAyB;IAEhD,OAAO;KAAE,IAAI;KAAM,OAAO,MAAM,cAAc,WAAW,WAAW;IAAE;GACxE,CAAC;GACD,IAAI,CAAC,OAAO,IAAI,WAAW,OAAO;GAClC,OAAO;EACT,UAAU;GACR,kBAAkB,eAAe,aAAa,UAAU,QAAQ,MAAM,QAAQ;EAChF;CACF;CAEA,eAAe,oBAAoB,MAAuH;EACxJ,MAAM,EAAE,UAAU,WAAW,OAAO,kBAAkB;EACtD,MAAM,SAAS,OAAO,SAAS;EAC/B,kBAAkB,eAAe,aAAa,UAAU,QAAQ,KAAK;EACrE,IAAI;EACJ,IAAI;GACF,MAAM,SAAS,MAAM,WAA8B,UAAU;IAAE;IAAO,WAAW;GAAsB,GAAG,OAAO,EAAE,cAAc;IAC/H,OAAA,GAAA,UAAA,kBAAA,CAAwB,WAAW,SAAS,EAC1C,UAAU,QAAQ,IACpB,CAA4C;IAE5C,MAAM,OAAO,QAAQ,OAAO,OAAO,MAAM;IACzC,MAAM,YAAY,QAAQ,OAAO,MAAM,UAAU,EAAE,cAAA,GAAA,UAAA,sBAAA,CAAmC,KAAK,QAAQ,IAAI,SAAS,MAAM,QAAQ,IAAI;IAElI,IAAI,CAAC,aAAa,EAAA,GAAA,GAAA,WAAA,CAAY,SAAS,GAAG;KAKxC,IAAI,MAAM,2BAA2B;MACnC;MACA;MACA,QAAQ,aAAA,GAAA,GAAA,WAAA,CAAuB,SAAS,IAAI;MAC5C,gBAAgB,OAAO,MAAM,SAAS,WAAW,KAAK,KAAK,SAAS;MACpE,kBAAkB,QAAQ,QAAQ,OAAO,MAAM,UAAU,EAAE,SAAS;KACtE,CAAC;KACD,OAAO,cAAc,yBAAyB;IAChD;IACA,OAAO;KAAE,IAAI;KAAM,OAAO,MAAM,cAAc,WAAW,YAAY;IAAE;GACzE,CAAC;GACD,IAAI,CAAC,OAAO,IAAI,WAAW,OAAO;GAClC,OAAO;EACT,UAAU;GACR,kBAAkB,eAAe,aAAa,UAAU,QAAQ,MAAM,QAAQ;EAChF;CACF;CAEA,eAAe,kBAAkB,MAAiH;EAChJ,MAAM,EAAE,UAAU,KAAK,OAAO,kBAAkB;EAChD,kBAAkB,eAAe,kBAAkB,UAAU,KAAK,KAAK;EACvE,IAAI;EACJ,IAAI;GACF,MAAM,SAAS,MAAM,WAA8B,UAAU;IAAE;IAAO,WAAW;GAAmB,GAAG,OAAO,EAAE,cAAc;IAG5H,MAAM,eAAe,QAAQ,OAAO,OAAO,aAAa,UAAU,CAAC;IACnE,MAAM,aAAa,aAAa;IAChC,IAAI,CAAC,cAAc,WAAW,SAAS,eACrC,OAAO,aAAa,iCAAiC,KAAK;IAG5D,MAAM,QAAQ,OAAO,KAAK,YAAY,CAAC,CAAC,QAAQ,GAAG;IACnD,MAAM,aAAA,GAAA,UAAA,sBAAA,CAAkC,SAAS,KAAK,KAAK;IAC3D,CAAA,GAAA,GAAA,UAAA,CAAU,KAAA,QAAK,QAAQ,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;IAEtD,OAAA,GAAA,UAAA,uBAAA,CAA6B;KAC3B;KACA;KACA;KACA,OAAO;KACP;IACF,CAAC;IACD,IAAI,EAAA,GAAA,GAAA,WAAA,CAAY,SAAS,GACvB,OAAO,cAAc,mCAAmC;IAE1D,OAAO;KAAE,IAAI;KAAM,OAAO,MAAM,cAAc,WAAW,WAAW;IAAE;GACxE,CAAC;GACD,IAAI,CAAC,OAAO,IAAI,WAAW,OAAO;GAClC,OAAO;EACT,UAAU;GACR,kBAAkB,eAAe,kBAAkB,UAAU,KAAK,MAAM,QAAQ;EAClF;CACF;CAIA,eAAe,kBAAkB,UAAkB,WAAmB,WAAyD;EAC7H,OAAO,WAA8B,UAAU,EAAE,WAAW,oBAAoB,GAAG,OAAO,EAAE,cAAc;GACxG,MAAM,EAAE,eAAA,GAAA,UAAA,oBAAA,CAAkC,SAAS,SAAS;GAG5D,MAAM,SAAS,aAAa,SAAS;GACrC,MAAM,QAAQ,gBAAgB,WAAW,OAAO,KAAK,QAAQ,QAAQ,CAAC;GACtE,OAAO;IAAE,IAAI;IAAM,OAAO,MAAM,cAAc,WAAW,WAAW;GAAE;EACxE,CAAC;CACH;CAEA,eAAe,uBAAuB,UAAkB,KAAa,WAAyD;EAC5H,OAAO,WAA8B,UAAU,EAAE,WAAW,yBAAyB,GAAG,OAAO,EAAE,cAAc;GAC7G,MAAM,aAAA,GAAA,UAAA,sBAAA,CAAkC,SAAS,KAAK,KAAK;GAC3D,MAAM,SAAS,aAAa,SAAS;GACrC,MAAM,QAAQ,gBAAgB,WAAW,OAAO,KAAK,QAAQ,QAAQ,CAAC;GACtE,OAAO;IAAE,IAAI;IAAM,OAAO,MAAM,cAAc,WAAW,WAAW;GAAE;EACxE,CAAC;CACH;CASA,MAAM,iCAAiB,IAAI,IAAY;CAKvC,MAAM,+BAAe,IAAI,IAAY;CAQrC,eAAe,mBAAmB,kBAA0B,iBAAsF;EAChJ,OAAO,4BAA4B,iBAAiB,kBAAkB,eAAe,CAAC;CACxF;CAEA,eAAe,iBAAiB,kBAA0B,iBAAsF;EAC9I,MAAM,UAAU,MAAM,aAAa,gBAAgB;EACnD,IAAI,CAAC,SAAS,OAAO;GAAE,IAAI;GAAO,OAAO;EAAqC;EAE9E,OAAO,iBACL,QAAQ,OAAO,OAAO,QACrB,aAAa,cAAc;GAC1B,IAAI,gBAAgB,WAAW,gBAAgB,SAAS;GACxD,gBAAgB;IAAE,MAAM;IAAa;GAAU,CAAC;EAClD,GACA,YAAY;GAQV,MAAM,gBAAgB,OAAA,GAAA,UAAA,OAAA,CAAa,OAAA,GAAA,UAAA,MAAA,CADF,OAAO,CACO;GAC/C,OAAA,GAAA,UAAA,MAAA,CAAY,aAAa;GAEzB,MAAM,cAAA,GAAA,UAAA,cAAA,CAA2B,aAAa;GAC9C,IAAI,EAAA,GAAA,GAAA,WAAA,CAAY,UAAU,GAAG,OAAO;IAAE,IAAI;IAAO,OAAO;GAA0B;GAClF,OAAO;IAAE,IAAI;IAAM;GAAW;EAChC,CACF;CACF;;;;;;;;CASA,eAAe,gBAAgB,UAAkB,eAA6E;EAC5H,MAAM,SAAS,YAAY;EAC3B,IAAI,QAAQ,OAAO;EACnB,MAAM,WAAW,aAAa,QAAQ;EACtC,IAAI,CAAC,SAAS,IAAI,OAAO;EACzB,MAAM,mBAAmB,SAAS;EAElC,IAAI,eAAe,IAAI,gBAAgB,GACrC,OAAO,aAAa,yDAAyD;EAG/E,eAAe,IAAI,gBAAgB;EACnC,kBAAkB,eAAe,SAAS,UAAU,IAAI,KAAK;EAC7D,IAAI;EACJ,IAAI;GACF,MAAM,SAAS,MAAM,mBAAmB,mBAAmB,UAAU;IAEnE,kBAAkB,eADA,MAAM,SAAS,UAAU,cAAc,aACb,UAAU,OAAO,MAAM,SAAS,GAAG,IAAI;GACrF,CAAC;GACD,IAAI,CAAC,OAAO,IAAI;IACd,WAAW,OAAO;IAClB,OAAO,cAAc,OAAO,KAAK;GACnC;GACA,OAAO;IAAE,IAAI;IAAM,WAAW,WAAW,OAAO,UAAU;GAAE;EAC9D,SAAS,KAAK;GACZ,WAAW,eAAA,aAAa,GAAG;GAC3B,OAAO,cAAc,QAAQ;EAC/B,UAAU;GACR,eAAe,OAAO,gBAAgB;GACtC,kBAAkB,eAAe,SAAS,UAAU,IAAI,MAAM,QAAQ;EACxE;CACF;CAEA,SAAS,2BAA2B,kBAA0B,cAAsB,eAAyC;EAC3H,IAAI,eAAe,IAAI,gBAAgB,GAAG;EAC1C,eAAe,IAAI,gBAAgB;EACnC,6BAAkC,kBAAkB,cAAc,aAAa;CACjF;CAWA,eAAe,6BAA6B,kBAA0B,cAAsB,eAAkD;EAC5I,MAAM,mBAAmB,GAAG,iBAAiB;EAI7C,IAAI;GACF,CAAA,GAAA,GAAA,WAAA,CAAW,gBAAgB;EAC7B,QAAQ,CAER;EAEA,kBAAkB,eAAe,SAAS,cAAc,IAAI,KAAK;EACjE,IAAI;EACJ,IAAI;GACF,MAAM,SAAS,MAAM,mBAAmB,mBAAmB,UAAU;IAOnE,MAAM,YAAY,MAAM,SAAS,UAAU,cAAc;IACzD,MAAM,MAAM,OAAO,MAAM,SAAS;IAClC,kBAAkB,eAAe,WAAW,cAAc,KAAK,KAAK;IACpE,mBAAmB,kBAAkB,eAAe,WAAW,cAAc,KAAK,IAAI,CAAC;GACzF,CAAC;GAED,IAAI,CAAC,OAAO,IAAI;IACd,WAAW,OAAO;IAClB,MAAM,kBAAkB,kBAAkB,OAAO,KAAK;IACtD,IAAI,KAAK,sCAAsC;KAAE,UAAU;KAAc,OAAO,OAAO;IAAM,CAAC;IAC9F;GACF;GACA,IAAI,KAAK,oCAAoC;IAC3C,UAAU;IACV,YAAY,OAAO;GACrB,CAAC;EACH,SAAS,KAAK;GACZ,WAAW,eAAA,aAAa,GAAG;GAC3B,MAAM,kBAAkB,kBAAkB,QAAQ;GAClD,IAAI,MAAM,uCAAuC;IAAE,UAAU;IAAc,OAAO;GAAS,CAAC;EAC9F,UAAU;GACR,eAAe,OAAO,gBAAgB;GACtC,kBAAkB,eAAe,SAAS,cAAc,IAAI,MAAM,QAAQ;EAC5E;CACF;CAGA,eAAe,kBAAkB,kBAA0B,SAAgC;EACzF,IAAI;GACF,MAAM,QAAQ,gBAAgB,kBAAkB,OAAO;EACzD,SAAS,UAAU;GACjB,IAAI,MAAM,iCAAiC;IACzC;IACA,OAAO,eAAA,aAAa,QAAQ;GAC9B,CAAC;EACH;CACF;CAUA,eAAe,iBAAiB,SAAuB,iBAA4E;EACjI,OAAO,4BAA4B,eAAe,SAAS,eAAe,CAAC;CAC7E;CAEA,eAAe,eAAe,SAAuB,iBAA4E;EAC/H,OAAO,iBACL,QAAQ,OAAO,OAAO,QACrB,aAAa,cAAc;GAC1B,IAAI,gBAAgB,SAAS;GAC7B,gBAAgB,SAAS;EAC3B,GACA,YAAY;GACV,MAAM,gBAAgB,OAAA,GAAA,UAAA,OAAA,CAAa,OAAO;GAC1C,OAAA,GAAA,UAAA,IAAA,CAAU,eAAe,UAAA,IAAkB;GAC3C,MAAM,cAAA,GAAA,UAAA,YAAA,CAAyB,eAAe,QAAQ;GACtD,IAAI,EAAA,GAAA,GAAA,WAAA,CAAY,UAAU,GAAG,OAAO;IAAE,IAAI;IAAO,OAAO;GAAwB;GAChF,OAAO;IAAE,IAAI;IAAM;GAAW;EAChC,CACF;CACF;;;CAIA,eAAe,cAAc,UAAkB,eAA2E;EACxH,MAAM,SAAS,YAAY;EAC3B,IAAI,QAAQ,OAAO;EACnB,MAAM,WAAW,aAAa,QAAQ;EACtC,IAAI,CAAC,SAAS,IAAI,OAAO;EACzB,MAAM,mBAAmB,SAAS;EAElC,IAAI,aAAa,IAAI,gBAAgB,GACnC,OAAO,aAAa,uDAAuD;EAG7E,aAAa,IAAI,gBAAgB;EACjC,kBAAkB,eAAe,OAAO,UAAU,IAAI,KAAK;EAC3D,IAAI;EACJ,IAAI;GACF,MAAM,UAAU,MAAM,aAAa,gBAAgB;GACnD,IAAI,CAAC,SAAS;IACZ,WAAW;IACX,OAAO,cAAc,QAAQ;GAC/B;GACA,MAAM,SAAS,MAAM,iBAAiB,UAAU,cAAc;IAC5D,kBAAkB,eAAe,aAAa,UAAU,OAAO,SAAS,GAAG,IAAI;GACjF,CAAC;GACD,IAAI,CAAC,OAAO,IAAI;IACd,WAAW,OAAO;IAClB,OAAO,cAAc,OAAO,KAAK;GACnC;GACA,OAAO;IAAE,IAAI;IAAM,SAAS,WAAW,OAAO,UAAU;GAAE;EAC5D,SAAS,KAAK;GACZ,WAAW,eAAA,aAAa,GAAG;GAC3B,OAAO,cAAc,QAAQ;EAC/B,UAAU;GACR,aAAa,OAAO,gBAAgB;GACpC,kBAAkB,eAAe,OAAO,UAAU,IAAI,MAAM,QAAQ;EACtE;CACF;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF;;;ACv0BA,SAAS,cAAc,SAAqC;CAK1D,OAAO;EAAE,IAAI;EAAO,MADP,QAAQ,SAAS,gBAAgB,iBAAiB,QAAQ;EAC7C,OAAO,QAAQ;CAAM;AACjD;AAEA,SAAS,mBAAmB,SAA8C;CACxE,OAAO;EAAE,IAAI;EAAO,MAAM,QAAQ;EAAM,OAAO,QAAQ;CAAM;AAC/D;AAEA,SAAS,YAAY,MAA+B;CAClD,OAAO;EAAE,IAAI;EAAO,MAAM;EAAe,OAAO,oDAAoD,KAAK;CAAG;AAC9G;AAEA,SAAS,IAAI,OAAoC;CAC/C,OAAO,OAAO,UAAU,YAAY,UAAU,KAAK,QAAQ,KAAA;AAC7D;AAKA,SAAS,IAAI,OAAoC;CAC/C,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,KAAK,KAAK,SAAS,IAAI,QAAQ,KAAA;AACtF;;AAaA,SAAS,SAAY,QAA8E;CACjG,OAAO,OAAO,KAAK,SAAS,cAAc,MAAM;AAClD;AAEA,SAAS,SAAS,MAAgD;CAChE,MAAM,WAAW,IAAI,KAAK,QAAQ;CAClC,MAAM,YAAY,IAAI,KAAK,SAAS;CACpC,IAAI,CAAC,YAAY,cAAc,KAAA,GAAW,OAAO;CACjD,OAAO;EAAE;EAAU;CAAU;AAC/B;AAEA,SAAS,QAAQ,MAA+C;CAC9D,MAAM,WAAW,IAAI,KAAK,QAAQ;CAClC,MAAM,MAAM,IAAI,KAAK,GAAG;CACxB,IAAI,CAAC,YAAY,CAAC,KAAK,OAAO;CAC9B,OAAO;EAAE;EAAU;CAAI;AACzB;AAEA,IAAM,8BAAc,IAAI,IAAI;CAAC;CAAa;CAAa;CAAa;CAAkB;CAAe;AAAW,CAAC;AACjH,IAAM,iCAAiB,IAAI,IAAI;CAAC;CAAc;CAAqB;CAAmB;CAAiB;AAAa,CAAC;AACrH,IAAM,+BAAe,IAAI,IAAI,CAAC,mBAAmB,sBAAsB,CAAC;;;;;;;AAUxE,SAAgB,iCAAiC,KAAuD;CACtG,MAAM,iBAA4C,EAAE,OAAO,EAAE,WAAW,IAAI,QAAQ,UAAU,EAAE;CAEhG,eAAe,SAAS,MAAiD;EACvE,MAAM,QAAQ,IAAI,mBAAmB,KAAK,QAAQ;EAClD,IAAI,OAAO,OAAO,cAAc,KAAK;EACrC,MAAM,UAAU,MAAM,eAAA,uBAAuB,gBAAgB;GAC3D,QAAQ,KAAK;GACb,UAAU,IAAI,KAAK,QAAQ;GAC3B,UAAU,IAAI,KAAK,QAAQ;EAC7B,CAAC;EACD,IAAI,CAAC,QAAQ,IAAI,OAAO,mBAAmB,OAAO;EAClD,OAAO;GAAE,IAAI;GAAM,QAAQ,QAAQ;GAAQ,UAAU,QAAQ;GAAU,SAAS,QAAQ;EAAQ;CAClG;CAEA,eAAe,WAAW,MAAqC,MAAiD;EAC9G,MAAM,QAAQ,IAAI,mBAAmB,KAAK,QAAQ;EAClD,IAAI,OAAO,OAAO,cAAc,KAAK;EACrC,MAAM,UAAU,SAAS,eAAe,MAAM,eAAA,kBAAkB,gBAAgB,IAAI,IAAI,MAAM,eAAA,oBAAoB,gBAAgB,IAAI;EACtI,OAAO,QAAQ,KAAK,EAAE,IAAI,KAAK,IAAI,mBAAmB,OAAO;CAC/D;CAEA,MAAM,aAAa;EAAE,aAAa,IAAI;EAAe,WAAW,IAAI;CAAY;CAChF,MAAM,iBAAiB;EAAE,WAAW,IAAI;EAAa,WAAW,IAAI;EAAa,WAAW,IAAI;CAAY;CAE5G,eAAe,UAAU,MAAc,MAAiD;EACtF,MAAM,WAAW,WAAW;EAC5B,IAAI,UAAU;GACZ,MAAM,WAAW,IAAI,KAAK,QAAQ;GAClC,OAAO,WAAW,SAAS,MAAM,SAAS,QAAQ,CAAC,IAAI,YAAY,IAAI;EACzE;EACA,IAAI,SAAS,kBAAkB;GAC7B,MAAM,SAAS,QAAQ,IAAI;GAC3B,OAAO,SAAS,SAAS,MAAM,IAAI,iBAAiB,OAAO,UAAU,OAAO,GAAG,CAAC,IAAI,YAAY,IAAI;EACtG;EACA,MAAM,SAAS,SAAS,IAAI;EAC5B,IAAI,CAAC,QAAQ,OAAO,YAAY,IAAI;EACpC,OAAO,SAAS,MAAM,eAAe,KAAoC,CAAC,OAAO,UAAU,OAAO,SAAS,CAAC;CAC9G;CAEA,eAAe,aAAa,MAAc,MAAiD;EACzF,MAAM,gBAAgB,IAAI,KAAK,aAAa;EAC5C,MAAM,QAAQ,KAAK,UAAU;EAC7B,IAAI,SAAS,mBAAmB,SAAS,eAAe;GACtD,MAAM,WAAW,IAAI,KAAK,QAAQ;GAClC,IAAI,CAAC,UAAU,OAAO,YAAY,IAAI;GAEtC,OAAO,SADQ,SAAS,kBAAkB,MAAM,IAAI,gBAAgB,UAAU,aAAa,IAAI,MAAM,IAAI,cAAc,UAAU,aAAa,CACxH;EACxB;EACA,IAAI,SAAS,mBAAmB;GAC9B,MAAM,SAAS,QAAQ,IAAI;GAC3B,OAAO,SAAS,SAAS,MAAM,IAAI,kBAAkB;IAAE,GAAG;IAAQ;IAAO;GAAc,CAAC,CAAC,IAAI,YAAY,IAAI;EAC/G;EACA,MAAM,SAAS,SAAS,IAAI;EAC5B,IAAI,CAAC,QAAQ,OAAO,YAAY,IAAI;EAGpC,OAAO,SADL,SAAS,eAAe,MAAM,IAAI,aAAa;GAAE,GAAG;GAAQ;GAAO;EAAc,CAAC,IAAI,MAAM,IAAI,oBAAoB;GAAE,GAAG;GAAQ;GAAO;EAAc,CAAC,CACnI;CACxB;CAEA,eAAe,WAAW,MAAc,MAAiD;EACvF,MAAM,YAAY,IAAI,KAAK,SAAS;EACpC,IAAI,CAAC,WAAW,OAAO,YAAY,IAAI;EACvC,IAAI,SAAS,wBAAwB;GACnC,MAAM,SAAS,QAAQ,IAAI;GAC3B,OAAO,SAAS,SAAS,MAAM,IAAI,uBAAuB,OAAO,UAAU,OAAO,KAAK,SAAS,CAAC,IAAI,YAAY,IAAI;EACvH;EACA,MAAM,SAAS,SAAS,IAAI;EAC5B,IAAI,CAAC,QAAQ,OAAO,YAAY,IAAI;EACpC,OAAO,SAAS,MAAM,IAAI,kBAAkB,OAAO,UAAU,OAAO,WAAW,SAAS,CAAC;CAC3F;CAEA,OAAO,OAAO,SAAoD;EAChE,MAAM,OAAO,IAAI,KAAK,IAAI;EAC1B,IAAI,CAAC,MAAM,OAAO,YAAY,WAAW;EACzC,IAAI,SAAS,QAAQ,OAAO,SAAS,IAAI;EACzC,IAAI,SAAS,gBAAgB,SAAS,gBAAgB,OAAO,WAAW,MAAM,IAAI;EAClF,IAAI,YAAY,IAAI,IAAI,GAAG,OAAO,UAAU,MAAM,IAAI;EACtD,IAAI,eAAe,IAAI,IAAI,GAAG,OAAO,aAAa,MAAM,IAAI;EAC5D,IAAI,aAAa,IAAI,IAAI,GAAG,OAAO,WAAW,MAAM,IAAI;EACxD,IAAI,SAAS,sBAAsB;GACjC,MAAM,WAAW,IAAI,KAAK,QAAQ;GAClC,IAAI,CAAC,UAAU,OAAO,YAAY,IAAI;GACtC,OAAO;IAAE,IAAI;IAAM,SAAS,IAAI,mBAAmB,QAAQ;GAAE;EAC/D;EACA,OAAO;GAAE,IAAI;GAAO,MAAM;GAAe,OAAO,sCAAsC,KAAK;EAAG;CAChG;AACF"}
|
|
1
|
+
{"version":3,"file":"server.cjs","names":[],"sources":["../src/server/support.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","// 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 {\n getFileObject,\n initializeContextFromFiles,\n generateBeatImage,\n getBeatPngImagePath,\n generateBeatAudio,\n getBeatAudioPathOrUrl,\n getBeatAnimatedVideoPath,\n getBeatMoviePaths,\n generateReferenceImage,\n getReferenceImagePath,\n images,\n audio,\n movie,\n movieFilePath,\n pdf,\n pdfFilePath,\n setGraphAILogger,\n addSessionProgressCallback,\n removeSessionProgressCallback,\n} from \"mulmocast\";\nimport type { MulmoBeat, MulmoImagePromptMedia, MulmoStudioContext } from \"@mulmocast/types\";\nimport type { MulmoScriptGenerationEvent } from \"../core/contract\";\nimport { normalizeStoryPath } from \"../core/paths\";\nimport { errorMessage } from \"@mulmoclaude/common\";\nimport { resolveWithinRoot } from \"@mulmoclaude/core/files\";\nimport { fileToDataUri, stripDataUri } from \"./support\";\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) => { 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 /**\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): string {\n return JSON.stringify([kind, filePath, key]);\n}\n\n/**\n * Build the per-host mulmoScript server ops instance. One instance per\n * process — it owns the in-flight movie/PDF dedup sets and the\n * generation-state tracker, and binds the injected host backend.\n */\nexport function createMulmoScriptServerOps(backend: MulmoScriptServerBackend) {\n const log = backend.log ?? NOOP_LOG;\n setMulmoErrorCaptureLogger(log);\n const storiesDir = path.resolve(backend.storiesDir);\n\n // ── Story path infrastructure ─────────────────────────────────\n\n // The download / status ops expect \"stories/<rel>\" (historical\n // convention, independent of the on-disk location) — the wire format\n // every endpoint keys on. Relativize against the REALPATH root when it\n // resolves: with a symlinked stories dir, mulmocast returns output\n // paths under the link's target, and relativizing against the link\n // itself would produce a traversal-like \"stories/../../…\" ref that\n // resolveStory then rejects (CodeRabbit on #2137).\n function toStoryRef(absolutePath: string): string {\n const root = ensureStoriesReal() ?? storiesDir;\n const rel = path.relative(root, absolutePath).split(path.sep).join(\"/\");\n return rel ? `stories/${rel}` : \"stories\";\n }\n\n // Lazily realpath the stories dir on first use. We can't realpath at\n // instance creation because the directory may not exist yet (it's\n // created on demand by the save route). The cache is invalidated\n // never — once the dir exists, its realpath is stable.\n let storiesRealCache: string | null = null;\n function ensureStoriesReal(): string | null {\n if (storiesRealCache) return storiesRealCache;\n try {\n mkdirSync(storiesDir, { recursive: true });\n storiesRealCache = realpathSync(storiesDir);\n return storiesRealCache;\n } catch {\n return null;\n }\n }\n\n /**\n * Resolve and validate a stories wire path to its absolute realpath.\n *\n * Uses the realpath-based resolveWithinRoot helper to defeat\n * symlink-based escapes. Callers pass wire paths like\n * \"stories/foo.json\" or \"stories/__movies__/bar.mp4\". We strip the\n * leading \"stories/\" segment and resolve the remainder against the\n * realpath of the stories directory itself — this works whether\n * stories/ is a regular directory or a legitimate symlink to another\n * location. ENOENT and traversal are distinguished (404 vs 400).\n */\n function resolveStory(filePath: string): { ok: true; absolutePath: string } | OpFailure {\n const storiesReal = ensureStoriesReal();\n if (!storiesReal) {\n return opServerError(\"stories directory not available\");\n }\n // Reject absolute paths and parent traversal at the syntactic\n // level — defense in depth on top of the realpath check below.\n if (path.isAbsolute(filePath)) {\n return opBadRequest(\"Invalid filePath\");\n }\n // Accept the workspace-relative spelling \"artifacts/stories/<rel>\"\n // the tool description historically taught (the wire form was truly\n // workspace-relative before the stories dir moved under artifacts/\n // in #284) by reducing it to the canonical \"stories/<rel>\".\n const ARTIFACTS_STORIES = \"artifacts/stories\";\n const wirePath = filePath === ARTIFACTS_STORIES || filePath.startsWith(`${ARTIFACTS_STORIES}/`) ? filePath.slice(\"artifacts/\".length) : filePath;\n // Strip the optional \"stories/\" prefix so the remainder is a path\n // relative to storiesReal. Accepts both \"stories/foo.json\" (the\n // canonical caller convention) and bare \"foo.json\".\n const STORIES_PREFIX = `stories${path.sep}`;\n const relFromStories =\n wirePath === \"stories\" ? \"\" : wirePath.startsWith(STORIES_PREFIX) || wirePath.startsWith(\"stories/\") ? wirePath.slice(\"stories/\".length) : wirePath;\n // A base path with no remainder (\"stories\", \"artifacts/stories\",\n // trailing-slash variants) would resolve to the stories directory\n // itself and hand downstream ops a directory where they expect a\n // file — reject it, mirroring normalizeStoryPath's non-empty rule.\n if (relFromStories === \"\") {\n return opBadRequest(\"Invalid filePath\");\n }\n // resolveWithinRoot enforces both the realpath boundary AND\n // existence; ENOENT and traversal both produce null. Distinguish\n // them via a follow-up existsSync so 404 vs 400 stays accurate —\n // but only consult the filesystem for lexically in-root candidates:\n // a traversal path must never touch the fs (and gets a uniform\n // bad_request so responses don't leak existence outside the root).\n const resolved = resolveWithinRoot(storiesReal, relFromStories);\n if (!resolved) {\n const candidate = path.resolve(storiesReal, relFromStories);\n const inRoot = candidate === storiesReal || candidate.startsWith(storiesReal + path.sep);\n if (inRoot && !existsSync(candidate)) {\n return opNotFound(`File not found: ${filePath}`);\n }\n return opBadRequest(\"Invalid filePath\");\n }\n return { ok: true, absolutePath: resolved };\n }\n\n /**\n * Realpath containment pre-guard for wire paths handed to the phase-1\n * core's save/reopen/update executes. The core's own path guard is\n * lexical (it runs against the generic FileOps, whose read/write follows\n * symlinks), so hosts re-assert the realpath boundary here before\n * invoking it — a symlink planted below the stories dir can't read or\n * write outside the tree (Codex P1 on MulmoClaude#2133).\n *\n * Returns null when `filePath` isn't a non-empty string — shape\n * validation (including the script-vs-filePath mode check) belongs to\n * the core.\n */\n function guardStoryWirePath(filePath: unknown): OpFailure | null {\n if (typeof filePath !== \"string\" || filePath === \"\") return null;\n const resolved = resolveStory(filePath);\n return resolved.ok ? null : resolved;\n }\n\n // mulmocast shells out to ffmpeg for movie / beat rendering. When the\n // host's probe reports it absent, intercept with a clear failure\n // instead of letting the library throw an opaque spawn ENOENT\n // mid-pipeline. `undefined` means the probe hasn't completed — assume\n // available so a brief startup window never blocks a render.\n function ffmpegGuard(): OpFailure | null {\n if (backend.isFfmpegAvailable?.() === false) {\n return {\n ok: false,\n code: \"unavailable\",\n error: \"ffmpeg is not installed — movie and beat rendering are unavailable. Install ffmpeg and restart the server.\",\n };\n }\n return null;\n }\n\n // ── Generation tracker (edge-triggered) ───────────────────────\n\n // Refcounted: two concurrent generations with the same kind/filePath/key\n // (e.g. the same beat rendered from two tabs) must not have the first\n // completion erase the second run's snapshot entry, and only the first\n // start / LAST finish reach the host channels — an early completion\n // can't clear subscribers' spinners while a duplicate run is active.\n // A finish with no tracked start (the movie/PDF pipelines' per-beat\n // completion pulses) always publishes.\n const inFlightGenerations = new Map<string, { kind: GenerationKind; filePath: string; key: string; count: number }>();\n\n /** Tracker state and events key on the canonical `stories/<rel>` wire\n * form: subscribers (the View's pubsub filter, `pendingGenerations`\n * callers) match by exact string, so the accepted alias spellings\n * (`artifacts/stories/<rel>`, bare `<rel>`) must collapse to the same\n * key as the canonical one (Codex P2 on #2139). Untrusted spellings\n * pass through unchanged — they never resolve, so they can't collide. */\n function canonicalWirePath(filePath: string): string {\n return normalizeStoryPath(filePath) ?? filePath;\n }\n\n function publishGeneration(chatSessionId: string | undefined, kind: GenerationKind, filePath: string, key: string, finished: boolean, error?: string): void {\n const wirePath = canonicalWirePath(filePath);\n const mapKey = generationMapKey(kind, wirePath, key);\n const existing = inFlightGenerations.get(mapKey);\n if (finished) {\n if (existing && existing.count > 1) {\n existing.count -= 1;\n return; // a duplicate run is still active — suppress the early finish\n }\n inFlightGenerations.delete(mapKey);\n } else {\n if (existing) {\n existing.count += 1;\n return; // already reported as started\n }\n inFlightGenerations.set(mapKey, { kind, filePath: wirePath, key, count: 1 });\n }\n const event: MulmoScriptGenerationEvent = { kind, filePath: wirePath, key, done: finished, ...(error ? { error } : {}) };\n backend.onGenerationEvent?.(chatSessionId, event);\n }\n\n /** Snapshot of generations currently in flight for one script — the\n * View's mount-time catch-up, filtered to its wire `filePath`. */\n function pendingGenerations(filePath: string): MulmoScriptGenerationEvent[] {\n const wirePath = canonicalWirePath(filePath);\n return [...inFlightGenerations.values()]\n .filter((entry) => entry.filePath === wirePath)\n .map(({ kind, key }) => ({ kind, filePath: wirePath, key, done: false }));\n }\n\n // ── Op scaffolding ────────────────────────────────────────────\n\n /**\n * Shared scaffolding for mulmoScript ops. Resolves the wire filePath,\n * builds the mulmo context, and folds unexpected handler errors into a\n * server_error failure (with a warn breadcrumb). Accepts a `deps` param\n * so unit tests can inject fakes without the full mulmocast stack.\n */\n async function runStoryOp<T>(\n filePath: string,\n options: RunStoryOpOptions<T>,\n handler: (ctx: { absoluteFilePath: string; context: StoryContext }) => Promise<OpResult<T>>,\n deps: RunStoryOpDeps = {},\n ): Promise<OpResult<T>> {\n const resolver = deps.resolveStory ?? resolveStory;\n const build = deps.buildContext ?? buildContext;\n const resolved = resolver(filePath);\n if (!resolved.ok) return resolved;\n try {\n const context = await build(resolved.absolutePath, options.force ?? false);\n if (!context) {\n if (options.onContextMissing) return options.onContextMissing();\n return opServerError(\"Failed to initialize mulmo context\");\n }\n // withMulmoErrorCapture appends the underlying provider error\n // (missing API key, quota, …) to any mulmocast failure, which\n // otherwise reaches the client as a generic \"generate error\".\n return await withMulmoErrorCapture(() => handler({ absoluteFilePath: resolved.absolutePath, context }));\n } catch (err) {\n // Log every op failure at warn so operators get a breadcrumb even\n // when the op doesn't wrap its own try/catch.\n log.warn(\"op failed\", {\n ...(options.operation ? { operation: options.operation } : {}),\n filePath,\n error: errorMessage(err),\n });\n return opServerError(errorMessage(err));\n }\n }\n\n // ── Probe ops ─────────────────────────────────────────────────\n\n async function beatImageOp(filePath: string, beatIndex: number): Promise<OpResult<{ image: string | null }>> {\n return runStoryOp<{ image: string | null }>(filePath, { operation: \"beat-image\" }, async ({ context }) => {\n const { imagePath } = getBeatPngImagePath(context, beatIndex);\n if (!existsSync(imagePath)) return { ok: true, image: null };\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n }\n\n // beatAudio is a probe — the frontend polls it expecting `{ audio: null }`\n // when nothing has been generated yet. Override the default\n // server_error-on-context-missing so the soft-fail contract is preserved.\n async function beatAudioOp(filePath: string, beatIndex: number): Promise<OpResult<{ audio: string | null }>> {\n return runStoryOp<{ audio: string | null }>(\n filePath,\n { operation: \"beat-audio\", onContextMissing: () => ({ ok: true, audio: null }) },\n async ({ context }) => {\n const beat = context.studio.script.beats[beatIndex];\n // 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): Promise<OpResult<{ moviePath: string | null }>> {\n return runStoryOp<{ moviePath: string | null }>(filePath, { operation: \"beat-movie\" }, async ({ context }) => {\n const { movieFile, soundEffectFile, lipSyncFile } = getBeatMoviePaths(context, beatIndex);\n const candidates = [lipSyncFile, soundEffectFile, movieFile, getBeatAnimatedVideoPath(context, beatIndex)];\n const existing = candidates.find((candidate) => existsSync(candidate));\n return { ok: true, moviePath: existing ? toStoryRef(existing) : null };\n });\n }\n\n async function characterImageOp(filePath: string, key: string): Promise<OpResult<{ image: string | null }>> {\n return runStoryOp<{ image: string | null }>(filePath, { operation: \"character-image\" }, async ({ context }) => {\n const imagePath = getReferenceImagePath(context, key, \"png\");\n if (!existsSync(imagePath)) return { ok: true, image: null };\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n }\n\n /** Shared \"output exists and is newer than the source script\" gate for\n * movie / PDF status. A stale artifact (script edited after it was\n * generated) reports null so the UI re-offers the Generate button. */\n function freshOutputRef(outputPath: string, absoluteFilePath: string): string | null {\n if (!existsSync(outputPath)) return null;\n const outputMtime = statSync(outputPath).mtimeMs;\n const sourceMtime = statSync(absoluteFilePath).mtimeMs;\n if (outputMtime < sourceMtime) return null;\n return toStoryRef(outputPath);\n }\n\n async function movieStatusOp(filePath: string): Promise<OpResult<{ moviePath: string | null }>> {\n return runStoryOp(\n filePath,\n { operation: \"movie-status\", onContextMissing: () => ({ ok: true, moviePath: null }) },\n async ({ absoluteFilePath, context }) => ({ ok: true, moviePath: freshOutputRef(movieFilePath(context), absoluteFilePath) }),\n );\n }\n\n async function pdfStatusOp(filePath: string): Promise<OpResult<{ pdfPath: string | null }>> {\n return runStoryOp(filePath, { operation: \"pdf-status\", onContextMissing: () => ({ ok: true, pdfPath: null }) }, async ({ absoluteFilePath, context }) => ({\n ok: true,\n pdfPath: freshOutputRef(pdfFilePath(context, PDF_MODE), absoluteFilePath),\n }));\n }\n\n // ── Generation ops ────────────────────────────────────────────\n\n async function renderBeatOp(args: GenerateOpArgsWith<\"filePath\" | \"beatIndex\">): Promise<OpResult<{ image: string }>> {\n const { filePath, beatIndex, force, chatSessionId } = args;\n const ffmpeg = ffmpegGuard();\n if (ffmpeg) return ffmpeg;\n\n const mapKey = String(beatIndex);\n publishGeneration(chatSessionId, \"beatImage\", filePath, mapKey, false);\n let genError: string | undefined;\n try {\n const result = await runStoryOp<{ image: string }>(filePath, { force, operation: \"render-beat\" }, async ({ context }) => {\n await generateBeatImage({\n index: beatIndex,\n context,\n ...(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, genError);\n }\n }\n\n async function generateBeatAudioOp(args: GenerateOpArgsWith<\"filePath\" | \"beatIndex\">): Promise<OpResult<{ audio: string }>> {\n const { filePath, beatIndex, force, chatSessionId } = args;\n const mapKey = String(beatIndex);\n publishGeneration(chatSessionId, \"beatAudio\", filePath, mapKey, false);\n let genError: string | undefined;\n try {\n const result = await runStoryOp<{ audio: string }>(filePath, { force, operation: \"generate-beat-audio\" }, async ({ context }) => {\n await generateBeatAudio(beatIndex, context, {\n settings: process.env as Record<string, string>,\n } as Parameters<typeof generateBeatAudio>[2]);\n\n const beat = context.studio.script.beats[beatIndex];\n // 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, genError);\n }\n }\n\n async function renderCharacterOp(args: GenerateOpArgsWith<\"filePath\" | \"key\">): Promise<OpResult<{ image: string }>> {\n const { filePath, key, force, chatSessionId } = args;\n publishGeneration(chatSessionId, \"characterImage\", filePath, key, false);\n let genError: string | undefined;\n try {\n const result = await runStoryOp<{ image: string }>(filePath, { force, operation: \"render-character\" }, async ({ context }) => {\n // `imageEntries` (not `images`) to avoid shadowing mulmocast's\n // imported `images()` pipeline stage.\n const imageEntries = context.studio.script.imageParams?.images ?? {};\n const imageEntry = imageEntries[key];\n if (!imageEntry || imageEntry.type !== \"imagePrompt\") {\n return opBadRequest(`No imagePrompt entry for key: ${key}`);\n }\n\n const index = Object.keys(imageEntries).indexOf(key);\n const imagePath = getReferenceImagePath(context, key, \"png\");\n mkdirSync(path.dirname(imagePath), { recursive: true });\n\n await generateReferenceImage({\n context,\n key,\n index,\n image: imageEntry as MulmoImagePromptMedia,\n ...(force !== 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, genError);\n }\n }\n\n // ── Upload ops ────────────────────────────────────────────────\n\n async function uploadBeatImageOp(filePath: string, beatIndex: number, imageData: string): Promise<OpResult<{ image: string }>> {\n return runStoryOp<{ image: string }>(filePath, { operation: \"upload-beat-image\" }, async ({ context }) => {\n const { imagePath } = getBeatPngImagePath(context, beatIndex);\n // writeFileAtomic creates parent dirs and prevents a half-\n // written PNG from surviving a crash mid-write (#881 v2).\n const base64 = stripDataUri(imageData);\n await backend.writeFileAtomic(imagePath, Buffer.from(base64, \"base64\"));\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n }\n\n async function uploadCharacterImageOp(filePath: string, key: string, imageData: string): Promise<OpResult<{ image: string }>> {\n return runStoryOp<{ image: string }>(filePath, { operation: \"upload-character-image\" }, async ({ context }) => {\n const imagePath = getReferenceImagePath(context, key, \"png\");\n const base64 = stripDataUri(imageData);\n await backend.writeFileAtomic(imagePath, Buffer.from(base64, \"base64\"));\n return { ok: true, image: await fileToDataUri(imagePath, \"image/png\") };\n });\n }\n\n // ── Movie / PDF pipelines ─────────────────────────────────────\n\n // Per-instance dedup so a foreground call (SSE route or long-held\n // dispatch) and a fire-and-forget background call can't race on the same\n // script. Keyed by the realpath (absoluteFilePath) so two different wire\n // spellings of the same file still collide. Process-local — a\n // multi-process deployment would need an external lock; out of scope.\n const inFlightMovies = new Set<string>();\n\n // Same dedup model as inFlightMovies, scoped to PDF generation\n // (#1614). PDFs and movies don't share the lock — they write to\n // different output files and can safely run in parallel.\n const inFlightPdfs = new Set<string>();\n\n // Shared core for the SSE-streaming route, the long-held dispatch op, and\n // the fire-and-forget background path triggered by `autoGenerateMovie`.\n // Builds the mulmo context, runs audio→images→movie, and reports\n // per-beat progress through the supplied callback. Throws on\n // unexpected pipeline errors; returns a structured failure when the\n // pipeline runs to completion but the output file is missing.\n async function runMovieGeneration(absoluteFilePath: string, onProgressEvent: (event: MovieProgressEvent) => void): Promise<MovieGenerationResult> {\n return withMulmoErrorCapture(() => runMoviePipeline(absoluteFilePath, onProgressEvent));\n }\n\n async function runMoviePipeline(absoluteFilePath: string, onProgressEvent: (event: MovieProgressEvent) => void): Promise<MovieGenerationResult> {\n const context = await buildContext(absoluteFilePath);\n if (!context) return { ok: false, error: \"Failed to initialize mulmo context\" };\n\n 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): Promise<OpResult<{ moviePath: string }>> {\n const ffmpeg = ffmpegGuard();\n if (ffmpeg) return ffmpeg;\n const resolved = resolveStory(filePath);\n if (!resolved.ok) return resolved;\n const absoluteFilePath = resolved.absolutePath;\n\n if (inFlightMovies.has(absoluteFilePath)) {\n return opBadRequest(\"Movie generation is already in progress for this script\");\n }\n\n inFlightMovies.add(absoluteFilePath);\n publishGeneration(chatSessionId, \"movie\", filePath, \"\", false);\n let genError: string | undefined;\n try {\n const result = await runMovieGeneration(absoluteFilePath, (event) => {\n const eventKind = event.kind === \"image\" ? \"beatImage\" : \"beatAudio\";\n publishGeneration(chatSessionId, eventKind, filePath, String(event.beatIndex), true);\n });\n if (!result.ok) {\n genError = result.error;\n return opServerError(result.error);\n }\n return { ok: true, moviePath: toStoryRef(result.outputPath) };\n } catch (err) {\n genError = errorMessage(err);\n return opServerError(genError);\n } finally {\n inFlightMovies.delete(absoluteFilePath);\n publishGeneration(chatSessionId, \"movie\", filePath, \"\", true, genError);\n }\n }\n\n function triggerAutoBackgroundMovie(absoluteFilePath: string, wireFilePath: string, chatSessionId: string | undefined): void {\n if (inFlightMovies.has(absoluteFilePath)) return;\n inFlightMovies.add(absoluteFilePath);\n void runBackgroundMovieGeneration(absoluteFilePath, wireFilePath, chatSessionId);\n }\n\n // Detached movie generation. Reports progress through the generation\n // channels the View watches — so a user opening the canvas\n // mid-generation sees spinners, and a user opening it after completion\n // sees the finished movie loaded from disk by the View's normal\n // mount-time path. Errors are persisted to a `<filename>.error.txt`\n // sidecar next to the script (no synchronous client to alert); any\n // stale sidecar from a previous run is cleared on each new attempt.\n // Triggered server-side from the unified save route when the caller\n // passes `autoGenerateMovie: true`.\n async function runBackgroundMovieGeneration(absoluteFilePath: string, wireFilePath: string, chatSessionId: string | undefined): Promise<void> {\n const errorSidecarPath = `${absoluteFilePath}.error.txt`;\n // Clear stale error from a previous failed run before starting; if it\n // doesn't exist that's fine. Catch any unexpected fs errors silently —\n // the worst case is the user sees an out-of-date error file later.\n try {\n unlinkSync(errorSidecarPath);\n } catch {\n // intentional: ENOENT is the common case, others non-fatal\n }\n\n publishGeneration(chatSessionId, \"movie\", wireFilePath, \"\", false);\n let genError: string | undefined;\n try {\n const result = await runMovieGeneration(absoluteFilePath, (event) => {\n // Mirror per-beat completions through the generation channels so\n // subscribed Views reload the asset off disk. We fire start+finish\n // in two ticks — `setImmediate` lets the session SSE writer flush\n // the start event before the finish removes the entry, otherwise\n // Vue's batched reactivity could see a net \"no change\" and skip\n // the reload.\n const eventKind = event.kind === \"image\" ? \"beatImage\" : \"beatAudio\";\n const key = String(event.beatIndex);\n publishGeneration(chatSessionId, eventKind, wireFilePath, key, false);\n setImmediate(() => publishGeneration(chatSessionId, eventKind, wireFilePath, key, true));\n });\n\n if (!result.ok) {\n genError = result.error;\n await writeErrorSidecar(errorSidecarPath, result.error);\n log.warn(\"background movie generation failed\", { filePath: wireFilePath, error: result.error });\n return;\n }\n log.info(\"background movie generation done\", {\n filePath: wireFilePath,\n outputPath: result.outputPath,\n });\n } catch (err) {\n genError = 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, genError);\n }\n }\n\n // Atomic write so a crash mid-write can't leave a truncated sidecar.\n async function writeErrorSidecar(errorSidecarPath: string, message: string): Promise<void> {\n try {\n await backend.writeFileAtomic(errorSidecarPath, message);\n } catch (writeErr) {\n log.error(\"failed to write error sidecar\", {\n errorSidecarPath,\n error: 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): Promise<OpResult<{ pdfPath: string }>> {\n const ffmpeg = ffmpegGuard();\n if (ffmpeg) return ffmpeg;\n const resolved = resolveStory(filePath);\n if (!resolved.ok) return resolved;\n const absoluteFilePath = resolved.absolutePath;\n\n if (inFlightPdfs.has(absoluteFilePath)) {\n return opBadRequest(\"PDF generation is already in progress for this script\");\n }\n\n inFlightPdfs.add(absoluteFilePath);\n publishGeneration(chatSessionId, \"pdf\", filePath, \"\", false);\n let genError: string | undefined;\n try {\n const context = await buildContext(absoluteFilePath);\n if (!context) {\n genError = \"Failed to initialize mulmo context\";\n return opServerError(genError);\n }\n const result = await runPdfGeneration(context, (beatIndex) => {\n publishGeneration(chatSessionId, \"beatImage\", filePath, String(beatIndex), true);\n });\n if (!result.ok) {\n genError = result.error;\n return opServerError(result.error);\n }\n return { ok: true, pdfPath: toStoryRef(result.outputPath) };\n } catch (err) {\n genError = errorMessage(err);\n return opServerError(genError);\n } finally {\n inFlightPdfs.delete(absoluteFilePath);\n publishGeneration(chatSessionId, \"pdf\", filePath, \"\", true, genError);\n }\n }\n\n return {\n backend,\n toStoryRef,\n resolveStory,\n guardStoryWirePath,\n ffmpegGuard,\n runStoryOp,\n publishGeneration,\n pendingGenerations,\n beatImageOp,\n beatAudioOp,\n beatMovieOp,\n characterImageOp,\n movieStatusOp,\n pdfStatusOp,\n renderBeatOp,\n generateBeatAudioOp,\n renderCharacterOp,\n uploadBeatImageOp,\n uploadCharacterImageOp,\n inFlightMovies,\n inFlightPdfs,\n runMovieGeneration,\n runPdfGeneration,\n generateMovieOp,\n generatePdfOp,\n triggerAutoBackgroundMovie,\n };\n}\n\nexport type MulmoScriptServerOps = ReturnType<typeof createMulmoScriptServerOps>;\n","// The mulmoScript dispatch router, moved from MulmoClaude's\n// `server/plugins/mulmoscript-builtin.ts` in phase 3 so every host serves\n// the package View's `useRuntime().dispatch({ kind, … })` calls with the\n// SAME kind routing and validation. Hosts register the returned handler on\n// their dispatch channel (MulmoClaude: `registerBuiltinDispatch`;\n// MulmoTerminal: its `/api/plugin` interception).\n//\n// Response contract: every kind resolves to an `{ ok: … }` envelope (see\n// `../core/contract.ts`) — business failures are data, not thrown errors,\n// so user-facing messages stay free of transport prefixes.\n\nimport { executeMulmoScriptSave, executeUpdateBeat, executeUpdateScript, type MulmoScriptFailure } from \"../core/plugin\";\nimport type { MulmoScriptExecuteContext } from \"../core/types\";\nimport type { MulmoScriptServerOps } from \"./ops\";\nimport type { OpFailure } from \"./types\";\n\ninterface DispatchFailure {\n ok: false;\n code: \"bad_request\" | \"not_found\" | \"server_error\";\n error: string;\n}\n\nfunction fromOpFailure(failure: OpFailure): DispatchFailure {\n // \"unavailable\" (ffmpeg missing) has no slot in the contract's code\n // union — the View only reads `error`, so fold it into server_error\n // rather than widening the shared contract for one case.\n const code = failure.code === \"unavailable\" ? \"server_error\" : failure.code;\n return { ok: false, code, error: failure.error };\n}\n\nfunction fromPackageFailure(failure: MulmoScriptFailure): DispatchFailure {\n return { ok: false, code: failure.code, error: failure.error };\n}\n\nfunction invalidArgs(kind: string): DispatchFailure {\n return { ok: false, code: \"bad_request\", error: `invalid arguments for mulmoScript dispatch kind \"${kind}\"` };\n}\n\nfunction str(value: unknown): string | undefined {\n return typeof value === \"string\" && value !== \"\" ? value : undefined;\n}\n\n// Beat indexes must be non-negative integers — reject `-1` / `1.5` at the\n// dispatch boundary so invalid client input surfaces as a deterministic\n// bad_request instead of leaking into beat-indexed ops.\nfunction num(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0 ? value : undefined;\n}\n\ninterface BeatArgs {\n filePath: string;\n beatIndex: number;\n}\n\ninterface KeyArgs {\n filePath: string;\n key: string;\n}\n\n/** Pass ok results through untouched; normalize failures for the wire. */\nfunction envelope<T>(result: ({ ok: true } & T) | OpFailure): ({ ok: true } & T) | DispatchFailure {\n return result.ok ? result : fromOpFailure(result);\n}\n\nfunction beatArgs(args: Record<string, unknown>): BeatArgs | null {\n const filePath = str(args.filePath);\n const beatIndex = num(args.beatIndex);\n if (!filePath || beatIndex === undefined) return null;\n return { filePath, beatIndex };\n}\n\nfunction keyArgs(args: Record<string, unknown>): KeyArgs | null {\n const filePath = str(args.filePath);\n const key = str(args.key);\n if (!filePath || !key) return null;\n return { filePath, key };\n}\n\nconst PROBE_KINDS = new Set([\"beatImage\", \"beatAudio\", \"beatMovie\", \"characterImage\", \"movieStatus\", \"pdfStatus\"]);\nconst GENERATE_KINDS = new Set([\"renderBeat\", \"generateBeatAudio\", \"renderCharacter\", \"generateMovie\", \"generatePdf\"]);\nconst UPLOAD_KINDS = new Set([\"uploadBeatImage\", \"uploadCharacterImage\"]);\n\nexport type MulmoScriptDispatchHandler = (args: Record<string, unknown>) => Promise<unknown>;\n\n/**\n * Build the kind router over an ops instance. The save / reopen / update\n * kinds run the phase-1 core executes against the backend's artifacts\n * FileOps, guarded by the instance's realpath containment\n * (`guardStoryWirePath`) — the core's own guard is lexical.\n */\nexport function createMulmoScriptDispatchHandler(ops: MulmoScriptServerOps): MulmoScriptDispatchHandler {\n const executeContext: MulmoScriptExecuteContext = { files: { artifacts: ops.backend.artifacts } };\n\n async function saveKind(args: Record<string, unknown>): Promise<unknown> {\n const guard = ops.guardStoryWirePath(args.filePath);\n if (guard) return fromOpFailure(guard);\n const outcome = await executeMulmoScriptSave(executeContext, {\n script: args.script,\n filename: str(args.filename),\n filePath: str(args.filePath),\n });\n if (!outcome.ok) return fromPackageFailure(outcome);\n return { ok: true, script: outcome.script, filePath: outcome.filePath, message: outcome.message };\n }\n\n async function updateKind(kind: \"updateBeat\" | \"updateScript\", args: Record<string, unknown>): Promise<unknown> {\n const guard = ops.guardStoryWirePath(args.filePath);\n if (guard) return fromOpFailure(guard);\n const outcome = kind === \"updateBeat\" ? await executeUpdateBeat(executeContext, args) : await executeUpdateScript(executeContext, args);\n return outcome.ok ? { ok: true } : fromPackageFailure(outcome);\n }\n\n const STATUS_OPS = { movieStatus: ops.movieStatusOp, pdfStatus: ops.pdfStatusOp } as const;\n const BEAT_PROBE_OPS = { beatImage: ops.beatImageOp, beatAudio: ops.beatAudioOp, beatMovie: ops.beatMovieOp } as const;\n\n async function probeKind(kind: string, args: Record<string, unknown>): Promise<unknown> {\n const statusOp = STATUS_OPS[kind as keyof typeof STATUS_OPS];\n if (statusOp) {\n const filePath = str(args.filePath);\n return filePath ? envelope(await statusOp(filePath)) : invalidArgs(kind);\n }\n if (kind === \"characterImage\") {\n const parsed = keyArgs(args);\n return parsed ? envelope(await ops.characterImageOp(parsed.filePath, parsed.key)) : invalidArgs(kind);\n }\n const parsed = beatArgs(args);\n if (!parsed) return invalidArgs(kind);\n return envelope(await BEAT_PROBE_OPS[kind as keyof typeof BEAT_PROBE_OPS](parsed.filePath, parsed.beatIndex));\n }\n\n async function generateKind(kind: string, args: Record<string, unknown>): Promise<unknown> {\n const chatSessionId = str(args.chatSessionId);\n const force = args.force === true;\n if (kind === \"generateMovie\" || kind === \"generatePdf\") {\n const filePath = str(args.filePath);\n if (!filePath) return invalidArgs(kind);\n const result = kind === \"generateMovie\" ? await ops.generateMovieOp(filePath, chatSessionId) : await ops.generatePdfOp(filePath, chatSessionId);\n return envelope(result);\n }\n if (kind === \"renderCharacter\") {\n const parsed = keyArgs(args);\n return parsed ? envelope(await ops.renderCharacterOp({ ...parsed, force, chatSessionId })) : invalidArgs(kind);\n }\n const parsed = beatArgs(args);\n if (!parsed) return invalidArgs(kind);\n const result =\n kind === \"renderBeat\" ? await ops.renderBeatOp({ ...parsed, force, chatSessionId }) : await ops.generateBeatAudioOp({ ...parsed, force, chatSessionId });\n return envelope(result);\n }\n\n async function uploadKind(kind: string, args: Record<string, unknown>): Promise<unknown> {\n const imageData = str(args.imageData);\n if (!imageData) return invalidArgs(kind);\n if (kind === \"uploadCharacterImage\") {\n const parsed = keyArgs(args);\n return parsed ? envelope(await ops.uploadCharacterImageOp(parsed.filePath, parsed.key, imageData)) : invalidArgs(kind);\n }\n const parsed = beatArgs(args);\n if (!parsed) return invalidArgs(kind);\n return envelope(await ops.uploadBeatImageOp(parsed.filePath, parsed.beatIndex, imageData));\n }\n\n return async (args: Record<string, unknown>): Promise<unknown> => {\n const kind = str(args.kind);\n if (!kind) return invalidArgs(\"<missing>\");\n if (kind === \"save\") return saveKind(args);\n if (kind === \"updateBeat\" || kind === \"updateScript\") return updateKind(kind, args);\n if (PROBE_KINDS.has(kind)) return probeKind(kind, args);\n if (GENERATE_KINDS.has(kind)) return generateKind(kind, args);\n if (UPLOAD_KINDS.has(kind)) return uploadKind(kind, args);\n if (kind === \"pendingGenerations\") {\n const filePath = str(args.filePath);\n if (!filePath) return invalidArgs(kind);\n return { ok: true, pending: ops.pendingGenerations(filePath) };\n }\n return { ok: false, code: \"bad_request\", error: `unknown mulmoScript dispatch kind \"${kind}\"` };\n };\n}\n"],"mappings":"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"}
|