@mengine/medeo-tool 1.0.1-alpha.2 → 1.2.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.
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { i as renderCompactProjection, n as collectAffectedPartIds, r as renderPreview, t as EditSandboxSession } from "./script-session-B8fc9Ccb.mjs";
1
+ import { i as renderCompactProjection, n as collectAffectedPartIds, r as renderPreview, t as EditSandboxSession } from "./script-session-DdPA4tTf.mjs";
2
2
  import { MengineDocSession, MengineHttpClient, MengineHttpRequestError, ValidationError, createMirrorVideoDocument, createPlainMemoryAdapter, replayJournal, toVideoDocument } from "@mengine/medeo-client";
3
3
  import { Worker } from "node:worker_threads";
4
4
  import { randomUUID } from "node:crypto";
@@ -157,6 +157,777 @@ function runEditScript(options) {
157
157
  });
158
158
  }
159
159
  //#endregion
160
+ //#region src/sandbox/generated/edit-sandbox-model-context.ts
161
+ /**
162
+ * @generated by gen:sandbox-dts — DO NOT EDIT MANUALLY
163
+ *
164
+ * Runtime copy of the sandbox TypeScript disclosure. The model prompt imports
165
+ * this value so its interface and the checked-in declaration cannot drift.
166
+ */
167
+ const EDIT_SANDBOX_API_DTS = [
168
+ "/**",
169
+ " * @generated by gen:sandbox-dts — DO NOT EDIT MANUALLY",
170
+ " *",
171
+ " * Schema version: video-document/v0",
172
+ " * Semantic ops: 20",
173
+ " *",
174
+ " * Boundary: zod `superRefine` / custom refine rules are NOT introspectable and",
175
+ " * do not appear here. Business mutual-exclusion rules surface via runtime",
176
+ " * validation errors (L3 feedback channel).",
177
+ " *",
178
+ " * @example 读取→计算→批量写",
179
+ " * ```ts",
180
+ " * const clips = timeline.clipsInRange(0, 10_000);",
181
+ " * await edit.setVideoClipSpeedShift({",
182
+ " * clips: clips.map((c) => ({ clip_id: c.id, speed_shift: { category: 'linear', mode: 'constant', config: { linear: { speed: 1.5 } } } })),",
183
+ " * });",
184
+ " * ```",
185
+ " *",
186
+ " * @example anchored 删除",
187
+ " * ```ts",
188
+ " * await edit.deleteVideoClips({ clip_ids: ['clip_a'], on_anchored: 'detach' });",
189
+ " * ```",
190
+ " */",
191
+ "",
192
+ "/**",
193
+ " * A clip's playback-speed fact, the only thing `SetVideoClipSpeedShift` writes.",
194
+ " */",
195
+ "export interface SpeedShift {",
196
+ " category: 'linear' | 'curve';",
197
+ " mode: string;",
198
+ " config:",
199
+ " | {",
200
+ " linear: {",
201
+ " /**",
202
+ " * @constraint positive",
203
+ " */",
204
+ " speed: number;",
205
+ " };",
206
+ " }",
207
+ " | {",
208
+ " curve: {",
209
+ " /**",
210
+ " * @constraint minLength(2)",
211
+ " */",
212
+ " keyframes: {",
213
+ " /**",
214
+ " * @constraint min(0)",
215
+ " * @constraint max(1)",
216
+ " */",
217
+ " position: number;",
218
+ " /**",
219
+ " * @constraint min(0)",
220
+ " */",
221
+ " rate: number;",
222
+ " /**",
223
+ " * Bezier tangent handle (x, y)",
224
+ " */",
225
+ " in_tangent?: { x: number; y: number };",
226
+ " /**",
227
+ " * Bezier tangent handle (x, y)",
228
+ " */",
229
+ " out_tangent?: { x: number; y: number };",
230
+ " }[];",
231
+ " };",
232
+ " };",
233
+ "}",
234
+ "",
235
+ "/**",
236
+ " * TTS voice summary attached to a speech",
237
+ " */",
238
+ "export interface Voice {",
239
+ " /**",
240
+ " * @constraint minLength(1)",
241
+ " */",
242
+ " id: string;",
243
+ " name: string;",
244
+ "}",
245
+ "",
246
+ "/**",
247
+ " * A materialized speech-subtree write (speeches + their captions).",
248
+ " */",
249
+ "export interface SpeechAssets {",
250
+ " /**",
251
+ " * Materialized speech parts to write",
252
+ " * @constraint minLength(1)",
253
+ " */",
254
+ " speeches: {",
255
+ " /**",
256
+ " * The speech part ID (= side-effect speech_parts[].id)",
257
+ " * @constraint minLength(1)",
258
+ " */",
259
+ " speech_id: string;",
260
+ " /**",
261
+ " * Host video clip part ID the speech anchors to (RFC 02 §4)",
262
+ " * @constraint minLength(1)",
263
+ " */",
264
+ " anchor_part_id: string;",
265
+ " /**",
266
+ " * Offset within the host clip (speech.abs = host.abs + offset_ms)",
267
+ " * @constraint int",
268
+ " * @constraint min(0)",
269
+ " */",
270
+ " offset_ms: number;",
271
+ " /**",
272
+ " * @constraint minLength(1)",
273
+ " */",
274
+ " audio_storage_key: string;",
275
+ " /**",
276
+ " * Duration in milliseconds (> 0)",
277
+ " * @constraint int",
278
+ " * @constraint positive",
279
+ " */",
280
+ " duration_ms: number;",
281
+ " audio_script: string;",
282
+ " /**",
283
+ " * Volume in decibels (-60.0 to 20.0; 0.0 = original, -60 = mute, +20 = max)",
284
+ " * @constraint min(-60)",
285
+ " * @constraint max(20)",
286
+ " */",
287
+ " volume: number;",
288
+ " /**",
289
+ " * TTS voice summary attached to a speech",
290
+ " */",
291
+ " voice: {",
292
+ " /**",
293
+ " * @constraint minLength(1)",
294
+ " */",
295
+ " id: string;",
296
+ " name: string;",
297
+ " };",
298
+ " /**",
299
+ " * @constraint minLength(1)",
300
+ " */",
301
+ " origin_speech_id: string;",
302
+ " /**",
303
+ " * Caption part IDs owned by this speech",
304
+ " */",
305
+ " caption_ids: string[];",
306
+ " }[];",
307
+ " /**",
308
+ " * Materialized caption parts owned by the speeches",
309
+ " */",
310
+ " captions: {",
311
+ " /**",
312
+ " * The caption part ID (= side-effect created_caption_parts[].id)",
313
+ " * @constraint minLength(1)",
314
+ " */",
315
+ " caption_id: string;",
316
+ " /**",
317
+ " * The owning speech part ID",
318
+ " * @constraint minLength(1)",
319
+ " */",
320
+ " speech_part_id: string;",
321
+ " text: string;",
322
+ " /**",
323
+ " * Offset within the host speech (caption.abs = speech.abs + start_ms)",
324
+ " * @constraint int",
325
+ " * @constraint min(0)",
326
+ " */",
327
+ " start_ms: number;",
328
+ " /**",
329
+ " * Duration in milliseconds (> 0)",
330
+ " * @constraint int",
331
+ " * @constraint positive",
332
+ " */",
333
+ " duration_ms: number;",
334
+ " }[];",
335
+ "}",
336
+ "",
337
+ "export interface MoveVideoClipsInput {",
338
+ " /**",
339
+ " * List of video clips to move to new positions",
340
+ " * @constraint minLength(1)",
341
+ " */",
342
+ " clips: {",
343
+ " /**",
344
+ " * The video clip part ID to move",
345
+ " * @constraint minLength(1)",
346
+ " */",
347
+ " clip_id: string;",
348
+ " /**",
349
+ " * New absolute start time in milliseconds on the timeline",
350
+ " * @constraint int",
351
+ " * @constraint min(0)",
352
+ " */",
353
+ " new_start_ms: number;",
354
+ " /**",
355
+ " * Target track ID to move the clip to (optional)",
356
+ " * @constraint minLength(1)",
357
+ " */",
358
+ " new_track_id?: string;",
359
+ " }[];",
360
+ "}",
361
+ "",
362
+ "export interface MoveVideoClipsByAnchorInput {",
363
+ " /**",
364
+ " * Clips to move as one block, keeping their relative order. Need not be contiguous on the track.",
365
+ " * @constraint minLength(1)",
366
+ " */",
367
+ " clip_ids: string[];",
368
+ " /**",
369
+ " * Where the moved block lands: before/after a reference clip, or at the head of the track",
370
+ " */",
371
+ " anchor:",
372
+ " | {",
373
+ " position: 'before';",
374
+ " /**",
375
+ " * The moved block lands immediately before this clip",
376
+ " * @constraint minLength(1)",
377
+ " */",
378
+ " clip_id: string;",
379
+ " }",
380
+ " | {",
381
+ " position: 'after';",
382
+ " /**",
383
+ " * The moved block lands immediately after this clip",
384
+ " * @constraint minLength(1)",
385
+ " */",
386
+ " clip_id: string;",
387
+ " }",
388
+ " | { position: 'track_start' };",
389
+ " /**",
390
+ " * What happens to speeches anchored to the moved clips (required — see the policy doc)",
391
+ " */",
392
+ " on_anchored: 'follow' | 'keep_absolute';",
393
+ "}",
394
+ "",
395
+ "export interface DeleteVideoClipsInput {",
396
+ " /**",
397
+ " * List of video clip part IDs to delete from the main track",
398
+ " * @constraint minLength(1)",
399
+ " */",
400
+ " clip_ids: string[];",
401
+ " /**",
402
+ " * How to treat anchored children (default cascade)",
403
+ " */",
404
+ " on_anchored?: 'cascade' | 'detach';",
405
+ "}",
406
+ "",
407
+ "/**",
408
+ " * Add video clips to a track.",
409
+ " */",
410
+ "export interface AddVideoClipsInput {",
411
+ " /**",
412
+ " * List of video clips to create",
413
+ " * @constraint minLength(1)",
414
+ " */",
415
+ " clips: {",
416
+ " /**",
417
+ " * The media asset ID for the video clip",
418
+ " * @constraint minLength(1)",
419
+ " */",
420
+ " media_id: string;",
421
+ " /**",
422
+ " * Absolute start time in milliseconds on the timeline",
423
+ " * @constraint int",
424
+ " * @constraint min(0)",
425
+ " */",
426
+ " start_ms?: number;",
427
+ " /**",
428
+ " * The source media's intrinsic full length in ms",
429
+ " * @constraint int",
430
+ " * @constraint positive",
431
+ " */",
432
+ " media_duration_ms: number;",
433
+ " /**",
434
+ " * Trim window start in the media (default 0)",
435
+ " * @constraint int",
436
+ " * @constraint min(0)",
437
+ " */",
438
+ " play_in?: number;",
439
+ " /**",
440
+ " * Trim window end in the media (default media_duration_ms)",
441
+ " * @constraint int",
442
+ " * @constraint positive",
443
+ " */",
444
+ " play_out?: number;",
445
+ " /**",
446
+ " * Target track ID (optional, defaults to main track)",
447
+ " * @constraint minLength(1)",
448
+ " */",
449
+ " track_id?: string;",
450
+ " }[];",
451
+ " /**",
452
+ " * Insert new clips before this clip ID",
453
+ " * @constraint minLength(1)",
454
+ " */",
455
+ " before_clip_id?: string;",
456
+ " /**",
457
+ " * Insert new clips after this clip ID",
458
+ " * @constraint minLength(1)",
459
+ " */",
460
+ " after_clip_id?: string;",
461
+ "}",
462
+ "",
463
+ "export interface AdjustVideoClipVolumeInput {",
464
+ " /**",
465
+ " * List of video clips with their new volume settings",
466
+ " * @constraint minLength(1)",
467
+ " */",
468
+ " clips: {",
469
+ " /**",
470
+ " * The video clip part ID to adjust volume for",
471
+ " * @constraint minLength(1)",
472
+ " */",
473
+ " clip_id: string;",
474
+ " /**",
475
+ " * Volume in decibels (-60.0 to 20.0; 0.0 = original)",
476
+ " * @constraint min(-60)",
477
+ " * @constraint max(20)",
478
+ " */",
479
+ " volume: number;",
480
+ " }[];",
481
+ "}",
482
+ "",
483
+ "/**",
484
+ " * Set the playback speed of existing video clips.",
485
+ " */",
486
+ "export interface SetVideoClipSpeedShiftInput {",
487
+ " /**",
488
+ " * Video clips with their new speed settings",
489
+ " * @constraint minLength(1)",
490
+ " */",
491
+ " clips: {",
492
+ " /**",
493
+ " * The video clip part ID to set speed for",
494
+ " * @constraint minLength(1)",
495
+ " */",
496
+ " clip_id: string;",
497
+ " /**",
498
+ " * The new speed setting, or null to reset to 1×",
499
+ " */",
500
+ " speed_shift: {",
501
+ " category: 'linear' | 'curve';",
502
+ " mode: string;",
503
+ " config:",
504
+ " | {",
505
+ " linear: {",
506
+ " /**",
507
+ " * @constraint positive",
508
+ " */",
509
+ " speed: number;",
510
+ " };",
511
+ " }",
512
+ " | {",
513
+ " curve: {",
514
+ " /**",
515
+ " * @constraint minLength(2)",
516
+ " */",
517
+ " keyframes: {",
518
+ " /**",
519
+ " * @constraint min(0)",
520
+ " * @constraint max(1)",
521
+ " */",
522
+ " position: number;",
523
+ " /**",
524
+ " * @constraint min(0)",
525
+ " */",
526
+ " rate: number;",
527
+ " /**",
528
+ " * Bezier tangent handle (x, y)",
529
+ " */",
530
+ " in_tangent?: { x: number; y: number };",
531
+ " /**",
532
+ " * Bezier tangent handle (x, y)",
533
+ " */",
534
+ " out_tangent?: { x: number; y: number };",
535
+ " }[];",
536
+ " };",
537
+ " };",
538
+ " } | null;",
539
+ " }[];",
540
+ "}",
541
+ "",
542
+ "/**",
543
+ " * Replace the media backing existing video clips.",
544
+ " */",
545
+ "export interface ReplaceVideoClipContentInput {",
546
+ " /**",
547
+ " * Video clips whose media is being replaced",
548
+ " * @constraint minLength(1)",
549
+ " */",
550
+ " clips: {",
551
+ " /**",
552
+ " * Existing video clip part ID to re-point",
553
+ " * @constraint minLength(1)",
554
+ " */",
555
+ " clip_id: string;",
556
+ " /**",
557
+ " * The new media asset ID",
558
+ " * @constraint minLength(1)",
559
+ " */",
560
+ " origin_media_id: string;",
561
+ " /**",
562
+ " * The new media's intrinsic full length",
563
+ " * @constraint int",
564
+ " * @constraint positive",
565
+ " */",
566
+ " media_duration_ms: number;",
567
+ " /**",
568
+ " * Trim window start in the new media (usually 0)",
569
+ " * @constraint int",
570
+ " * @constraint min(0)",
571
+ " */",
572
+ " play_in: number;",
573
+ " /**",
574
+ " * Trim window end in the new media (usually = media_duration_ms)",
575
+ " * @constraint int",
576
+ " * @constraint positive",
577
+ " */",
578
+ " play_out: number;",
579
+ " /**",
580
+ " * Volume in decibels (-60.0 to 20.0; 0.0 = original, -60 = mute, +20 = max)",
581
+ " * @constraint min(-60)",
582
+ " * @constraint max(20)",
583
+ " */",
584
+ " volume: number;",
585
+ " }[];",
586
+ "}",
587
+ "",
588
+ "export interface ReplaceVideoClipSequenceInput {",
589
+ " /**",
590
+ " * The clips being replaced: a contiguous main-track run, listed in timeline order",
591
+ " * @constraint minLength(1)",
592
+ " */",
593
+ " old_clip_ids: string[];",
594
+ " /**",
595
+ " * The replacement clips, in the order they take on the track",
596
+ " * @constraint minLength(1)",
597
+ " */",
598
+ " new_clips: {",
599
+ " /**",
600
+ " * The replacement media asset ID. Omit to create an empty placeholder clip.",
601
+ " * @constraint minLength(1)",
602
+ " */",
603
+ " media_id?: string;",
604
+ " /**",
605
+ " * The source media's intrinsic full length in ms",
606
+ " * @constraint int",
607
+ " * @constraint positive",
608
+ " */",
609
+ " media_duration_ms: number;",
610
+ " /**",
611
+ " * Trim window start in the media (default 0)",
612
+ " * @constraint int",
613
+ " * @constraint min(0)",
614
+ " */",
615
+ " play_in?: number;",
616
+ " /**",
617
+ " * Trim window end in the media (default media_duration_ms)",
618
+ " * @constraint int",
619
+ " * @constraint positive",
620
+ " */",
621
+ " play_out?: number;",
622
+ " }[];",
623
+ " /**",
624
+ " * What happens to speeches anchored to the replaced clips (required — see the policy doc)",
625
+ " */",
626
+ " on_anchored: 'remap' | 'cascade';",
627
+ "}",
628
+ "",
629
+ "/**",
630
+ " * Re-trim existing video clips (the user-facing \"adjust duration\" gesture is a trim of the source window).",
631
+ " */",
632
+ "export interface AdjustVideoClipDurationInput {",
633
+ " /**",
634
+ " * Video clips with their new trim windows",
635
+ " * @constraint minLength(1)",
636
+ " */",
637
+ " clips: {",
638
+ " /**",
639
+ " * The video clip part ID to re-trim",
640
+ " * @constraint minLength(1)",
641
+ " */",
642
+ " clip_id: string;",
643
+ " /**",
644
+ " * New trim window start in the source media",
645
+ " * @constraint int",
646
+ " * @constraint min(0)",
647
+ " */",
648
+ " play_in: number;",
649
+ " /**",
650
+ " * New trim window end in the source media",
651
+ " * @constraint int",
652
+ " * @constraint positive",
653
+ " */",
654
+ " play_out: number;",
655
+ " }[];",
656
+ "}",
657
+ "",
658
+ "/**",
659
+ " * Add speeches (and their captions).",
660
+ " */",
661
+ "export interface AddSpeechesInput extends SpeechAssets {}",
662
+ "",
663
+ "/**",
664
+ " * Delete speeches with their captions.",
665
+ " */",
666
+ "export interface DeleteSpeechesInput {",
667
+ " /**",
668
+ " * Speech part IDs to delete (their captions cascade-delete)",
669
+ " * @constraint minLength(1)",
670
+ " */",
671
+ " speech_ids: string[];",
672
+ "}",
673
+ "",
674
+ "/**",
675
+ " * Move speeches in time.",
676
+ " */",
677
+ "export interface MoveSpeechesInput {",
678
+ " /**",
679
+ " * Speeches to move to new positions",
680
+ " * @constraint minLength(1)",
681
+ " */",
682
+ " speeches: {",
683
+ " /**",
684
+ " * The speech part ID to move",
685
+ " * @constraint minLength(1)",
686
+ " */",
687
+ " speech_id: string;",
688
+ " /**",
689
+ " * New absolute start time on the timeline",
690
+ " * @constraint int",
691
+ " * @constraint min(0)",
692
+ " */",
693
+ " new_start_ms: number;",
694
+ " }[];",
695
+ "}",
696
+ "",
697
+ "/**",
698
+ " * Change a speech's script or voice.",
699
+ " */",
700
+ "export interface ChangeSpeechScriptInput extends SpeechAssets {}",
701
+ "",
702
+ "export interface ChangeSpeechVoiceInput extends SpeechAssets {}",
703
+ "",
704
+ "export interface AdjustSpeechVolumeInput {",
705
+ " /**",
706
+ " * List of speeches with their new volume settings",
707
+ " * @constraint minLength(1)",
708
+ " */",
709
+ " speeches: {",
710
+ " /**",
711
+ " * The speech part ID to adjust volume for",
712
+ " * @constraint minLength(1)",
713
+ " */",
714
+ " speech_id: string;",
715
+ " /**",
716
+ " * Volume in decibels (-60.0 to 20.0; 0.0 = original)",
717
+ " * @constraint min(-60)",
718
+ " * @constraint max(20)",
719
+ " */",
720
+ " volume: number;",
721
+ " }[];",
722
+ "}",
723
+ "",
724
+ "/**",
725
+ " * Toggle caption visibility (the caption track's `is_hidden` flag).",
726
+ " */",
727
+ "export interface SetCaptionVisibilityInput {",
728
+ " /**",
729
+ " * Whether the caption track is hidden",
730
+ " */",
731
+ " is_hidden: boolean;",
732
+ "}",
733
+ "",
734
+ "/**",
735
+ " * Set the caption visual style.",
736
+ " */",
737
+ "export interface SetCaptionStyleInput {",
738
+ " /**",
739
+ " * Font ID referencing a font from the font library",
740
+ " * @constraint minLength(1)",
741
+ " */",
742
+ " font_id?: string;",
743
+ " /**",
744
+ " * Font size in points",
745
+ " * @constraint positive",
746
+ " */",
747
+ " font_size?: number;",
748
+ " /**",
749
+ " * Font color as hex string, e.g. \"#FFFFFF\"",
750
+ " * @constraint minLength(1)",
751
+ " */",
752
+ " font_color?: string;",
753
+ " /**",
754
+ " * Numeric font weight, e.g. 400 or 700",
755
+ " * @constraint int",
756
+ " */",
757
+ " font_weight?: number;",
758
+ " /**",
759
+ " * Entrance animation preset ID, e.g. \"fade\" or \"none\"",
760
+ " */",
761
+ " entrance_animation?: string;",
762
+ " /**",
763
+ " * Entrance animation duration in ms",
764
+ " * @constraint min(0)",
765
+ " */",
766
+ " entrance_animation_duration_ms?: number;",
767
+ " /**",
768
+ " * Outline/stroke color as hex string, e.g. \"#000000\"",
769
+ " * @constraint minLength(1)",
770
+ " */",
771
+ " stroke_color?: string;",
772
+ " /**",
773
+ " * Outline/stroke width in pixels",
774
+ " * @constraint min(0)",
775
+ " */",
776
+ " stroke_width?: number;",
777
+ " /**",
778
+ " * Caption center X as a fraction (0.0 to 1.0)",
779
+ " */",
780
+ " position_x?: number;",
781
+ " /**",
782
+ " * Caption center Y as a fraction (0.0 to 1.0)",
783
+ " */",
784
+ " position_y?: number;",
785
+ "}",
786
+ "",
787
+ "/**",
788
+ " * Set the document BGM.",
789
+ " */",
790
+ "export interface SetBgmInput {",
791
+ " /**",
792
+ " * The bgm part ID to write",
793
+ " * @constraint minLength(1)",
794
+ " */",
795
+ " bgm_id: string;",
796
+ " /**",
797
+ " * @constraint minLength(1)",
798
+ " */",
799
+ " audio_storage_key: string;",
800
+ " /**",
801
+ " * The media asset ID",
802
+ " * @constraint minLength(1)",
803
+ " */",
804
+ " origin_media_id: string;",
805
+ " /**",
806
+ " * Volume in decibels (-60.0 to 20.0; 0.0 = original, -60 = mute, +20 = max)",
807
+ " * @constraint min(-60)",
808
+ " * @constraint max(20)",
809
+ " */",
810
+ " volume: number;",
811
+ "}",
812
+ "",
813
+ "/**",
814
+ " * Remove the document BGM.",
815
+ " */",
816
+ "export interface DeleteBgmInput {",
817
+ " [key: string]: never;",
818
+ "}",
819
+ "",
820
+ "export interface AdjustBgmVolumeInput {",
821
+ " /**",
822
+ " * List of bgm parts with their new volume settings",
823
+ " * @constraint minLength(1)",
824
+ " */",
825
+ " bgm: {",
826
+ " /**",
827
+ " * The bgm part ID to adjust volume for",
828
+ " * @constraint minLength(1)",
829
+ " */",
830
+ " bgm_id: string;",
831
+ " /**",
832
+ " * Volume in decibels (-60.0 to 20.0; 0.0 = original)",
833
+ " * @constraint min(-60)",
834
+ " * @constraint max(20)",
835
+ " */",
836
+ " volume: number;",
837
+ " }[];",
838
+ "}",
839
+ "",
840
+ "/** Agent write surface — one method per SemanticOp kind. */",
841
+ "export interface EditApi {",
842
+ " moveVideoClips(input: MoveVideoClipsInput): Promise<void>;",
843
+ " moveVideoClipsByAnchor(input: MoveVideoClipsByAnchorInput): Promise<void>;",
844
+ " deleteVideoClips(input: DeleteVideoClipsInput): Promise<void>;",
845
+ " /** Add video clips to a track. */",
846
+ " addVideoClips(input: AddVideoClipsInput): Promise<void>;",
847
+ " adjustVideoClipVolume(input: AdjustVideoClipVolumeInput): Promise<void>;",
848
+ " /** Set the playback speed of existing video clips. */",
849
+ " setVideoClipSpeedShift(input: SetVideoClipSpeedShiftInput): Promise<void>;",
850
+ " /** Replace the media backing existing video clips. */",
851
+ " replaceVideoClipContent(input: ReplaceVideoClipContentInput): Promise<void>;",
852
+ " replaceVideoClipSequence(input: ReplaceVideoClipSequenceInput): Promise<void>;",
853
+ " /** Re-trim existing video clips (the user-facing \"adjust duration\" gesture is a trim of the source window). */",
854
+ " adjustVideoClipDuration(input: AdjustVideoClipDurationInput): Promise<void>;",
855
+ " /** Add speeches (and their captions). */",
856
+ " addSpeeches(input: AddSpeechesInput): Promise<void>;",
857
+ " /** Delete speeches with their captions. */",
858
+ " deleteSpeeches(input: DeleteSpeechesInput): Promise<void>;",
859
+ " /** Move speeches in time. */",
860
+ " moveSpeeches(input: MoveSpeechesInput): Promise<void>;",
861
+ " /** Change a speech's script or voice. */",
862
+ " changeSpeechScript(input: ChangeSpeechScriptInput): Promise<void>;",
863
+ " changeSpeechVoice(input: ChangeSpeechVoiceInput): Promise<void>;",
864
+ " adjustSpeechVolume(input: AdjustSpeechVolumeInput): Promise<void>;",
865
+ " /** Toggle caption visibility (the caption track's `is_hidden` flag). */",
866
+ " setCaptionVisibility(input: SetCaptionVisibilityInput): Promise<void>;",
867
+ " /** Set the caption visual style. */",
868
+ " setCaptionStyle(input: SetCaptionStyleInput): Promise<void>;",
869
+ " /** Set the document BGM. */",
870
+ " setBgm(input: SetBgmInput): Promise<void>;",
871
+ " /** Remove the document BGM. */",
872
+ " deleteBgm(input: DeleteBgmInput): Promise<void>;",
873
+ " adjustBgmVolume(input: AdjustBgmVolumeInput): Promise<void>;",
874
+ "}",
875
+ "",
876
+ "/** Clip hit from `clipsInRange`. */",
877
+ "export interface TimelineClipDescriptor {",
878
+ " id: string;",
879
+ " start_ms: number;",
880
+ " end_ms: number;",
881
+ " duration_ms: number;",
882
+ " speed_shift: unknown;",
883
+ " volume: number | undefined;",
884
+ " media_id: string | undefined;",
885
+ "}",
886
+ "",
887
+ "/** Part descriptor from `part(id)`. */",
888
+ "export interface TimelinePartDescriptor {",
889
+ " id: string;",
890
+ " kind: string;",
891
+ " lane: string;",
892
+ " start_ms: number;",
893
+ " end_ms: number;",
894
+ " duration_ms: number;",
895
+ " part: unknown;",
896
+ "}",
897
+ "",
898
+ "/** Opaque VideoDraft projection (full IDL lives in host document types). */",
899
+ "export type VideoDraftProjection = {",
900
+ " readonly timeline?: { readonly duration_ms?: number };",
901
+ " readonly [key: string]: unknown;",
902
+ "};",
903
+ "",
904
+ "/** Agent read surface over the forked document. */",
905
+ "export interface TimelineApi {",
906
+ " /** Snapshot the current VideoDraft projection. */",
907
+ " snapshot(): VideoDraftProjection;",
908
+ " /** Clips whose midpoint falls in `[startMs, endMs)`. */",
909
+ " clipsInRange(startMs: number, endMs: number): TimelineClipDescriptor[];",
910
+ " /** Look up a part by id, or null if missing. */",
911
+ " part(id: string): TimelinePartDescriptor | null;",
912
+ "}",
913
+ "",
914
+ "/** Opaque checkpoint handle for rollback. */",
915
+ "export interface SandboxCheckpoint {",
916
+ " readonly index: number;",
917
+ "}",
918
+ "",
919
+ "export declare const edit: EditApi;",
920
+ "export declare const timeline: TimelineApi;",
921
+ "",
922
+ "/** Capture a rollback point. */",
923
+ "export declare function checkpoint(): SandboxCheckpoint;",
924
+ "/** Roll the sandbox document back to a prior checkpoint. */",
925
+ "export declare function rollbackTo(cp: SandboxCheckpoint): void;",
926
+ "/** Host-injected script arguments (opaque). */",
927
+ "export declare const inputs: unknown;",
928
+ ""
929
+ ].join("\n");
930
+ //#endregion
160
931
  //#region src/prompt.ts
