mulmoclaude 1.2.0 → 1.3.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.
Files changed (75) hide show
  1. package/client/assets/{PluginScopedRoot-CjiZ2FJI.js → PluginScopedRoot-B53YSjaC.js} +1 -1
  2. package/client/assets/index-D3czxq1I.css +2 -0
  3. package/client/assets/{index-yQkLdtMk.js → index-DUdqhWg6.js} +42 -43
  4. package/client/assets/{marp-Bs_qwoKZ.js → marp-CyWxL3bc.js} +12 -3
  5. package/client/index.html +3 -3
  6. package/package.json +6 -5
  7. package/server/agent/attachmentConverter.ts +0 -25
  8. package/server/api/auth/viewToken.ts +10 -8
  9. package/server/api/routes/agent.ts +35 -10
  10. package/server/api/routes/collections.ts +150 -96
  11. package/server/api/routes/files.ts +57 -54
  12. package/server/api/routes/mulmo-script.ts +257 -895
  13. package/server/api/routes/wiki/history.ts +17 -19
  14. package/server/events/collection-change.ts +0 -5
  15. package/server/events/file-change.ts +1 -6
  16. package/server/index.ts +25 -76
  17. package/server/plugins/diagnostics.ts +0 -6
  18. package/server/plugins/mulmoscript-server.ts +58 -0
  19. package/server/prompts/system/system.md +2 -2
  20. package/server/remoteHost/handlers/getCollection.ts +5 -4
  21. package/server/remoteHost/handlers/googleCalendar.ts +50 -3
  22. package/server/remoteHost/handlers/index.ts +3 -1
  23. package/server/utils/files/html-store.ts +2 -16
  24. package/server/utils/files/journal-io.ts +0 -12
  25. package/server/utils/files/plugins-io.ts +2 -11
  26. package/server/utils/files/safe.ts +39 -0
  27. package/server/utils/httpError.ts +6 -0
  28. package/server/utils/time.ts +0 -3
  29. package/server/workspace/collections/index.ts +1 -0
  30. package/server/workspace/collections/remoteView.ts +13 -4
  31. package/server/workspace/journal/state.ts +1 -9
  32. package/server/workspace/memory/topic-index-hook.ts +0 -9
  33. package/server/workspace/memory/topic-io.ts +0 -4
  34. package/server/workspace/skills/external/catalog.ts +24 -13
  35. package/server/workspace/skills/writer.ts +20 -16
  36. package/server/workspace/wiki-pages/snapshot.ts +13 -12
  37. package/src/composables/useFileDropZone.ts +3 -15
  38. package/src/config/apiRoutes.ts +6 -0
  39. package/src/config/roles.ts +0 -4
  40. package/src/lang/de.ts +0 -30
  41. package/src/lang/en.ts +0 -30
  42. package/src/lang/es.ts +0 -30
  43. package/src/lang/fr.ts +0 -30
  44. package/src/lang/ja.ts +0 -30
  45. package/src/lang/ko.ts +0 -30
  46. package/src/lang/pt-BR.ts +0 -30
  47. package/src/lang/zh.ts +0 -30
  48. package/src/plugins/accounting/index.ts +0 -2
  49. package/src/plugins/api.ts +0 -6
  50. package/src/plugins/canvas/index.ts +0 -2
  51. package/src/plugins/chart/index.ts +0 -2
  52. package/src/plugins/editImages/index.ts +0 -2
  53. package/src/plugins/generateImage/index.ts +0 -2
  54. package/src/plugins/manageSkills/index.ts +0 -2
  55. package/src/plugins/markdown/index.ts +0 -2
  56. package/src/plugins/photoLocations/index.ts +0 -2
  57. package/src/plugins/presentCollection/index.ts +0 -2
  58. package/src/plugins/presentForm/index.ts +0 -2
  59. package/src/plugins/presentHtml/index.ts +0 -2
  60. package/src/plugins/presentMulmoScript/definition.ts +11 -116
  61. package/src/plugins/presentMulmoScript/index.ts +62 -12
  62. package/src/plugins/presentSVG/index.ts +0 -2
  63. package/src/plugins/skill/index.ts +0 -3
  64. package/src/plugins/spreadsheet/engine/parser.ts +0 -103
  65. package/src/plugins/spreadsheet/index.ts +0 -2
  66. package/src/plugins/textResponse/index.ts +0 -3
  67. package/src/plugins/wiki/index.ts +0 -2
  68. package/src/types/notification.ts +0 -10
  69. package/src/utils/api.ts +0 -4
  70. package/client/assets/index-D5nJbMO1.css +0 -2
  71. package/server/api/routes/mulmoScriptValidate.ts +0 -101
  72. package/server/utils/mulmoErrorCapture.ts +0 -93
  73. package/src/plugins/presentMulmoScript/Preview.vue +0 -23
  74. package/src/plugins/presentMulmoScript/View.vue +0 -1949
  75. package/src/plugins/presentMulmoScript/helpers.ts +0 -206
