@mengine/medeo-tool 1.0.1-alpha.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.
@@ -0,0 +1,439 @@
1
+ import { SchemaValidator, SemanticEditor, createEditSandbox, effectiveVideoClipDurationMs, fromVideoDocument, solveVideoDocument, speedOf } from "@mengine/medeo-client";
2
+ //#region src/document/compact-projection.ts
3
+ const DEFAULT_TEXT_PREVIEW_LENGTH = 24;
4
+ /** Kind tag shown in the first column (`video_clip` → `clip`). */
5
+ function kindTag(kind) {
6
+ return kind === "video_clip" ? "clip" : kind;
7
+ }
8
+ /** Lane label: `video_clip` tracks display as `main`, otherwise `parts_kind`. */
9
+ function laneLabel(partsKind) {
10
+ return partsKind === "video_clip" ? "main" : partsKind;
11
+ }
12
+ /** Speed token: absent → `1`; linear → numeric multiplier; anything else → `custom`. */
13
+ function speedToken(speedShift) {
14
+ if (speedShift == null) return "1";
15
+ if (speedShift.category === "linear") return String(speedOf(speedShift));
16
+ return "custom";
17
+ }
18
+ function effectiveDurationMs(part, timelineDurationMs) {
19
+ if (part.video_clip != null) return effectiveVideoClipDurationMs(part.video_clip);
20
+ if (part.speech != null) return part.speech.media_duration_ms ?? 0;
21
+ if (part.caption != null) return part.caption.initial_duration_ms ?? 0;
22
+ if (part.bgm != null) return timelineDurationMs;
23
+ return 0;
24
+ }
25
+ function truncateText(text, budget) {
26
+ if (text.length <= budget) return text;
27
+ return `${text.slice(0, budget)}…`;
28
+ }
29
+ function anchorToken(timePosition) {
30
+ if (timePosition.mode === "anchored") return `anchor=${timePosition.anchorPartId}+${timePosition.offsetMs}`;
31
+ if (timePosition.mode === "absolute") return "anchor=abs";
32
+ return "anchor=abs";
33
+ }
34
+ function clipAttrs(clip) {
35
+ const playIn = clip.play_in ?? 0;
36
+ const playOut = clip.play_out ?? 0;
37
+ return `media=${clip.origin_media_id ?? ""} trim=${playIn}-${playOut} speed=${speedToken(clip.speed_shift)} vol=${clip.volume ?? 0}`;
38
+ }
39
+ function partAttrs(part, item, textPreviewLength) {
40
+ if (part.video_clip != null) return clipAttrs(part.video_clip);
41
+ if (part.speech != null) return `${anchorToken(item.time_position)} dur=${part.speech.media_duration_ms ?? 0}`;
42
+ if (part.caption != null) {
43
+ const preview = truncateText(part.caption.text ?? "", textPreviewLength);
44
+ return `${anchorToken(item.time_position)} text="${preview}"`;
45
+ }
46
+ if (part.bgm != null) return `vol=${part.bgm.volume ?? 0}`;
47
+ return "";
48
+ }
49
+ /**
50
+ * Render a `VideoDocument` as compact text: one header line plus one row per
51
+ * timeline part (optionally filtered by `onlyPartIds`). Deterministic and
52
+ * side-effect free — same document always yields the same string.
53
+ */
54
+ function renderCompactProjection(document, options) {
55
+ const onlyPartIds = options?.onlyPartIds;
56
+ const textPreviewLength = options?.textPreviewLength ?? DEFAULT_TEXT_PREVIEW_LENGTH;
57
+ const solved = solveVideoDocument(document);
58
+ const library = document.part_library ?? {};
59
+ const tracks = document.tracks ?? [];
60
+ let totalParts = 0;
61
+ const rows = [];
62
+ for (const track of tracks) {
63
+ const partsKind = track.parts_kind;
64
+ if (partsKind == null) continue;
65
+ const lane = laneLabel(partsKind);
66
+ const tag = kindTag(partsKind);
67
+ for (const item of track.items ?? []) {
68
+ totalParts += 1;
69
+ const partId = item.part_id;
70
+ if (onlyPartIds != null && !onlyPartIds.has(partId)) continue;
71
+ const part = library[partId];
72
+ if (part == null) continue;
73
+ const abs = solved.absByPartId.get(partId) ?? 0;
74
+ const dur = effectiveDurationMs(part, solved.durationMs);
75
+ const attrs = partAttrs(part, item, textPreviewLength);
76
+ rows.push(`${tag} ${partId} ${lane} [${abs},${abs + dur}) ${attrs}`);
77
+ }
78
+ }
79
+ return [`# draft=${document.meta.draft_id ?? ""} v=${document.meta.version ?? 0} duration=${solved.durationMs} parts=${totalParts} shown=${rows.length}`, ...rows].join("\n");
80
+ }
81
+ //#endregion
82
+ //#region src/sandbox/preview.ts
83
+ /**
84
+ * Collect part ids referenced by a journal for compact preview filtering.
85
+ * Walks known id-shaped payload keys (宁多勿少) and unions `generated_ids`.
86
+ */
87
+ const PART_ID_KEYS = new Set([
88
+ "clip_id",
89
+ "clip_ids",
90
+ "before_clip_id",
91
+ "after_clip_id",
92
+ "speech_id",
93
+ "speech_ids",
94
+ "speech_part_id",
95
+ "caption_id",
96
+ "caption_ids",
97
+ "bgm_id",
98
+ "anchor_part_id",
99
+ "part_id",
100
+ "body_part_id"
101
+ ]);
102
+ /** Extract every part id a journal entry touches (payload refs + minted ids). */
103
+ function collectAffectedPartIds(journal) {
104
+ const ids = /* @__PURE__ */ new Set();
105
+ for (const entry of journal) {
106
+ for (const generated of entry.generated_ids ?? []) if (generated.length > 0) ids.add(generated);
107
+ collectFromValue(entry.payload, ids);
108
+ }
109
+ return ids;
110
+ }
111
+ function collectFromValue(value, ids) {
112
+ if (value == null) return;
113
+ if (Array.isArray(value)) {
114
+ for (const item of value) collectFromValue(item, ids);
115
+ return;
116
+ }
117
+ if (typeof value !== "object") return;
118
+ for (const [key, child] of Object.entries(value)) {
119
+ if (PART_ID_KEYS.has(key)) {
120
+ if (typeof child === "string" && child.length > 0) ids.add(child);
121
+ else if (Array.isArray(child)) {
122
+ for (const item of child) if (typeof item === "string" && item.length > 0) ids.add(item);
123
+ }
124
+ }
125
+ collectFromValue(child, ids);
126
+ }
127
+ }
128
+ /**
129
+ * Render a ChangePlan preview: header + rows for journal-affected parts only.
130
+ * Empty journal → empty `onlyPartIds` (header alone), matching the T2 contract.
131
+ */
132
+ function renderPreview(document, journal) {
133
+ return renderCompactProjection(document, { onlyPartIds: journal.length === 0 ? /* @__PURE__ */ new Set() : collectAffectedPartIds(journal) });
134
+ }
135
+ //#endregion
136
+ //#region src/sandbox/script-session.ts
137
+ const LOG_LINE_MAX = 2e3;
138
+ const LOG_LINE_CAP = 1e3;
139
+ const LOG_BYTE_CAP = 64 * 1024;
140
+ const TRUNCATE_MARK = "[truncated]";
141
+ const LOG_TRUNCATED = "[log truncated]";
142
+ /** Session core for one forked document; globals stay identity-stable across rollback. */
143
+ var EditSandboxSession = class {
144
+ original;
145
+ idFactory;
146
+ onEntry;
147
+ onLog;
148
+ onTruncate;
149
+ current;
150
+ /** Adapter journal length already accounted for — new slices are real commits. */
151
+ adapterJournalSeen = 0;
152
+ entries = [];
153
+ logs = [];
154
+ logBytes = 0;
155
+ logCapped = false;
156
+ edit;
157
+ timeline;
158
+ console;
159
+ checkpoint;
160
+ rollbackTo;
161
+ constructor(document, options) {
162
+ this.original = structuredClone(document);
163
+ this.idFactory = options?.idFactory;
164
+ this.onEntry = options?.onEntry;
165
+ this.onLog = options?.onLog;
166
+ this.onTruncate = options?.onTruncate;
167
+ this.current = this.boot(structuredClone(this.original));
168
+ this.adapterJournalSeen = this.current.adapter.journal.length;
169
+ this.edit = this.buildEditFacade();
170
+ this.timeline = this.buildTimelineFacade();
171
+ this.console = this.buildConsoleShim();
172
+ this.checkpoint = () => ({ index: this.entries.length });
173
+ this.rollbackTo = (cp) => this.doRollbackTo(cp);
174
+ }
175
+ /** Assemble a ChangePlan from the self-maintained journal + current preview. */
176
+ buildPlan(baseVersion) {
177
+ return {
178
+ doc_id: this.original.meta.draft_id ?? "",
179
+ base_version: baseVersion,
180
+ ops: this.entries.slice(),
181
+ preview: renderPreview(this.current.adapter.snapshot(), this.entries),
182
+ logs: this.logs.slice()
183
+ };
184
+ }
185
+ getEntries() {
186
+ return this.entries;
187
+ }
188
+ getLogs() {
189
+ return this.logs;
190
+ }
191
+ boot(document) {
192
+ const sandbox = createEditSandbox(document, this.idFactory != null ? { idFactory: this.idFactory } : void 0);
193
+ return {
194
+ adapter: sandbox.adapter,
195
+ editor: sandbox.editor
196
+ };
197
+ }
198
+ doRollbackTo(cp) {
199
+ if (cp.index > this.entries.length) throw new Error(`rollbackTo: checkpoint index ${cp.index} is past journal length ${this.entries.length}`);
200
+ const prefix = this.entries.slice(0, cp.index);
201
+ const next = this.boot(structuredClone(this.original));
202
+ replayJournalSync(next.adapter, prefix);
203
+ this.entries.length = 0;
204
+ this.entries.push(...prefix);
205
+ this.current = next;
206
+ this.adapterJournalSeen = next.adapter.journal.length;
207
+ this.onTruncate?.(prefix.length);
208
+ }
209
+ captureNewEntries() {
210
+ const journal = this.current.adapter.journal;
211
+ if (journal.length <= this.adapterJournalSeen) return;
212
+ const fresh = journal.slice(this.adapterJournalSeen);
213
+ this.adapterJournalSeen = journal.length;
214
+ for (const entry of fresh) {
215
+ this.entries.push(entry);
216
+ this.onEntry?.(entry);
217
+ }
218
+ }
219
+ appendLog(line) {
220
+ if (this.logCapped) return;
221
+ if (this.logs.length >= LOG_LINE_CAP || this.logBytes >= LOG_BYTE_CAP) {
222
+ this.logs.push(LOG_TRUNCATED);
223
+ this.logCapped = true;
224
+ this.onLog?.(LOG_TRUNCATED);
225
+ return;
226
+ }
227
+ let out = line;
228
+ if (out.length > LOG_LINE_MAX) out = `${out.slice(0, LOG_LINE_MAX - 11)}${TRUNCATE_MARK}`;
229
+ this.logs.push(out);
230
+ this.logBytes += out.length;
231
+ this.onLog?.(out);
232
+ }
233
+ buildConsoleShim() {
234
+ const write = (...args) => {
235
+ this.appendLog(args.map(formatLogArg).join(" "));
236
+ };
237
+ return {
238
+ log: write,
239
+ info: write,
240
+ warn: write,
241
+ error: write
242
+ };
243
+ }
244
+ buildEditFacade() {
245
+ const wrap = (method) => async (input) => {
246
+ await method(this.current.editor, input);
247
+ this.captureNewEntries();
248
+ };
249
+ return {
250
+ addSpeeches: wrap((e, i) => e.addSpeeches(i)),
251
+ addVideoClips: async (input) => {
252
+ const needsAppend = input.before_clip_id == null && input.after_clip_id == null && input.clips.some((clip) => clip.start_ms == null);
253
+ const appendAt = this.timeline.snapshot().timeline?.duration_ms ?? 0;
254
+ const normalized = needsAppend ? {
255
+ ...input,
256
+ clips: input.clips.map((clip) => clip.start_ms == null ? {
257
+ ...clip,
258
+ start_ms: appendAt
259
+ } : clip)
260
+ } : input;
261
+ await this.current.editor.addVideoClips(normalized);
262
+ this.captureNewEntries();
263
+ },
264
+ adjustBgmVolume: wrap((e, i) => e.adjustBgmVolume(i)),
265
+ adjustSpeechVolume: wrap((e, i) => e.adjustSpeechVolume(i)),
266
+ adjustVideoClipDuration: wrap((e, i) => e.adjustVideoClipDuration(i)),
267
+ adjustVideoClipVolume: wrap((e, i) => e.adjustVideoClipVolume(i)),
268
+ changeSpeechScript: wrap((e, i) => e.changeSpeechScript(i)),
269
+ changeSpeechVoice: wrap((e, i) => e.changeSpeechVoice(i)),
270
+ deleteBgm: wrap((e, i) => e.deleteBgm(i)),
271
+ deleteSpeeches: wrap((e, i) => e.deleteSpeeches(i)),
272
+ deleteVideoClips: wrap((e, i) => e.deleteVideoClips(i)),
273
+ moveSpeeches: wrap((e, i) => e.moveSpeeches(i)),
274
+ moveVideoClips: wrap((e, i) => e.moveVideoClips(i)),
275
+ replaceVideoClipContent: wrap((e, i) => e.replaceVideoClipContent(i)),
276
+ setBgm: wrap((e, i) => e.setBgm(i)),
277
+ setCaptionStyle: wrap((e, i) => e.setCaptionStyle(i)),
278
+ setCaptionVisibility: wrap((e, i) => e.setCaptionVisibility(i)),
279
+ setVideoClipSpeedShift: wrap((e, i) => e.setVideoClipSpeedShift(i))
280
+ };
281
+ }
282
+ buildTimelineFacade() {
283
+ return {
284
+ snapshot: () => fromVideoDocument(this.current.adapter.snapshot()),
285
+ clipsInRange: (startMs, endMs) => this.clipsInRange(startMs, endMs),
286
+ part: (id) => this.part(id)
287
+ };
288
+ }
289
+ clipsInRange(startMs, endMs) {
290
+ const document = this.current.adapter.snapshot();
291
+ const solved = solveVideoDocument(document);
292
+ const library = document.part_library ?? {};
293
+ const main = document.tracks?.find((track) => track.parts_kind === "video_clip");
294
+ const out = [];
295
+ for (const item of main?.items ?? []) {
296
+ const id = item.part_id;
297
+ if (id == null) continue;
298
+ const clip = library[id]?.video_clip;
299
+ if (clip == null) continue;
300
+ const start = solved.absByPartId.get(id) ?? 0;
301
+ const duration = effectiveVideoClipDurationMs(clip);
302
+ const end = start + duration;
303
+ const mid = start + duration / 2;
304
+ if (!(mid >= startMs && mid < endMs)) continue;
305
+ out.push({
306
+ id,
307
+ start_ms: start,
308
+ end_ms: end,
309
+ duration_ms: duration,
310
+ speed_shift: clip.speed_shift,
311
+ volume: clip.volume,
312
+ media_id: clip.origin_media_id
313
+ });
314
+ }
315
+ return out;
316
+ }
317
+ part(id) {
318
+ const document = this.current.adapter.snapshot();
319
+ const part = (document.part_library ?? {})[id];
320
+ if (part == null) return null;
321
+ let lane = "main";
322
+ let kind = "video_clip";
323
+ for (const track of document.tracks ?? []) {
324
+ if (!(track.items ?? []).some((item) => item.part_id === id)) continue;
325
+ const partsKind = track.parts_kind ?? "video_clip";
326
+ kind = partsKind;
327
+ lane = partsKind === "video_clip" ? "main" : partsKind;
328
+ break;
329
+ }
330
+ const solved = solveVideoDocument(document);
331
+ const start = solved.absByPartId.get(id) ?? 0;
332
+ let duration = 0;
333
+ if (part.video_clip != null) duration = effectiveVideoClipDurationMs(part.video_clip);
334
+ else if (part.speech != null) duration = part.speech.media_duration_ms ?? 0;
335
+ else if (part.caption != null) duration = part.caption.initial_duration_ms ?? 0;
336
+ else if (part.bgm != null) duration = solved.durationMs;
337
+ return {
338
+ id,
339
+ kind,
340
+ lane,
341
+ start_ms: start,
342
+ end_ms: start + duration,
343
+ duration_ms: duration,
344
+ part
345
+ };
346
+ }
347
+ };
348
+ function formatLogArg(value) {
349
+ if (typeof value === "string") return value;
350
+ if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0) return String(value);
351
+ try {
352
+ return JSON.stringify(value);
353
+ } catch {
354
+ return "[unstringifiable]";
355
+ }
356
+ }
357
+ /**
358
+ * Synchronous journal replay for rollback. Editor methods are `async` only for
359
+ * interface uniformity — their bodies complete before the Promise is returned,
360
+ * so voiding the call applies mutations in-order without yielding.
361
+ */
362
+ function replayJournalSync(adapter, journal) {
363
+ const queue = [];
364
+ const idFactory = (_prefix) => {
365
+ const id = queue.shift();
366
+ if (id == null) throw new Error("unrecorded id");
367
+ return id;
368
+ };
369
+ const editor = new SemanticEditor(adapter, new SchemaValidator(), idFactory);
370
+ for (const entry of journal) {
371
+ queue.push(...entry.generated_ids ?? []);
372
+ const payload = entry.payload;
373
+ switch (entry.kind) {
374
+ case "MoveVideoClips":
375
+ editor.moveVideoClips(payload);
376
+ break;
377
+ case "DeleteVideoClips":
378
+ editor.deleteVideoClips(payload);
379
+ break;
380
+ case "AddVideoClips":
381
+ editor.addVideoClips(payload);
382
+ break;
383
+ case "AdjustVideoClipVolume":
384
+ editor.adjustVideoClipVolume(payload);
385
+ break;
386
+ case "SetVideoClipSpeedShift":
387
+ editor.setVideoClipSpeedShift(payload);
388
+ break;
389
+ case "ReplaceVideoClipContent":
390
+ editor.replaceVideoClipContent(payload);
391
+ break;
392
+ case "AdjustVideoClipDuration":
393
+ editor.adjustVideoClipDuration(payload);
394
+ break;
395
+ case "AddSpeeches":
396
+ editor.addSpeeches(payload);
397
+ break;
398
+ case "DeleteSpeeches":
399
+ editor.deleteSpeeches(payload);
400
+ break;
401
+ case "MoveSpeeches":
402
+ editor.moveSpeeches(payload);
403
+ break;
404
+ case "ChangeSpeechScript":
405
+ editor.changeSpeechScript(payload);
406
+ break;
407
+ case "ChangeSpeechVoice":
408
+ editor.changeSpeechVoice(payload);
409
+ break;
410
+ case "AdjustSpeechVolume":
411
+ editor.adjustSpeechVolume(payload);
412
+ break;
413
+ case "SetCaptionVisibility":
414
+ editor.setCaptionVisibility(payload);
415
+ break;
416
+ case "SetCaptionStyle":
417
+ editor.setCaptionStyle(payload);
418
+ break;
419
+ case "SetBgm":
420
+ editor.setBgm(payload);
421
+ break;
422
+ case "DeleteBgm":
423
+ editor.deleteBgm(payload);
424
+ break;
425
+ case "AdjustBgmVolume":
426
+ editor.adjustBgmVolume(payload);
427
+ break;
428
+ default: {
429
+ const _exhaustive = entry.kind;
430
+ throw new Error(`replayJournalSync: unsupported kind ${String(_exhaustive)}`);
431
+ }
432
+ }
433
+ if (queue.length > 0) throw new Error("unconsumed ids");
434
+ }
435
+ }
436
+ //#endregion
437
+ export { renderCompactProjection as i, collectAffectedPartIds as n, renderPreview as r, EditSandboxSession as t };
438
+
439
+ //# sourceMappingURL=script-session-B8fc9Ccb.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"script-session-B8fc9Ccb.mjs","names":[],"sources":["../src/document/compact-projection.ts","../src/sandbox/preview.ts","../src/sandbox/script-session.ts"],"sourcesContent":["import {\n effectiveVideoClipDurationMs,\n solveVideoDocument,\n speedOf,\n type PartKind,\n type PartUnion,\n type SpeedShift,\n type TrackItem,\n type TrackItemTimePosition,\n type VideoClipPart,\n type VideoDocument,\n} from '@mengine/medeo-client';\n\n/**\n * Compact text projection of a `VideoDocument` for sandbox ChangePlan\n * `preview` (and pipeline dry-run preview). Pure, runtime-neutral: solves\n * via `solveVideoDocument`, emits one header line plus one row per timeline\n * item (library orphans are never rendered). Row intervals and per-kind\n * effective durations align with `toReadViewPartLibrary` /\n * `fromVideoDocument`.\n */\n\nexport interface CompactProjectionOptions {\n /** Only render these parts (header still reports the full timeline total). Default = all. */\n onlyPartIds?: ReadonlySet<string>;\n /** Caption text preview truncation length. Default 24. */\n textPreviewLength?: number;\n}\n\nconst DEFAULT_TEXT_PREVIEW_LENGTH = 24;\n\n/** Kind tag shown in the first column (`video_clip` → `clip`). */\nfunction kindTag(kind: PartKind): string {\n return kind === 'video_clip' ? 'clip' : kind;\n}\n\n/** Lane label: `video_clip` tracks display as `main`, otherwise `parts_kind`. */\nfunction laneLabel(partsKind: PartKind): string {\n return partsKind === 'video_clip' ? 'main' : partsKind;\n}\n\n/** Speed token: absent → `1`; linear → numeric multiplier; anything else → `custom`. */\nfunction speedToken(speedShift: SpeedShift | undefined): string {\n if (speedShift == null) return '1';\n if (speedShift.category === 'linear') return String(speedOf(speedShift));\n return 'custom';\n}\n\nfunction effectiveDurationMs(part: PartUnion, timelineDurationMs: number): number {\n if (part.video_clip != null) return effectiveVideoClipDurationMs(part.video_clip);\n if (part.speech != null) return part.speech.media_duration_ms ?? 0;\n if (part.caption != null) return part.caption.initial_duration_ms ?? 0;\n if (part.bgm != null) return timelineDurationMs;\n return 0;\n}\n\nfunction truncateText(text: string, budget: number): string {\n if (text.length <= budget) return text;\n return `${text.slice(0, budget)}…`;\n}\n\nfunction anchorToken(timePosition: TrackItemTimePosition): string {\n if (timePosition.mode === 'anchored') {\n return `anchor=${timePosition.anchorPartId}+${timePosition.offsetMs}`;\n }\n if (timePosition.mode === 'absolute') return 'anchor=abs';\n return 'anchor=abs';\n}\n\nfunction clipAttrs(clip: VideoClipPart): string {\n const playIn = clip.play_in ?? 0;\n const playOut = clip.play_out ?? 0;\n return `media=${clip.origin_media_id ?? ''} trim=${playIn}-${playOut} speed=${speedToken(clip.speed_shift)} vol=${clip.volume ?? 0}`;\n}\n\nfunction partAttrs(part: PartUnion, item: TrackItem, textPreviewLength: number): string {\n if (part.video_clip != null) return clipAttrs(part.video_clip);\n if (part.speech != null) {\n return `${anchorToken(item.time_position)} dur=${part.speech.media_duration_ms ?? 0}`;\n }\n if (part.caption != null) {\n const preview = truncateText(part.caption.text ?? '', textPreviewLength);\n return `${anchorToken(item.time_position)} text=\"${preview}\"`;\n }\n if (part.bgm != null) return `vol=${part.bgm.volume ?? 0}`;\n return '';\n}\n\n/**\n * Render a `VideoDocument` as compact text: one header line plus one row per\n * timeline part (optionally filtered by `onlyPartIds`). Deterministic and\n * side-effect free — same document always yields the same string.\n */\nexport function renderCompactProjection(document: VideoDocument, options?: CompactProjectionOptions): string {\n const onlyPartIds = options?.onlyPartIds;\n const textPreviewLength = options?.textPreviewLength ?? DEFAULT_TEXT_PREVIEW_LENGTH;\n\n const solved = solveVideoDocument(document);\n const library = document.part_library ?? {};\n const tracks = document.tracks ?? [];\n\n let totalParts = 0;\n const rows: string[] = [];\n\n for (const track of tracks) {\n const partsKind = track.parts_kind;\n if (partsKind == null) continue;\n const lane = laneLabel(partsKind);\n const tag = kindTag(partsKind);\n\n for (const item of track.items ?? []) {\n totalParts += 1;\n const partId = item.part_id;\n if (onlyPartIds != null && !onlyPartIds.has(partId)) continue;\n\n const part = library[partId];\n if (part == null) continue;\n\n const abs = solved.absByPartId.get(partId) ?? 0;\n const dur = effectiveDurationMs(part, solved.durationMs);\n const attrs = partAttrs(part, item, textPreviewLength);\n rows.push(`${tag} ${partId} ${lane} [${abs},${abs + dur}) ${attrs}`);\n }\n }\n\n const header = `# draft=${document.meta.draft_id ?? ''} v=${document.meta.version ?? 0} duration=${solved.durationMs} parts=${totalParts} shown=${rows.length}`;\n return [header, ...rows].join('\\n');\n}\n","import type { JournalEntry, VideoDocument } from '@mengine/medeo-client';\n\nimport { renderCompactProjection } from '../document/compact-projection.ts';\n\n/**\n * Collect part ids referenced by a journal for compact preview filtering.\n * Walks known id-shaped payload keys (宁多勿少) and unions `generated_ids`.\n */\nconst PART_ID_KEYS = new Set([\n 'clip_id',\n 'clip_ids',\n 'before_clip_id',\n 'after_clip_id',\n 'speech_id',\n 'speech_ids',\n 'speech_part_id',\n 'caption_id',\n 'caption_ids',\n 'bgm_id',\n 'anchor_part_id',\n 'part_id',\n 'body_part_id',\n]);\n\n/** Extract every part id a journal entry touches (payload refs + minted ids). */\nexport function collectAffectedPartIds(journal: readonly JournalEntry[]): Set<string> {\n const ids = new Set<string>();\n for (const entry of journal) {\n for (const generated of entry.generated_ids ?? []) {\n if (generated.length > 0) ids.add(generated);\n }\n collectFromValue(entry.payload, ids);\n }\n return ids;\n}\n\nfunction collectFromValue(value: unknown, ids: Set<string>): void {\n if (value == null) return;\n if (Array.isArray(value)) {\n for (const item of value) collectFromValue(item, ids);\n return;\n }\n if (typeof value !== 'object') return;\n for (const [key, child] of Object.entries(value as Record<string, unknown>)) {\n if (PART_ID_KEYS.has(key)) {\n if (typeof child === 'string' && child.length > 0) ids.add(child);\n else if (Array.isArray(child)) {\n for (const item of child) {\n if (typeof item === 'string' && item.length > 0) ids.add(item);\n }\n }\n }\n collectFromValue(child, ids);\n }\n}\n\n/**\n * Render a ChangePlan preview: header + rows for journal-affected parts only.\n * Empty journal → empty `onlyPartIds` (header alone), matching the T2 contract.\n */\nexport function renderPreview(document: VideoDocument, journal: readonly JournalEntry[]): string {\n const onlyPartIds = journal.length === 0 ? new Set<string>() : collectAffectedPartIds(journal);\n return renderCompactProjection(document, { onlyPartIds });\n}\n","import {\n createEditSandbox,\n effectiveVideoClipDurationMs,\n fromVideoDocument,\n SchemaValidator,\n SemanticEditor,\n solveVideoDocument,\n type JournalEntry,\n type PartIdFactory,\n type PlainMemoryAdapter,\n type VideoDocument,\n} from '@mengine/medeo-client';\nimport type {\n AddSpeechesInput,\n AddVideoClipsInput,\n AdjustBgmVolumeInput,\n AdjustSpeechVolumeInput,\n AdjustVideoClipDurationInput,\n AdjustVideoClipVolumeInput,\n ChangeSpeechScriptInput,\n ChangeSpeechVoiceInput,\n DeleteBgmInput,\n DeleteSpeechesInput,\n DeleteVideoClipsInput,\n MoveSpeechesInput,\n MoveVideoClipsInput,\n ReplaceVideoClipContentInput,\n SetBgmInput,\n SetCaptionStyleInput,\n SetCaptionVisibilityInput,\n SetVideoClipSpeedShiftInput,\n} from '@mengine/medeo-client/schemas';\n\nimport { renderPreview } from './preview.ts';\n\n/**\n * Runtime-neutral edit-sandbox session: `edit.*` / `timeline.*` / checkpoint\n * facade over a forked `VideoDocument`, self-maintained journal, and console\n * log buffer. No `node:*` imports — host/worker layers inject this into vm.\n *\n * Known gap (not solved here): `Math.random` / `Date.now` remain reachable in\n * the vm; purity is by convention.\n */\n\nexport interface SandboxCheckpoint {\n readonly index: number;\n}\n\nexport interface ChangePlan {\n doc_id: string;\n base_version: string;\n ops: readonly JournalEntry[];\n preview: string;\n logs: string[];\n}\n\nexport interface EditSandboxSessionOptions {\n idFactory?: PartIdFactory;\n onEntry?: (entry: JournalEntry) => void;\n onLog?: (line: string) => void;\n /** Notify host that the streamed journal was truncated to `index` (rollback). */\n onTruncate?: (index: number) => void;\n}\n\nexport interface TimelineClipDescriptor {\n id: string;\n start_ms: number;\n end_ms: number;\n duration_ms: number;\n speed_shift: unknown;\n volume: number | undefined;\n media_id: string | undefined;\n}\n\nexport interface TimelinePartDescriptor {\n id: string;\n kind: string;\n lane: string;\n start_ms: number;\n end_ms: number;\n duration_ms: number;\n part: unknown;\n}\n\nconst LOG_LINE_MAX = 2000;\nconst LOG_LINE_CAP = 1000;\nconst LOG_BYTE_CAP = 64 * 1024;\nconst TRUNCATE_MARK = '[truncated]';\nconst LOG_TRUNCATED = '[log truncated]';\n\ninterface SandboxRef {\n adapter: PlainMemoryAdapter;\n editor: SemanticEditor;\n}\n\n/** Session core for one forked document; globals stay identity-stable across rollback. */\nexport class EditSandboxSession {\n private readonly original: VideoDocument;\n private readonly idFactory: PartIdFactory | undefined;\n private readonly onEntry: ((entry: JournalEntry) => void) | undefined;\n private readonly onLog: ((line: string) => void) | undefined;\n private readonly onTruncate: ((index: number) => void) | undefined;\n\n private current: SandboxRef;\n /** Adapter journal length already accounted for — new slices are real commits. */\n private adapterJournalSeen = 0;\n private readonly entries: JournalEntry[] = [];\n private readonly logs: string[] = [];\n private logBytes = 0;\n private logCapped = false;\n\n readonly edit: EditFacade;\n readonly timeline: TimelineFacade;\n readonly console: ConsoleShim;\n readonly checkpoint: () => SandboxCheckpoint;\n readonly rollbackTo: (cp: SandboxCheckpoint) => void;\n\n constructor(document: VideoDocument, options?: EditSandboxSessionOptions) {\n this.original = structuredClone(document);\n this.idFactory = options?.idFactory;\n this.onEntry = options?.onEntry;\n this.onLog = options?.onLog;\n this.onTruncate = options?.onTruncate;\n\n this.current = this.boot(structuredClone(this.original));\n this.adapterJournalSeen = this.current.adapter.journal.length;\n\n this.edit = this.buildEditFacade();\n this.timeline = this.buildTimelineFacade();\n this.console = this.buildConsoleShim();\n this.checkpoint = () => ({ index: this.entries.length });\n this.rollbackTo = (cp) => this.doRollbackTo(cp);\n }\n\n /** Assemble a ChangePlan from the self-maintained journal + current preview. */\n buildPlan(baseVersion: string): ChangePlan {\n return {\n doc_id: this.original.meta.draft_id ?? '',\n base_version: baseVersion,\n ops: this.entries.slice(),\n preview: renderPreview(this.current.adapter.snapshot(), this.entries),\n logs: this.logs.slice(),\n };\n }\n\n getEntries(): readonly JournalEntry[] {\n return this.entries;\n }\n\n getLogs(): readonly string[] {\n return this.logs;\n }\n\n private boot(document: VideoDocument): SandboxRef {\n const sandbox = createEditSandbox(document, this.idFactory != null ? { idFactory: this.idFactory } : undefined);\n return { adapter: sandbox.adapter, editor: sandbox.editor };\n }\n\n private doRollbackTo(cp: SandboxCheckpoint): void {\n if (cp.index > this.entries.length) {\n throw new Error(`rollbackTo: checkpoint index ${cp.index} is past journal length ${this.entries.length}`);\n }\n const prefix = this.entries.slice(0, cp.index);\n const next = this.boot(structuredClone(this.original));\n // Sync replay: editor methods finish mutations before returning a Promise.\n // Must not use async `replayJournal` — agent scripts call rollbackTo without await.\n replayJournalSync(next.adapter, prefix);\n this.entries.length = 0;\n this.entries.push(...prefix);\n this.current = next;\n this.adapterJournalSeen = next.adapter.journal.length;\n // Host streams entries eagerly; tell it to drop the rolled-back suffix.\n this.onTruncate?.(prefix.length);\n }\n\n private captureNewEntries(): void {\n const journal = this.current.adapter.journal;\n if (journal.length <= this.adapterJournalSeen) return;\n const fresh = journal.slice(this.adapterJournalSeen);\n this.adapterJournalSeen = journal.length;\n for (const entry of fresh) {\n this.entries.push(entry);\n this.onEntry?.(entry);\n }\n }\n\n private appendLog(line: string): void {\n if (this.logCapped) return;\n if (this.logs.length >= LOG_LINE_CAP || this.logBytes >= LOG_BYTE_CAP) {\n this.logs.push(LOG_TRUNCATED);\n this.logCapped = true;\n this.onLog?.(LOG_TRUNCATED);\n return;\n }\n let out = line;\n if (out.length > LOG_LINE_MAX) {\n out = `${out.slice(0, LOG_LINE_MAX - TRUNCATE_MARK.length)}${TRUNCATE_MARK}`;\n }\n this.logs.push(out);\n this.logBytes += out.length;\n this.onLog?.(out);\n }\n\n private buildConsoleShim(): ConsoleShim {\n const write = (...args: unknown[]) => {\n this.appendLog(args.map(formatLogArg).join(' '));\n };\n return {\n log: write,\n info: write,\n warn: write,\n error: write,\n };\n }\n\n private buildEditFacade(): EditFacade {\n const wrap =\n <I>(method: (editor: SemanticEditor, input: I) => Promise<void>) =>\n async (input: I): Promise<void> => {\n await method(this.current.editor, input);\n this.captureNewEntries();\n };\n\n return {\n addSpeeches: wrap((e, i: AddSpeechesInput) => e.addSpeeches(i)),\n addVideoClips: async (input: AddVideoClipsInput): Promise<void> => {\n // Schema requires start_ms without before/after, but SemanticEditor\n // treats a missing start_ms as \"append\". Fill duration so agent scripts\n // that omit it (and the host-spec id-factory case) still validate.\n const needsAppend =\n input.before_clip_id == null &&\n input.after_clip_id == null &&\n input.clips.some((clip) => clip.start_ms == null);\n const appendAt = this.timeline.snapshot().timeline?.duration_ms ?? 0;\n const normalized: AddVideoClipsInput = needsAppend\n ? {\n ...input,\n clips: input.clips.map((clip) => (clip.start_ms == null ? { ...clip, start_ms: appendAt } : clip)),\n }\n : input;\n await this.current.editor.addVideoClips(normalized);\n this.captureNewEntries();\n },\n adjustBgmVolume: wrap((e, i: AdjustBgmVolumeInput) => e.adjustBgmVolume(i)),\n adjustSpeechVolume: wrap((e, i: AdjustSpeechVolumeInput) => e.adjustSpeechVolume(i)),\n adjustVideoClipDuration: wrap((e, i: AdjustVideoClipDurationInput) => e.adjustVideoClipDuration(i)),\n adjustVideoClipVolume: wrap((e, i: AdjustVideoClipVolumeInput) => e.adjustVideoClipVolume(i)),\n changeSpeechScript: wrap((e, i: ChangeSpeechScriptInput) => e.changeSpeechScript(i)),\n changeSpeechVoice: wrap((e, i: ChangeSpeechVoiceInput) => e.changeSpeechVoice(i)),\n deleteBgm: wrap((e, i: DeleteBgmInput) => e.deleteBgm(i)),\n deleteSpeeches: wrap((e, i: DeleteSpeechesInput) => e.deleteSpeeches(i)),\n deleteVideoClips: wrap((e, i: DeleteVideoClipsInput) => e.deleteVideoClips(i)),\n moveSpeeches: wrap((e, i: MoveSpeechesInput) => e.moveSpeeches(i)),\n moveVideoClips: wrap((e, i: MoveVideoClipsInput) => e.moveVideoClips(i)),\n replaceVideoClipContent: wrap((e, i: ReplaceVideoClipContentInput) => e.replaceVideoClipContent(i)),\n setBgm: wrap((e, i: SetBgmInput) => e.setBgm(i)),\n setCaptionStyle: wrap((e, i: SetCaptionStyleInput) => e.setCaptionStyle(i)),\n setCaptionVisibility: wrap((e, i: SetCaptionVisibilityInput) => e.setCaptionVisibility(i)),\n setVideoClipSpeedShift: wrap((e, i: SetVideoClipSpeedShiftInput) => e.setVideoClipSpeedShift(i)),\n };\n }\n\n private buildTimelineFacade(): TimelineFacade {\n return {\n snapshot: () => fromVideoDocument(this.current.adapter.snapshot()),\n clipsInRange: (startMs, endMs) => this.clipsInRange(startMs, endMs),\n part: (id) => this.part(id),\n };\n }\n\n private clipsInRange(startMs: number, endMs: number): TimelineClipDescriptor[] {\n const document = this.current.adapter.snapshot();\n const solved = solveVideoDocument(document);\n const library = document.part_library ?? {};\n const main = document.tracks?.find((track) => track.parts_kind === 'video_clip');\n const out: TimelineClipDescriptor[] = [];\n for (const item of main?.items ?? []) {\n const id = item.part_id;\n if (id == null) continue;\n const part = library[id];\n const clip = part?.video_clip;\n if (clip == null) continue;\n const start = solved.absByPartId.get(id) ?? 0;\n const duration = effectiveVideoClipDurationMs(clip);\n const end = start + duration;\n // Include clips whose midpoint falls in [startMs, endMs). Standard\n // interval overlap would also pull in a clip that only barely crosses\n // the window edge (e.g. clip_b@[4000,8000) vs query [0,5000)); the\n // midpoint rule matches the T2 host-spec pin for that fixture.\n const mid = start + duration / 2;\n if (!(mid >= startMs && mid < endMs)) continue;\n out.push({\n id,\n start_ms: start,\n end_ms: end,\n duration_ms: duration,\n speed_shift: clip.speed_shift,\n volume: clip.volume,\n media_id: clip.origin_media_id,\n });\n }\n return out;\n }\n\n private part(id: string): TimelinePartDescriptor | null {\n const document = this.current.adapter.snapshot();\n const library = document.part_library ?? {};\n const part = library[id];\n if (part == null) return null;\n\n let lane = 'main';\n let kind = 'video_clip';\n for (const track of document.tracks ?? []) {\n const hit = (track.items ?? []).some((item) => item.part_id === id);\n if (!hit) continue;\n const partsKind = track.parts_kind ?? 'video_clip';\n kind = partsKind;\n lane = partsKind === 'video_clip' ? 'main' : partsKind;\n break;\n }\n\n const solved = solveVideoDocument(document);\n const start = solved.absByPartId.get(id) ?? 0;\n let duration = 0;\n if (part.video_clip != null) duration = effectiveVideoClipDurationMs(part.video_clip);\n else if (part.speech != null) duration = part.speech.media_duration_ms ?? 0;\n else if (part.caption != null) duration = part.caption.initial_duration_ms ?? 0;\n else if (part.bgm != null) duration = solved.durationMs;\n\n return {\n id,\n kind,\n lane,\n start_ms: start,\n end_ms: start + duration,\n duration_ms: duration,\n part,\n };\n }\n}\n\nexport interface EditFacade {\n addSpeeches: (input: AddSpeechesInput) => Promise<void>;\n addVideoClips: (input: AddVideoClipsInput) => Promise<void>;\n adjustBgmVolume: (input: AdjustBgmVolumeInput) => Promise<void>;\n adjustSpeechVolume: (input: AdjustSpeechVolumeInput) => Promise<void>;\n adjustVideoClipDuration: (input: AdjustVideoClipDurationInput) => Promise<void>;\n adjustVideoClipVolume: (input: AdjustVideoClipVolumeInput) => Promise<void>;\n changeSpeechScript: (input: ChangeSpeechScriptInput) => Promise<void>;\n changeSpeechVoice: (input: ChangeSpeechVoiceInput) => Promise<void>;\n deleteBgm: (input: DeleteBgmInput) => Promise<void>;\n deleteSpeeches: (input: DeleteSpeechesInput) => Promise<void>;\n deleteVideoClips: (input: DeleteVideoClipsInput) => Promise<void>;\n moveSpeeches: (input: MoveSpeechesInput) => Promise<void>;\n moveVideoClips: (input: MoveVideoClipsInput) => Promise<void>;\n replaceVideoClipContent: (input: ReplaceVideoClipContentInput) => Promise<void>;\n setBgm: (input: SetBgmInput) => Promise<void>;\n setCaptionStyle: (input: SetCaptionStyleInput) => Promise<void>;\n setCaptionVisibility: (input: SetCaptionVisibilityInput) => Promise<void>;\n setVideoClipSpeedShift: (input: SetVideoClipSpeedShiftInput) => Promise<void>;\n}\n\nexport interface TimelineFacade {\n snapshot: () => ReturnType<typeof fromVideoDocument>;\n clipsInRange: (startMs: number, endMs: number) => TimelineClipDescriptor[];\n part: (id: string) => TimelinePartDescriptor | null;\n}\n\nexport interface ConsoleShim {\n log: (...args: unknown[]) => void;\n info: (...args: unknown[]) => void;\n warn: (...args: unknown[]) => void;\n error: (...args: unknown[]) => void;\n}\n\nfunction formatLogArg(value: unknown): string {\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean' || value === null || value === undefined) {\n return String(value);\n }\n try {\n return JSON.stringify(value);\n } catch {\n return '[unstringifiable]';\n }\n}\n\n/**\n * Synchronous journal replay for rollback. Editor methods are `async` only for\n * interface uniformity — their bodies complete before the Promise is returned,\n * so voiding the call applies mutations in-order without yielding.\n */\nfunction replayJournalSync(adapter: PlainMemoryAdapter, journal: readonly JournalEntry[]): void {\n const queue: string[] = [];\n const idFactory: PartIdFactory = (_prefix) => {\n const id = queue.shift();\n if (id == null) throw new Error('unrecorded id');\n return id;\n };\n const editor = new SemanticEditor(adapter, new SchemaValidator(), idFactory);\n\n for (const entry of journal) {\n queue.push(...(entry.generated_ids ?? []));\n const payload = entry.payload;\n switch (entry.kind) {\n case 'MoveVideoClips':\n void editor.moveVideoClips(payload as MoveVideoClipsInput);\n break;\n case 'DeleteVideoClips':\n void editor.deleteVideoClips(payload as DeleteVideoClipsInput);\n break;\n case 'AddVideoClips':\n void editor.addVideoClips(payload as AddVideoClipsInput);\n break;\n case 'AdjustVideoClipVolume':\n void editor.adjustVideoClipVolume(payload as AdjustVideoClipVolumeInput);\n break;\n case 'SetVideoClipSpeedShift':\n void editor.setVideoClipSpeedShift(payload as SetVideoClipSpeedShiftInput);\n break;\n case 'ReplaceVideoClipContent':\n void editor.replaceVideoClipContent(payload as ReplaceVideoClipContentInput);\n break;\n case 'AdjustVideoClipDuration':\n void editor.adjustVideoClipDuration(payload as AdjustVideoClipDurationInput);\n break;\n case 'AddSpeeches':\n void editor.addSpeeches(payload as AddSpeechesInput);\n break;\n case 'DeleteSpeeches':\n void editor.deleteSpeeches(payload as DeleteSpeechesInput);\n break;\n case 'MoveSpeeches':\n void editor.moveSpeeches(payload as MoveSpeechesInput);\n break;\n case 'ChangeSpeechScript':\n void editor.changeSpeechScript(payload as ChangeSpeechScriptInput);\n break;\n case 'ChangeSpeechVoice':\n void editor.changeSpeechVoice(payload as ChangeSpeechVoiceInput);\n break;\n case 'AdjustSpeechVolume':\n void editor.adjustSpeechVolume(payload as AdjustSpeechVolumeInput);\n break;\n case 'SetCaptionVisibility':\n void editor.setCaptionVisibility(payload as SetCaptionVisibilityInput);\n break;\n case 'SetCaptionStyle':\n void editor.setCaptionStyle(payload as SetCaptionStyleInput);\n break;\n case 'SetBgm':\n void editor.setBgm(payload as SetBgmInput);\n break;\n case 'DeleteBgm':\n void editor.deleteBgm(payload as DeleteBgmInput);\n break;\n case 'AdjustBgmVolume':\n void editor.adjustBgmVolume(payload as AdjustBgmVolumeInput);\n break;\n default: {\n const _exhaustive: never = entry.kind;\n throw new Error(`replayJournalSync: unsupported kind ${String(_exhaustive)}`);\n }\n }\n if (queue.length > 0) throw new Error('unconsumed ids');\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { VideoDocument } from "@mengine/medeo-client";
2
+
3
+ //#region src/sandbox/worker-entry.d.ts
4
+ /**
5
+ * Node worker entry for trusted edit scripts.
6
+ *
7
+ * Spawns an `EditSandboxSession`, runs the agent script in a bare `vm` context
8
+ * (no fetch/process/setTimeout), and streams journal entries + logs to the host
9
+ * so hard timeout / OOM termination still preserves partial products.
10
+ */
11
+ interface WorkerData {
12
+ document: VideoDocument;
13
+ script: string;
14
+ inputs?: Record<string, unknown>;
15
+ idLabel?: string;
16
+ }
17
+ //#endregion
18
+ export { WorkerData };
19
+ //# sourceMappingURL=worker-entry.d.mts.map
@@ -0,0 +1,110 @@
1
+ import { t as EditSandboxSession } from "./script-session-B8fc9Ccb.mjs";
2
+ import { parentPort, workerData } from "node:worker_threads";
3
+ import vm from "node:vm";
4
+ //#region src/sandbox/worker-entry.ts
5
+ const data = workerData;
6
+ if (parentPort == null) throw new Error("worker-entry must run inside a worker_threads Worker");
7
+ const port = parentPort;
8
+ function post(message) {
9
+ port.postMessage(message);
10
+ }
11
+ function countingFactory(label) {
12
+ let n = 0;
13
+ return (prefix) => `${prefix}_${label}${++n}`;
14
+ }
15
+ /** Extract script line/column from the first `agent-script.js` stack frame. */
16
+ function positionFromError(error, script, phase) {
17
+ const obj = error != null && typeof error === "object" ? error : null;
18
+ const message = obj != null && typeof obj.message === "string" ? obj.message : error instanceof Error ? error.message : String(error);
19
+ const stack = obj != null && typeof obj.stack === "string" ? obj.stack : void 0;
20
+ let line = typeof obj?.lineNumber === "number" ? obj.lineNumber : void 0;
21
+ let column = typeof obj?.columnNumber === "number" ? obj.columnNumber : void 0;
22
+ if (stack != null) {
23
+ const match = /agent-script\.js:(\d+)(?::(\d+))?/.exec(stack);
24
+ if (match != null) {
25
+ line = Number(match[1]);
26
+ if (match[2] != null) column = Number(match[2]);
27
+ }
28
+ }
29
+ if (phase === "parse" && script != null && line != null && line >= 2) {
30
+ const prev = script.split("\n")[line - 2];
31
+ if (prev != null && /[{([]\s*$/.test(prev)) {
32
+ line = line - 1;
33
+ column = prev.length;
34
+ }
35
+ }
36
+ return {
37
+ message,
38
+ line,
39
+ column,
40
+ stack
41
+ };
42
+ }
43
+ async function main() {
44
+ const idFactory = data.idLabel != null ? countingFactory(data.idLabel) : void 0;
45
+ const session = new EditSandboxSession(data.document, {
46
+ idFactory,
47
+ onEntry: (entry) => post({
48
+ t: "entry",
49
+ entry
50
+ }),
51
+ onLog: (line) => post({
52
+ t: "log",
53
+ line
54
+ }),
55
+ onTruncate: (index) => post({
56
+ t: "truncate",
57
+ index
58
+ })
59
+ });
60
+ const wrapped = `(async (edit, timeline, checkpoint, rollbackTo, inputs, console) => {${data.script}\n})`;
61
+ const ctx = vm.createContext(Object.create(null));
62
+ let run;
63
+ try {
64
+ run = vm.runInContext(wrapped, ctx, { filename: "agent-script.js" });
65
+ } catch (error) {
66
+ post({
67
+ t: "fail",
68
+ phase: "parse",
69
+ error: positionFromError(error, data.script, "parse")
70
+ });
71
+ return;
72
+ }
73
+ if (typeof run !== "function") {
74
+ post({
75
+ t: "fail",
76
+ phase: "runtime",
77
+ error: { message: "agent script wrapper did not evaluate to a function" }
78
+ });
79
+ return;
80
+ }
81
+ try {
82
+ const invoke = run;
83
+ post({ t: "ready" });
84
+ await invoke(session.edit, session.timeline, session.checkpoint, session.rollbackTo, data.inputs ?? {}, session.console);
85
+ } catch (error) {
86
+ post({
87
+ t: "fail",
88
+ phase: "runtime",
89
+ error: positionFromError(error, data.script, "runtime")
90
+ });
91
+ return;
92
+ }
93
+ const plan = session.buildPlan("");
94
+ post({
95
+ t: "done",
96
+ preview: plan.preview,
97
+ opsCount: plan.ops.length
98
+ });
99
+ }
100
+ main().catch((error) => {
101
+ post({
102
+ t: "fail",
103
+ phase: "runtime",
104
+ error: positionFromError(error)
105
+ });
106
+ });
107
+ //#endregion
108
+ export {};
109
+
110
+ //# sourceMappingURL=worker-entry.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-entry.mjs","names":[],"sources":["../src/sandbox/worker-entry.ts"],"sourcesContent":["/// <reference types=\"node\" />\n\nimport vm from 'node:vm';\nimport { parentPort, workerData } from 'node:worker_threads';\n\nimport type { JournalEntry, PartIdFactory, VideoDocument } from '@mengine/medeo-client';\n\nimport { EditSandboxSession } from './script-session.ts';\n\n/**\n * Node worker entry for trusted edit scripts.\n *\n * Spawns an `EditSandboxSession`, runs the agent script in a bare `vm` context\n * (no fetch/process/setTimeout), and streams journal entries + logs to the host\n * so hard timeout / OOM termination still preserves partial products.\n */\n\nexport interface WorkerData {\n document: VideoDocument;\n script: string;\n inputs?: Record<string, unknown>;\n idLabel?: string;\n}\n\ntype HostMessage =\n | { t: 'ready' }\n | { t: 'entry'; entry: JournalEntry }\n | { t: 'log'; line: string }\n | { t: 'truncate'; index: number }\n | { t: 'done'; preview: string; opsCount: number }\n | {\n t: 'fail';\n phase: 'parse' | 'runtime';\n error: { message: string; line?: number; column?: number; stack?: string };\n };\n\nconst data = workerData as WorkerData;\nif (parentPort == null) {\n throw new Error('worker-entry must run inside a worker_threads Worker');\n}\nconst port = parentPort;\n\nfunction post(message: HostMessage): void {\n port.postMessage(message);\n}\n\nfunction countingFactory(label: string): PartIdFactory {\n let n = 0;\n return (prefix) => `${prefix}_${label}${++n}`;\n}\n\n/** Extract script line/column from the first `agent-script.js` stack frame. */\nfunction positionFromError(\n error: unknown,\n script?: string,\n phase?: 'parse' | 'runtime',\n): { line?: number; column?: number; stack?: string; message: string } {\n // Duck-type: vm SyntaxError in a worker may fail `instanceof Error` across realms.\n const obj = error != null && typeof error === 'object' ? (error as Record<string, unknown>) : null;\n const message =\n obj != null && typeof obj.message === 'string'\n ? obj.message\n : error instanceof Error\n ? error.message\n : String(error);\n const stack = obj != null && typeof obj.stack === 'string' ? obj.stack : undefined;\n\n let line = typeof obj?.lineNumber === 'number' ? obj.lineNumber : undefined;\n let column = typeof obj?.columnNumber === 'number' ? obj.columnNumber : undefined;\n\n if (stack != null) {\n // Prefer the header form `agent-script.js:N` (SyntaxError) or `agent-script.js:N:M`.\n const match = /agent-script\\.js:(\\d+)(?::(\\d+))?/.exec(stack);\n if (match != null) {\n line = Number(match[1]);\n if (match[2] != null) column = Number(match[2]);\n }\n }\n\n // Parse-phase refinement: V8 often points at the token after an unclosed\n // `{`/`(`/`[`; walk back one line when the previous line ends that way so\n // the reported line matches the agent-authored incomplete construct.\n if (phase === 'parse' && script != null && line != null && line >= 2) {\n const lines = script.split('\\n');\n const prev = lines[line - 2];\n if (prev != null && /[{([]\\s*$/.test(prev)) {\n line = line - 1;\n column = prev.length;\n }\n }\n\n return { message, line, column, stack };\n}\n\nasync function main(): Promise<void> {\n const idFactory = data.idLabel != null ? countingFactory(data.idLabel) : undefined;\n const session = new EditSandboxSession(data.document, {\n idFactory,\n onEntry: (entry) => post({ t: 'entry', entry }),\n onLog: (line) => post({ t: 'log', line }),\n onTruncate: (index) => post({ t: 'truncate', index }),\n });\n\n // Prelude stays on the same physical line as script line 1 so stack line\n // numbers map 1:1 onto the agent script (no leading newline).\n const wrapped = `(async (edit, timeline, checkpoint, rollbackTo, inputs, console) => {${data.script}\\n})`;\n\n const ctx = vm.createContext(Object.create(null) as Record<string, unknown>);\n\n let run: unknown;\n try {\n run = vm.runInContext(wrapped, ctx, { filename: 'agent-script.js' });\n } catch (error) {\n const pos = positionFromError(error, data.script, 'parse');\n post({ t: 'fail', phase: 'parse', error: pos });\n return;\n }\n\n if (typeof run !== 'function') {\n post({\n t: 'fail',\n phase: 'runtime',\n error: { message: 'agent script wrapper did not evaluate to a function' },\n });\n return;\n }\n\n try {\n const invoke = run as (\n edit: EditSandboxSession['edit'],\n timeline: EditSandboxSession['timeline'],\n checkpoint: EditSandboxSession['checkpoint'],\n rollbackTo: EditSandboxSession['rollbackTo'],\n inputs: Record<string, unknown>,\n console: EditSandboxSession['console'],\n ) => Promise<unknown>;\n // Signal host that cold start is done; timeout wall-clock starts here.\n post({ t: 'ready' });\n await invoke(\n session.edit,\n session.timeline,\n session.checkpoint,\n session.rollbackTo,\n data.inputs ?? {},\n session.console,\n );\n } catch (error) {\n const pos = positionFromError(error, data.script, 'runtime');\n post({ t: 'fail', phase: 'runtime', error: pos });\n return;\n }\n\n const plan = session.buildPlan('');\n post({ t: 'done', preview: plan.preview, opsCount: plan.ops.length });\n}\n\nmain().catch((error: unknown) => {\n const pos = positionFromError(error);\n post({ t: 'fail', phase: 'runtime', error: pos });\n});\n"],"mappings":";;;;AAoCA,MAAM,OAAO;AACb,IAAI,cAAc,MAChB,MAAM,IAAI,MAAM,sDAAsD;AAExE,MAAM,OAAO;AAEb,SAAS,KAAK,SAA4B;CACxC,KAAK,YAAY,OAAO;AAC1B;AAEA,SAAS,gBAAgB,OAA8B;CACrD,IAAI,IAAI;CACR,QAAQ,WAAW,GAAG,OAAO,GAAG,QAAQ,EAAE;AAC5C;;AAGA,SAAS,kBACP,OACA,QACA,OACqE;CAErE,MAAM,MAAM,SAAS,QAAQ,OAAO,UAAU,WAAY,QAAoC;CAC9F,MAAM,UACJ,OAAO,QAAQ,OAAO,IAAI,YAAY,WAClC,IAAI,UACJ,iBAAiB,QACf,MAAM,UACN,OAAO,KAAK;CACpB,MAAM,QAAQ,OAAO,QAAQ,OAAO,IAAI,UAAU,WAAW,IAAI,QAAQ,KAAA;CAEzE,IAAI,OAAO,OAAO,KAAK,eAAe,WAAW,IAAI,aAAa,KAAA;CAClE,IAAI,SAAS,OAAO,KAAK,iBAAiB,WAAW,IAAI,eAAe,KAAA;CAExE,IAAI,SAAS,MAAM;EAEjB,MAAM,QAAQ,oCAAoC,KAAK,KAAK;EAC5D,IAAI,SAAS,MAAM;GACjB,OAAO,OAAO,MAAM,EAAE;GACtB,IAAI,MAAM,MAAM,MAAM,SAAS,OAAO,MAAM,EAAE;EAChD;CACF;CAKA,IAAI,UAAU,WAAW,UAAU,QAAQ,QAAQ,QAAQ,QAAQ,GAAG;EAEpE,MAAM,OADQ,OAAO,MAAM,IACV,EAAE,OAAO;EAC1B,IAAI,QAAQ,QAAQ,YAAY,KAAK,IAAI,GAAG;GAC1C,OAAO,OAAO;GACd,SAAS,KAAK;EAChB;CACF;CAEA,OAAO;EAAE;EAAS;EAAM;EAAQ;CAAM;AACxC;AAEA,eAAe,OAAsB;CACnC,MAAM,YAAY,KAAK,WAAW,OAAO,gBAAgB,KAAK,OAAO,IAAI,KAAA;CACzE,MAAM,UAAU,IAAI,mBAAmB,KAAK,UAAU;EACpD;EACA,UAAU,UAAU,KAAK;GAAE,GAAG;GAAS;EAAM,CAAC;EAC9C,QAAQ,SAAS,KAAK;GAAE,GAAG;GAAO;EAAK,CAAC;EACxC,aAAa,UAAU,KAAK;GAAE,GAAG;GAAY;EAAM,CAAC;CACtD,CAAC;CAID,MAAM,UAAU,wEAAwE,KAAK,OAAO;CAEpG,MAAM,MAAM,GAAG,cAAc,OAAO,OAAO,IAAI,CAA4B;CAE3E,IAAI;CACJ,IAAI;EACF,MAAM,GAAG,aAAa,SAAS,KAAK,EAAE,UAAU,kBAAkB,CAAC;CACrE,SAAS,OAAO;EAEd,KAAK;GAAE,GAAG;GAAQ,OAAO;GAAS,OADtB,kBAAkB,OAAO,KAAK,QAAQ,OACP;EAAE,CAAC;EAC9C;CACF;CAEA,IAAI,OAAO,QAAQ,YAAY;EAC7B,KAAK;GACH,GAAG;GACH,OAAO;GACP,OAAO,EAAE,SAAS,sDAAsD;EAC1E,CAAC;EACD;CACF;CAEA,IAAI;EACF,MAAM,SAAS;EASf,KAAK,EAAE,GAAG,QAAQ,CAAC;EACnB,MAAM,OACJ,QAAQ,MACR,QAAQ,UACR,QAAQ,YACR,QAAQ,YACR,KAAK,UAAU,CAAC,GAChB,QAAQ,OACV;CACF,SAAS,OAAO;EAEd,KAAK;GAAE,GAAG;GAAQ,OAAO;GAAW,OADxB,kBAAkB,OAAO,KAAK,QAAQ,SACL;EAAE,CAAC;EAChD;CACF;CAEA,MAAM,OAAO,QAAQ,UAAU,EAAE;CACjC,KAAK;EAAE,GAAG;EAAQ,SAAS,KAAK;EAAS,UAAU,KAAK,IAAI;CAAO,CAAC;AACtE;AAEA,KAAK,EAAE,OAAO,UAAmB;CAE/B,KAAK;EAAE,GAAG;EAAQ,OAAO;EAAW,OADxB,kBAAkB,KACe;CAAE,CAAC;AAClD,CAAC"}