161
932
  const MEDEO_TOOL_DESCRIPTION = `
162
933
  Edit a Medeo video document through a deterministic, side-effect-free JavaScript sandbox.
@@ -168,6 +939,31 @@ Operations:
168
939
 
169
940
  Default flow: snapshot → run-edit-script with auto_commit=false → inspect preview → commit-plan. Use auto_commit=true only for low-risk edits when the host does not need user confirmation. On version mismatch, rerun snapshot and the script; never try to patch a rejected journal by hand.
170
941
  `.trim();
942
+ const MEDEO_TOOL_EXECUTION_RULES = `
943
+ The host supplies the current document. Do not ask for, invent, or pass a document id.
944
+ Use timeline.snapshot() for the whole draft projection. Its duration is timeline.snapshot().timeline?.duration_ms; there is no top-level duration_ms.
945
+ Use only the globals and methods declared by the following TypeScript interface. Values not declared here are unavailable.
946
+ `.trim();
947
+ /** Render the complete MEngine-owned context injected before one model call. */
948
+ function renderMedeoModelContext(input) {
949
+ const updated = input.updatedSincePreviousModelCall == null ? "unknown (first model call)" : String(input.updatedSincePreviousModelCall);
950
+ return `
951
+ ${MEDEO_TOOL_DESCRIPTION}
952
+
953
+ ${MEDEO_TOOL_EXECUTION_RULES}
954
+
955
+ Current MEngine document state (sampled dynamically immediately before this model call):
956
+ - document_version: ${JSON.stringify(input.documentVersion)}
957
+ - updated_since_previous_model_call: ${updated}
958
+
959
+ When updated_since_previous_model_call is true, the document changed after the previous model call. The change may have come from this tool or another editor, so take a fresh snapshot before planning further edits.
960
+
961
+ Sandbox TypeScript interface:
962
+ \`\`\`ts
963
+ ${EDIT_SANDBOX_API_DTS}
964
+ \`\`\`
965
+ `.trim();
966
+ }
171
967
  //#endregion