@@ -1,206 +0,0 @@
1
- // Pure helpers for presentMulmoScript View.vue. Kept separate so
2
- // their logic is unit-testable without mounting the Vue component.
3
-
4
- import { errorMessage } from "../../utils/errors";
5
- import { isRecord } from "../../utils/types";
6
-
7
- export type SSEEvent =
8
- | { type: "beat_image_done"; beatIndex: number }
9
- | { type: "beat_audio_done"; beatIndex: number }
10
- | { type: "done"; moviePath: string }
11
- | { type: "error"; message: string }
12
- | { type: "unknown" };
13
-
14
- /**
15
- * Parse a single SSE line of the form `data: {json}`. Returns
16
- * null for non-data lines (comments, blank) or lines whose JSON
17
- * payload fails to parse. Unrecognised event types still parse
18
- * but resolve to `{ type: "unknown" }` so the caller can ignore
19
- * them without crashing.
20
- */
21
- export function parseSSEEventLine(line: string): SSEEvent | null {
22
- if (!line.startsWith("data: ")) return null;
23
- let obj: unknown;
24
- try {
25
- obj = JSON.parse(line.slice(6));
26
- } catch {
27
- return null;
28
- }
29
- if (!isRecord(obj)) return null;
30
- const event = obj;
31
- if (event.type === "beat_image_done" && typeof event.beatIndex === "number") {
32
- return { type: "beat_image_done", beatIndex: event.beatIndex };
33
- }
34
- if (event.type === "beat_audio_done" && typeof event.beatIndex === "number") {
35
- return { type: "beat_audio_done", beatIndex: event.beatIndex };
36
- }
37
- if (event.type === "done" && typeof event.moviePath === "string") {
38
- return { type: "done", moviePath: event.moviePath };
39
- }
40
- if (event.type === "error" && typeof event.message === "string") {
41
- return { type: "error", message: event.message };
42
- }
43
- return { type: "unknown" };
44
- }
45
-
46
- /**
47
- * Decide whether a beat should be rendered automatically at
48
- * script load time. Text-based beats (slides, charts, etc.) are
49
- * auto-rendered only when the script has no characters —
50
- * characters must be rendered first so they can be referenced by
51
- * any character-using beat.
52
- */
53
- export function shouldAutoRenderBeat(beat: { image?: { type?: string } }, hasCharacters: boolean, autoRenderTypes: readonly string[]): boolean {
54
- if (hasCharacters) return false;
55
- const type = beat.image?.type;
56
- if (typeof type !== "string") return false;
57
- return autoRenderTypes.includes(type);
58
- }
59
-
60
- /**
61
- * Of the given character keys, return those whose image is not
62
- * yet loaded and is not currently rendering. Used to fetch only
63
- * what's missing after a movie-generation event arrives.
64
- */
65
- export function getMissingCharacterKeys(keys: readonly string[], images: Record<string, unknown>, renderState: Record<string, string | undefined>): string[] {
66
- return keys.filter((charKey) => !images[charKey] && renderState[charKey] !== "rendering");
67
- }
68
-
69
- /**
70
- * A schema shape that exposes `safeParse` — matches Zod's API
71
- * without pulling the dep into this module.
72
- */
73
- export interface SafeParseSchema {
74
- safeParse: (value: unknown) => { success: boolean };
75
- }
76
-
77
- /**
78
- * Validate a candidate Beat JSON string against a schema.
79
- * Returns false on any JSON parse error or schema mismatch.
80
- */
81
- export function validateBeatJSON(json: string, schema: SafeParseSchema): boolean {
82
- let parsed: unknown;
83
- try {
84
- parsed = JSON.parse(json);
85
- } catch {
86
- return false;
87
- }
88
- return schema.safeParse(parsed).success;
89
- }
90
-
91
- /**
92
- * Stable structural equality for two MulmoScripts via JSON
93
- * canonicalisation. We compare the full re-serialised string
94
- * rather than walking keys because (a) MulmoScript is
95
- * deeply-nested and Object.keys-recursion would be ~50 lines, and
96
- * (b) `JSON.stringify` already preserves insertion order, which
97
- * `mulmoScriptSchema.safeParse` keeps stable across runs of the
98
- * same input. False positives (= "differ" when they don't) only
99
- * cost an extra `emit("updateResult", ...)` which is a no-op when
100
- * data hasn't actually changed.
101
- */
102
- export function isSameScript(left: unknown, right: unknown): boolean {
103
- return JSON.stringify(left) === JSON.stringify(right);
104
- }
105
-
106
- /** Convert an unknown thrown value into a human-readable string. */
107
- export function extractErrorMessage(err: unknown): string {
108
- return errorMessage(err);
109
- }
110
-
111
- /**
112
- * Callback set for `applyMovieEvent` / `streamMovieEvents`. Each
113
- * handler is scoped to one event shape; the dispatcher routes to
114
- * the right one based on the discriminated union's `type` field.
115
- * Keeping this as named handlers (rather than one big switch in
116
- * the caller) lets `generateMovie` stay under the
117
- * sonarjs/cognitive-complexity threshold.
118
- */
119
- export interface MovieEventHandlers {
120
- onBeatImageDone: (beatIndex: number) => void;
121
- onBeatAudioDone: (beatIndex: number) => void;
122
- onDone: (moviePath: string) => void;
123
- }
124
-
125
- /**
126
- * Dispatch a single already-parsed SSE event from the movie
127
- * generation stream to the matching handler. `"error"` events
128
- * throw so the caller's try/catch can surface the message the
129
- * same way a network failure would. `"unknown"` events are
130
- * silently ignored — the server occasionally introduces new
131
- * event types before the client catches up, and we don't want
132
- * those to tear down an otherwise-healthy stream.
133
- */
134
- export function applyMovieEvent(event: SSEEvent, handlers: MovieEventHandlers): void {
135
- switch (event.type) {
136
- case "beat_image_done":
137
- handlers.onBeatImageDone(event.beatIndex);
138
- return;
139
- case "beat_audio_done":
140
- handlers.onBeatAudioDone(event.beatIndex);
141
- return;
142
- case "done":
143
- handlers.onDone(event.moviePath);
144
- return;
145
- case "error":
146
- throw new Error(event.message);
147
- case "unknown":
148
- }
149
- }
150
-
151
- /**
152
- * Read the SSE stream body from the movie-generation endpoint
153
- * and dispatch every parsed event into the given handlers.
154
- * Returns when the server closes the stream; throws through any
155
- * `"error"` event or unhandled read error. Kept here (rather
156
- * than inline in `generateMovie`) so the reader + decoder +
157
- * line-buffer state machine is a single named unit instead of a
158
- * pyramid of `while` / `for` / `if` inside a Vue component.
159
- */
160
- export async function streamMovieEvents(body: ReadableStream<Uint8Array>, handlers: MovieEventHandlers): Promise<void> {
161
- const reader = body.getReader();
162
- const decoder = new TextDecoder();
163
- let buffer = "";
164
- while (true) {
165
- const { done, value } = await reader.read();
166
- if (done) break;
167
- buffer += decoder.decode(value, { stream: true });
168
- const lines = buffer.split("\n");
169
- buffer = lines.pop() ?? "";
170
- for (const line of lines) {
171
- const event = parseSSEEventLine(line);
172
- if (!event) continue;
173
- applyMovieEvent(event, handlers);
174
- }
175
- }
176
- }
177
-
178
- /**
179
- * True when a beat can have a generated video clip on disk — used to
180
- * decide whether to probe the beat-movie endpoint. `moviePrompt`
181
- * beats produce a per-beat movie file; `html_tailwind` beats with
182
- * `animation` set (either `true` or an options object) produce an
183
- * `_animated.mp4` render.
184
- */
185
- export function beatMayHaveMovie(beat: { moviePrompt?: string; image?: { type?: string; animation?: unknown } }): boolean {
186
- if (beat.moviePrompt) return true;
187
- return beat.image?.type === "html_tailwind" && Boolean(beat.image.animation);
188
- }
189
-
190
- /** Pure check: is every beat in the script a `slide`-typed beat?
191
- * When true, the View mounts `@mulmocast/deck-web`'s
192
- * `MulmoScriptDeckEditor` instead of the per-beat list UI (#1575).
193
- * Empty / missing `beats[]` returns false — there's nothing to edit
194
- * as a deck, fall through to the existing UI which renders an empty
195
- * state. Mixed scripts (any non-`slide` beat) also return false; that
196
- * case is deferred to a future phase. */
197
- export function isAllSlideDeck(script: unknown): boolean {
198
- if (!isRecord(script)) return false;
199
- const { beats } = script;
200
- if (!Array.isArray(beats) || beats.length === 0) return false;
201
- return beats.every((beat) => {
202
- if (!isRecord(beat)) return false;
203
- const { image } = beat;
204
- return isRecord(image) && image.type === "slide";
205
- });
206
- }