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.
- package/client/assets/{PluginScopedRoot-CjiZ2FJI.js → PluginScopedRoot-B53YSjaC.js} +1 -1
- package/client/assets/index-D3czxq1I.css +2 -0
- package/client/assets/{index-yQkLdtMk.js → index-DUdqhWg6.js} +42 -43
- package/client/assets/{marp-Bs_qwoKZ.js → marp-CyWxL3bc.js} +12 -3
- package/client/index.html +3 -3
- package/package.json +6 -5
- package/server/agent/attachmentConverter.ts +0 -25
- package/server/api/auth/viewToken.ts +10 -8
- package/server/api/routes/agent.ts +35 -10
- package/server/api/routes/collections.ts +150 -96
- package/server/api/routes/files.ts +57 -54
- package/server/api/routes/mulmo-script.ts +257 -895
- package/server/api/routes/wiki/history.ts +17 -19
- package/server/events/collection-change.ts +0 -5
- package/server/events/file-change.ts +1 -6
- package/server/index.ts +25 -76
- package/server/plugins/diagnostics.ts +0 -6
- package/server/plugins/mulmoscript-server.ts +58 -0
- package/server/prompts/system/system.md +2 -2
- package/server/remoteHost/handlers/getCollection.ts +5 -4
- package/server/remoteHost/handlers/googleCalendar.ts +50 -3
- package/server/remoteHost/handlers/index.ts +3 -1
- package/server/utils/files/html-store.ts +2 -16
- package/server/utils/files/journal-io.ts +0 -12
- package/server/utils/files/plugins-io.ts +2 -11
- package/server/utils/files/safe.ts +39 -0
- package/server/utils/httpError.ts +6 -0
- package/server/utils/time.ts +0 -3
- package/server/workspace/collections/index.ts +1 -0
- package/server/workspace/collections/remoteView.ts +13 -4
- package/server/workspace/journal/state.ts +1 -9
- package/server/workspace/memory/topic-index-hook.ts +0 -9
- package/server/workspace/memory/topic-io.ts +0 -4
- package/server/workspace/skills/external/catalog.ts +24 -13
- package/server/workspace/skills/writer.ts +20 -16
- package/server/workspace/wiki-pages/snapshot.ts +13 -12
- package/src/composables/useFileDropZone.ts +3 -15
- package/src/config/apiRoutes.ts +6 -0
- package/src/config/roles.ts +0 -4
- package/src/lang/de.ts +0 -30
- package/src/lang/en.ts +0 -30
- package/src/lang/es.ts +0 -30
- package/src/lang/fr.ts +0 -30
- package/src/lang/ja.ts +0 -30
- package/src/lang/ko.ts +0 -30
- package/src/lang/pt-BR.ts +0 -30
- package/src/lang/zh.ts +0 -30
- package/src/plugins/accounting/index.ts +0 -2
- package/src/plugins/api.ts +0 -6
- package/src/plugins/canvas/index.ts +0 -2
- package/src/plugins/chart/index.ts +0 -2
- package/src/plugins/editImages/index.ts +0 -2
- package/src/plugins/generateImage/index.ts +0 -2
- package/src/plugins/manageSkills/index.ts +0 -2
- package/src/plugins/markdown/index.ts +0 -2
- package/src/plugins/photoLocations/index.ts +0 -2
- package/src/plugins/presentCollection/index.ts +0 -2
- package/src/plugins/presentForm/index.ts +0 -2
- package/src/plugins/presentHtml/index.ts +0 -2
- package/src/plugins/presentMulmoScript/definition.ts +11 -116
- package/src/plugins/presentMulmoScript/index.ts +62 -12
- package/src/plugins/presentSVG/index.ts +0 -2
- package/src/plugins/skill/index.ts +0 -3
- package/src/plugins/spreadsheet/engine/parser.ts +0 -103
- package/src/plugins/spreadsheet/index.ts +0 -2
- package/src/plugins/textResponse/index.ts +0 -3
- package/src/plugins/wiki/index.ts +0 -2
- package/src/types/notification.ts +0 -10
- package/src/utils/api.ts +0 -4
- package/client/assets/index-D5nJbMO1.css +0 -2
- package/server/api/routes/mulmoScriptValidate.ts +0 -101
- package/server/utils/mulmoErrorCapture.ts +0 -93
- package/src/plugins/presentMulmoScript/Preview.vue +0 -23
- package/src/plugins/presentMulmoScript/View.vue +0 -1949
- 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
|
-
}
|