172
968
  //#region src/schema.ts
173
969
  const MEDEO_TOOL_NAME = "medeo";
@@ -347,6 +1143,7 @@ function opConflict(index, op_kind, message) {
347
1143
  //#endregion
348
1144
  //#region src/host-tool.ts
349
1145
  const DEFAULT_MAX_PLANS = 16;
1146
+ const DEFAULT_MAX_MODEL_CONTEXTS = 128;
350
1147
  function isRecord(value) {
351
1148
  return value !== null && typeof value === "object" && !Array.isArray(value);
352
1149
  }
@@ -410,7 +1207,9 @@ function parseInput(value) {
410
1207
  function createMedeoTool(options) {
411
1208
  const sessions = /* @__PURE__ */ new Map();
412
1209
  const plans = /* @__PURE__ */ new Map();
1210
+ const modelContextVersions = /* @__PURE__ */ new Map();
413
1211
  const maxPlans = options.maxPlans ?? DEFAULT_MAX_PLANS;
1212
+ const maxModelContexts = options.maxModelContexts ?? DEFAULT_MAX_MODEL_CONTEXTS;
414
1213
  let closed = false;
415
1214
  async function getSession(docId) {
416
1215
  if (closed) throw new Error("medeo tool is closed");
@@ -480,6 +1279,35 @@ function createMedeoTool(options) {
480
1279
  }
481
1280
  return planId;
482
1281
  }
1282
+ async function getModelContext(input) {
1283
+ const docId = input.doc_id.trim();
1284
+ const contextId = input.context_id.trim();
1285
+ if (docId.length === 0) throw new Error("doc_id must be a non-empty string");
1286
+ if (contextId.length === 0) throw new Error("context_id must be a non-empty string");
1287
+ const documentVersion = (await getSession(docId)).version();
1288
+ const baselineKey = `${contextId}\u0000${docId}`;
1289
+ const previousVersion = modelContextVersions.get(baselineKey);
1290
+ const updatedSincePreviousModelCall = previousVersion == null ? null : previousVersion !== documentVersion;
1291
+ modelContextVersions.delete(baselineKey);
1292
+ modelContextVersions.set(baselineKey, documentVersion);
1293
+ while (modelContextVersions.size > maxModelContexts) {
1294
+ const oldest = modelContextVersions.keys().next().value;
1295
+ if (oldest === void 0) break;
1296
+ modelContextVersions.delete(oldest);
1297
+ }
1298
+ return {
1299
+ prompt: renderMedeoModelContext({
1300
+ documentVersion,
1301
+ updatedSincePreviousModelCall
1302
+ }),
1303
+ document_version: documentVersion,
1304
+ updated_since_previous_model_call: updatedSincePreviousModelCall
1305
+ };
1306
+ }
1307
+ async function confirmCommitted(session, result) {
1308
+ if (result.kind === "committed" && result.ops_applied > 0) await session.waitForServerAck();
1309
+ return result;
1310
+ }
483
1311
  async function snapshot(input) {
484
1312
  const session = await getSession(input.doc_id);
485
1313
  const document = session.snapshot();
@@ -527,7 +1355,7 @@ function createMedeoTool(options) {
527
1355
  duration_ms: result.durationMs
528
1356
  };
529
1357
  if (input.auto_commit !== true) return base;
530
- const commit = await commitPlan(session, result.plan);
1358
+ const commit = await confirmCommitted(session, await commitPlan(session, result.plan));
531
1359
  return {
532
1360
  ...base,
533
1361
  committed: commit.kind === "committed",
@@ -539,7 +1367,7 @@ function createMedeoTool(options) {
539
1367
  if (cached == null || cached.docId !== input.doc_id) throw new Error(`plan_id ${input.plan_id} is not available for doc ${input.doc_id}`);
540
1368
  const session = await getSession(input.doc_id);
541
1369
  const commitOptions = input.validation === void 0 ? void 0 : { validation: input.validation };
542
- const result = await commitPlan(session, cached.plan, commitOptions);
1370
+ const result = await confirmCommitted(session, await commitPlan(session, cached.plan, commitOptions));
543
1371
  return {
544
1372
  ok: true,
545
1373
  op: "commit-plan",
@@ -553,6 +1381,7 @@ function createMedeoTool(options) {
553
1381
  name: MEDEO_TOOL_NAME,
554
1382
  description: MEDEO_TOOL_DESCRIPTION,
555
1383
  parameters: MEDEO_TOOL_PARAMETERS,
1384
+ getModelContext,
556
1385
  async handle(input) {
557
1386
  try {
558
1387
  const parsed = parseInput(input);
@@ -572,6 +1401,7 @@ function createMedeoTool(options) {
572
1401
  const opening = [...sessions.values()];
573
1402
  sessions.clear();
574
1403
  plans.clear();
1404
+ modelContextVersions.clear();
575
1405
  const errors = [];
576
1406
  for (const sessionPromise of opening) try {
577
1407
  (await sessionPromise).destroy();