@mengine/medeo-client 1.0.1-alpha.2 → 1.1.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.
@@ -1,4 +1,3 @@
1
- import { E as MoveVideoClipsInput, H as AdjustVideoClipVolumeInput, I as DeleteBgmInput, J as AdjustBgmVolumeInput, K as AdjustSpeechVolumeInput, O as MoveSpeechesInput, P as DeleteSpeechesInput, Q as AddSpeechesInput, R as ChangeSpeechScriptInput, S as SetBgmInput, W as AdjustVideoClipDurationInput, X as AddVideoClipsInput, b as SetCaptionStyleInput, g as SetVideoClipSpeedShiftInput, j as DeleteVideoClipsInput, v as SetCaptionVisibilityInput, w as ReplaceVideoClipContentInput, z as ChangeSpeechVoiceInput } from "./index-6e5cbdM3.js";
2
1
  import { InferInputType } from "loro-mirror";
3
2
  import { z } from "zod";
4
3
  import { LoroDoc, PeerID } from "loro-crdt";
@@ -553,19 +552,6 @@ interface Track {
553
552
  is_hidden: boolean | undefined;
554
553
  items: TrackItem[] | undefined;
555
554
  }
556
- /** The linear speed multiplier of a `speed_shift`, defaulting to 1 (original). */
557
- declare function speedOf(speedShift: SpeedShift | undefined): number;
558
- /**
559
- * A video clip's effective timeline duration, derived from authoritative facts
560
- * (RFC 02, `reference/16` §4, reference/17 §5): the trim window
561
- * `play_out - play_in` divided by the speed multiplier, rounded to integer ms.
562
- * `play_in` / `play_out` are optional in the IDL but every write path sets them
563
- * (defaulting to the whole media), and the legacy-ingest projection backfills the
564
- * window from a legacy `duration_ms` — so an authoritative clip always carries a
565
- * trim window and there is no stored `duration_ms` to fall back to. A clip with
566
- * neither bound yields 0. Speeds the clip up (>1× → shorter) or down (<1×).
567
- */
568
- declare function effectiveVideoClipDurationMs(clip: VideoClipPart): number;
569
555
  /**
570
556
  * Authoritative part union: a flat discriminated union over the four part kinds.
571
557
  * Deliberately *not* the IDL's namespace union (no `$unknown` / `visit`): engine
@@ -682,7 +668,7 @@ interface VideoDocument {
682
668
  tracks: Track[] | undefined;
683
669
  part_library: Record<string, PartUnion> | undefined;
684
670
  }
685
- type VideoDocumentValidationIssueCode = 'invalid_schema' | 'duplicate_track_item_identity' | 'unknown_part_kind' | 'part_kind_mismatch' | 'missing_part_reference' | 'track_kind_mismatch' | 'main_track_non_video_clip' | 'invalid_part_value' | 'invalid_speech_caption_reference' | 'invalid_position_anchor';
671
+ type VideoDocumentValidationIssueCode = 'invalid_schema' | 'duplicate_track_item_identity' | 'duplicate_lane_track' | 'unknown_part_kind' | 'part_kind_mismatch' | 'missing_part_reference' | 'track_kind_mismatch' | 'main_track_non_video_clip' | 'invalid_part_value' | 'invalid_speech_caption_reference' | 'invalid_position_anchor';
686
672
  /**
687
673
  * Issue severity (RFC 02 §11.1). `error` is a hard reject — the document is not
688
674
  * a legal `VideoDocument` and must not be projected. `recoverable` marks a
@@ -765,6 +751,49 @@ declare function derivePositionFromAbs(partId: string, abs: number, isMain: bool
765
751
  */
766
752
  declare function fromVideoDocument(document: VideoDocument): VideoDraft;
767
753
  //#endregion
754
+ //#region src/document/initial-document.d.ts
755
+ /**
756
+ * The project facts a freshly created document is built from — exactly the five
757
+ * fields Director sets when it creates a draft row, and nothing else.
758
+ *
759
+ * Deliberately not a whole `VideoDraft`: tracks and the part library of a new
760
+ * document are empty by definition, so accepting them would mean accepting
761
+ * values that must always be empty, and every such field is a place for a future
762
+ * caller to send something that is silently dropped. The narrow shape makes the
763
+ * "a new document has no content" rule structural instead of a convention.
764
+ */
765
+ interface InitialDocumentFacts {
766
+ draftId: string;
767
+ projectId: string | undefined;
768
+ ownerId: string | undefined;
769
+ chatSessionId: string | undefined;
770
+ videoCreationSettings: VideoCreationSettings | undefined;
771
+ }
772
+ /**
773
+ * Build the `VideoDocument` a newly created project starts from: the caller's
774
+ * project facts, no parts, and one empty track per lane.
775
+ *
776
+ * The empty lane tracks are the reason this function exists rather than callers
777
+ * assembling a document inline. Lane tracks are otherwise minted lazily by the
778
+ * first op that needs one (`ensureLaneTrack`), which is find-then-mint over a
779
+ * CRDT list — so if the Agent's first generation and a user edit race on an
780
+ * empty document, each mints its own copy of the same lane and the merge keeps
781
+ * both. Two video_clip tracks violate the at-most-one-main-track rule, and the
782
+ * merged document becomes unprojectable for *every* reader (editor, render,
783
+ * share) with no winning replica to fall back on. Seeding all four lanes up
784
+ * front means `ensureLaneTrack` only ever finds, never mints, so the race cannot
785
+ * happen on a document created here.
786
+ *
787
+ * All four, not just the main lane: the secondary lanes fail less loudly (a
788
+ * duplicate pane rather than an unreadable document), but they fail by the same
789
+ * mechanism, and covering only the fatal one would leave three live races.
790
+ *
791
+ * `timeline` is left absent: `unit_time_ms` is a display concern the editor
792
+ * supplies, and total duration is derived on read (RFC 02 §6), so a new document
793
+ * has no timeline fact to state.
794
+ */
795
+ declare function buildInitialVideoDocument(facts: InitialDocumentFacts): VideoDocument;
796
+ //#endregion
768
797
  //#region src/document/validation.d.ts
769
798
  /**
770
799
  * Business-level schema guard for `VideoDocument` (RFC 03 §9). It is the gate
@@ -882,35 +911,611 @@ type TrackDraft = NonNullable<NonNullable<VideoDocumentDraft['tracks']>[number]>
882
911
  /** A single track item in a draft. */
883
912
  type TrackItemDraft = NonNullable<NonNullable<TrackDraft['items']>[number]>;
884
913
  //#endregion
885
- //#region src/editor/id-gen.d.ts
914
+ //#region src/editor/schemas/add-speeches.d.ts
915
+ /**
916
+ * Add speeches (and their captions). TTS runs upstream; the stable speech /
917
+ * caption parts arrive materialized (see `speech-assets.ts`). The op writes the
918
+ * parts and each speech's `{ mode:'anchored', anchorPartId, offsetMs }` fact
919
+ * verbatim — no write-time host-picking, no cascade (RFC 02 §4). The projection
920
+ * derives absolute positions on read.
921
+ */
922
+ declare const addSpeechesInputSchema: z.ZodObject<{
923
+ speeches: z.ZodArray<z.ZodObject<{
924
+ speech_id: z.ZodString;
925
+ anchor_part_id: z.ZodString;
926
+ offset_ms: z.ZodNumber;
927
+ audio_storage_key: z.ZodString;
928
+ duration_ms: z.ZodNumber;
929
+ audio_script: z.ZodString;
930
+ volume: z.ZodNumber;
931
+ voice: z.ZodObject<{
932
+ id: z.ZodString;
933
+ name: z.ZodString;
934
+ }, z.core.$strip>;
935
+ origin_speech_id: z.ZodString;
936
+ caption_ids: z.ZodArray<z.ZodString>;
937
+ }, z.core.$strip>>;
938
+ captions: z.ZodArray<z.ZodObject<{
939
+ caption_id: z.ZodString;
940
+ speech_part_id: z.ZodString;
941
+ text: z.ZodString;
942
+ start_ms: z.ZodNumber;
943
+ duration_ms: z.ZodNumber;
944
+ }, z.core.$strip>>;
945
+ }, z.core.$strip>;
946
+ type AddSpeechesInput = z.infer<typeof addSpeechesInputSchema>;
947
+ //#endregion
948
+ //#region src/editor/schemas/add-video-clips.d.ts
949
+ /**
950
+ * Add video clips to a track. Each clip's duration facts are separated so a
951
+ * single number is never overloaded (RFC 02 / `reference/16` §0b):
952
+ *
953
+ * - `media_duration_ms` is the source media's intrinsic full length (a resource
954
+ * fact, written to the part);
955
+ * - `play_in` / `play_out` are the optional trim window into that media; when
956
+ * omitted the whole media is used (`play_in=0`, `play_out=media_duration_ms`).
957
+ *
958
+ * The clip's effective timeline duration is derived by the projection from the
959
+ * trim window and `speed_shift` — it is never an input here.
960
+ */
961
+ declare const addVideoClipsInputSchema: z.ZodObject<{
962
+ clips: z.ZodArray<z.ZodObject<{
963
+ media_id: z.ZodString;
964
+ start_ms: z.ZodOptional<z.ZodNumber>;
965
+ media_duration_ms: z.ZodNumber;
966
+ play_in: z.ZodOptional<z.ZodNumber>;
967
+ play_out: z.ZodOptional<z.ZodNumber>;
968
+ track_id: z.ZodOptional<z.ZodString>;
969
+ }, z.core.$strip>>;
970
+ before_clip_id: z.ZodOptional<z.ZodString>;
971
+ after_clip_id: z.ZodOptional<z.ZodString>;
972
+ }, z.core.$strip>;
973
+ type AddVideoClipsInput = z.infer<typeof addVideoClipsInputSchema>;
974
+ //#endregion
975
+ //#region src/editor/schemas/adjust-bgm-volume.d.ts
976
+ declare const adjustBgmVolumeInputSchema: z.ZodObject<{
977
+ bgm: z.ZodArray<z.ZodObject<{
978
+ bgm_id: z.ZodString;
979
+ volume: z.ZodNumber;
980
+ }, z.core.$strip>>;
981
+ }, z.core.$strip>;
982
+ type AdjustBgmVolumeInput = z.infer<typeof adjustBgmVolumeInputSchema>;
983
+ //#endregion
984
+ //#region src/editor/schemas/adjust-speech-volume.d.ts
985
+ declare const adjustSpeechVolumeInputSchema: z.ZodObject<{
986
+ speeches: z.ZodArray<z.ZodObject<{
987
+ speech_id: z.ZodString;
988
+ volume: z.ZodNumber;
989
+ }, z.core.$strip>>;
990
+ }, z.core.$strip>;
991
+ type AdjustSpeechVolumeInput = z.infer<typeof adjustSpeechVolumeInputSchema>;
992
+ //#endregion
993
+ //#region src/editor/schemas/adjust-video-clip-duration.d.ts
994
+ /**
995
+ * Re-trim existing video clips (the user-facing "adjust duration" gesture is a
996
+ * trim of the source window). The new `play_in` / `play_out` are the facts; the
997
+ * effective timeline duration is derived from them and the clip's `speed_shift`,
998
+ * and the change reflows downstream clips, speeches, and the timeline inside the
999
+ * op's transaction (no caller-materialized cascade).
1000
+ */
1001
+ declare const adjustVideoClipDurationInputSchema: z.ZodObject<{
1002
+ clips: z.ZodArray<z.ZodObject<{
1003
+ clip_id: z.ZodString;
1004
+ play_in: z.ZodNumber;
1005
+ play_out: z.ZodNumber;
1006
+ }, z.core.$strip>>;
1007
+ }, z.core.$strip>;
1008
+ type AdjustVideoClipDurationInput = z.infer<typeof adjustVideoClipDurationInputSchema>;
1009
+ //#endregion
1010
+ //#region src/editor/schemas/adjust-video-clip-volume.d.ts
1011
+ declare const adjustVideoClipVolumeInputSchema: z.ZodObject<{
1012
+ clips: z.ZodArray<z.ZodObject<{
1013
+ clip_id: z.ZodString;
1014
+ volume: z.ZodNumber;
1015
+ }, z.core.$strip>>;
1016
+ }, z.core.$strip>;
1017
+ type AdjustVideoClipVolumeInput = z.infer<typeof adjustVideoClipVolumeInputSchema>;
1018
+ //#endregion
1019
+ //#region src/editor/schemas/change-speech.d.ts
1020
+ /**
1021
+ * Change a speech's script or voice. Both re-run TTS upstream and return the
1022
+ * regenerated speech / caption parts in the same materialized shape as
1023
+ * `AddSpeeches` (`speech-assets.ts`); the op upserts them by id (the speech part
1024
+ * id is preserved across a re-TTS), re-seats at `start_ms`, and reflows. Old
1025
+ * caption parts no longer owned by the speech are removed via `caption_ids`.
1026
+ */
1027
+ declare const changeSpeechScriptInputSchema: z.ZodObject<{
1028
+ speeches: z.ZodArray<z.ZodObject<{
1029
+ speech_id: z.ZodString;
1030
+ anchor_part_id: z.ZodString;
1031
+ offset_ms: z.ZodNumber;
1032
+ audio_storage_key: z.ZodString;
1033
+ duration_ms: z.ZodNumber;
1034
+ audio_script: z.ZodString;
1035
+ volume: z.ZodNumber;
1036
+ voice: z.ZodObject<{
1037
+ id: z.ZodString;
1038
+ name: z.ZodString;
1039
+ }, z.core.$strip>;
1040
+ origin_speech_id: z.ZodString;
1041
+ caption_ids: z.ZodArray<z.ZodString>;
1042
+ }, z.core.$strip>>;
1043
+ captions: z.ZodArray<z.ZodObject<{
1044
+ caption_id: z.ZodString;
1045
+ speech_part_id: z.ZodString;
1046
+ text: z.ZodString;
1047
+ start_ms: z.ZodNumber;
1048
+ duration_ms: z.ZodNumber;
1049
+ }, z.core.$strip>>;
1050
+ }, z.core.$strip>;
1051
+ declare const changeSpeechVoiceInputSchema: z.ZodObject<{
1052
+ speeches: z.ZodArray<z.ZodObject<{
1053
+ speech_id: z.ZodString;
1054
+ anchor_part_id: z.ZodString;
1055
+ offset_ms: z.ZodNumber;
1056
+ audio_storage_key: z.ZodString;
1057
+ duration_ms: z.ZodNumber;
1058
+ audio_script: z.ZodString;
1059
+ volume: z.ZodNumber;
1060
+ voice: z.ZodObject<{
1061
+ id: z.ZodString;
1062
+ name: z.ZodString;
1063
+ }, z.core.$strip>;
1064
+ origin_speech_id: z.ZodString;
1065
+ caption_ids: z.ZodArray<z.ZodString>;
1066
+ }, z.core.$strip>>;
1067
+ captions: z.ZodArray<z.ZodObject<{
1068
+ caption_id: z.ZodString;
1069
+ speech_part_id: z.ZodString;
1070
+ text: z.ZodString;
1071
+ start_ms: z.ZodNumber;
1072
+ duration_ms: z.ZodNumber;
1073
+ }, z.core.$strip>>;
1074
+ }, z.core.$strip>;
1075
+ type ChangeSpeechScriptInput = z.infer<typeof changeSpeechScriptInputSchema>;
1076
+ type ChangeSpeechVoiceInput = z.infer<typeof changeSpeechVoiceInputSchema>;
1077
+ //#endregion
1078
+ //#region src/editor/schemas/delete-bgm.d.ts
1079
+ /**
1080
+ * Remove the document BGM. Pure document edit: clears the bgm lane and removes
1081
+ * the bgm part. Takes no input (a document holds at most one bgm); an empty
1082
+ * object keeps the op signature uniform with the rest.
1083
+ */
1084
+ declare const deleteBgmInputSchema: z.ZodObject<{}, z.core.$strip>;
1085
+ type DeleteBgmInput = z.infer<typeof deleteBgmInputSchema>;
1086
+ //#endregion
1087
+ //#region src/editor/schemas/delete-speeches.d.ts
1088
+ /**
1089
+ * Delete speeches with their captions. Pure document edit (no side effect): the
1090
+ * op removes each speech part, cascade-deletes the captions it owns (via
1091
+ * `caption_ids` / `speech_part_id`), drops their track items, and reflows.
1092
+ */
1093
+ declare const deleteSpeechesInputSchema: z.ZodObject<{
1094
+ speech_ids: z.ZodArray<z.ZodString>;
1095
+ }, z.core.$strip>;
1096
+ type DeleteSpeechesInput = z.infer<typeof deleteSpeechesInputSchema>;
1097
+ //#endregion
1098
+ //#region src/editor/schemas/delete-video-clips.d.ts
1099
+ /**
1100
+ * How a delete handles the anchored subtree (speeches anchored to a deleted clip,
1101
+ * and their captions) — a delete-op policy, not a data-model field (reference/17
1102
+ * §6). `cascade` (default) removes the subtree; `detach` keeps the direct
1103
+ * anchored children, re-pinning them to `absolute` so they stay on the timeline.
1104
+ */
1105
+ declare const anchoredDeletePolicySchema: z.ZodEnum<{
1106
+ cascade: "cascade";
1107
+ detach: "detach";
1108
+ }>;
1109
+ type AnchoredDeletePolicy = z.infer<typeof anchoredDeletePolicySchema>;
1110
+ declare const deleteVideoClipsInputSchema: z.ZodObject<{
1111
+ clip_ids: z.ZodArray<z.ZodString>;
1112
+ on_anchored: z.ZodOptional<z.ZodEnum<{
1113
+ cascade: "cascade";
1114
+ detach: "detach";
1115
+ }>>;
1116
+ }, z.core.$strip>;
1117
+ type DeleteVideoClipsInput = z.infer<typeof deleteVideoClipsInputSchema>;
1118
+ //#endregion
1119
+ //#region src/editor/schemas/move-speeches.d.ts
1120
+ /**
1121
+ * Move speeches in time. Pure document edit: the op re-seats each speech at its
1122
+ * new absolute `start_ms`; the cascade reassigns it to the host video clip,
1123
+ * resolves overlaps, and reflows. Captions follow their speech.
1124
+ */
1125
+ declare const moveSpeechesInputSchema: z.ZodObject<{
1126
+ speeches: z.ZodArray<z.ZodObject<{
1127
+ speech_id: z.ZodString;
1128
+ new_start_ms: z.ZodNumber;
1129
+ }, z.core.$strip>>;
1130
+ }, z.core.$strip>;
1131
+ type MoveSpeechesInput = z.infer<typeof moveSpeechesInputSchema>;
1132
+ //#endregion
1133
+ //#region src/editor/schemas/move-video-clips-by-anchor.d.ts
1134
+ /**
1135
+ * Where the moved block lands on the main track.
1136
+ *
1137
+ * A discriminated union rather than two optional `before_clip_id` /
1138
+ * `after_clip_id` fields (the shape `addVideoClips` had to use, because there the
1139
+ * two modes share a whole clip description): here the alternatives carry nothing
1140
+ * in common, so making them mutually exclusive *by type* removes three runtime
1141
+ * `superRefine` checks that would otherwise have to be written and tested.
1142
+ *
1143
+ * `track_start` is an explicit member, not the absence of an anchor. The
1144
+ * agent-harness mutation this maps from treats "neither anchor given" as
1145
+ * "move to the front" (`applyBatchMoveVideoClips` falls back to `insertIndex = 0`),
1146
+ * which is a default buried in a tool description. Requiring the caller to name
1147
+ * that intent keeps a forgotten field from silently reordering the timeline.
1148
+ */
1149
+ declare const moveAnchorSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1150
+ position: z.ZodLiteral<"before">;
1151
+ clip_id: z.ZodString;
1152
+ }, z.core.$strip>, z.ZodObject<{
1153
+ position: z.ZodLiteral<"after">;
1154
+ clip_id: z.ZodString;
1155
+ }, z.core.$strip>, z.ZodObject<{
1156
+ position: z.ZodLiteral<"track_start">;
1157
+ }, z.core.$strip>], "position">;
1158
+ /**
1159
+ * What happens to the speeches anchored to the clips being moved.
1160
+ *
1161
+ * - `follow` keeps each speech anchored where it is, so it travels with its clip
1162
+ * to the new position. In the anchored model this is the *no-op* branch: a
1163
+ * speech's authoritative fact is `{ anchorPartId, offsetMs }` and its absolute
1164
+ * time is derived on read from the host's position, so moving the host moves
1165
+ * the speech with no write to the speech at all.
1166
+ * - `keep_absolute` preserves each speech's current absolute landing instead, then
1167
+ * re-anchors it to whichever clip now covers that time (RFC 02 §9.1/§11.1). This
1168
+ * is the branch that costs an extra pass, and the one the FE timeline uses.
1169
+ *
1170
+ * **Required, with no default**, matching `deleteVideoClips` and
1171
+ * `replaceVideoClipSequence`. The two branches decide which picture the user's
1172
+ * narration ends up over, which is too consequential to infer from a missing
1173
+ * field — and neither branch is "safe enough" to be the implicit one.
1174
+ */
1175
+ declare const movedClipAnchoredPolicySchema: z.ZodEnum<{
1176
+ follow: "follow";
1177
+ keep_absolute: "keep_absolute";
1178
+ }>;
1179
+ /**
1180
+ * Reorder a set of main-track clips relative to a reference clip.
1181
+ *
1182
+ * Distinct from `moveVideoClips`, which positions clips by absolute time
1183
+ * (`new_start_ms`) and is what the FE timeline dispatches after a drag. Main-track
1184
+ * clips are `sequential`-positioned, so absolute time is not authoritative state
1185
+ * there (RFC 02 §4/§7): `moveVideoClips` has to *guess* an index back out of the
1186
+ * time it was handed, whereas an anchor already is the ordinal fact being changed.
1187
+ * Keeping them separate also isolates blast radius — this method can diverge on
1188
+ * speech policy without touching the FE path.
1189
+ *
1190
+ * `clip_ids` need not be contiguous. They move as one block, keeping their
1191
+ * relative order, which is the agent-harness `batch_move_video_clips` contract.
1192
+ */
1193
+ declare const moveVideoClipsByAnchorInputSchema: z.ZodObject<{
1194
+ clip_ids: z.ZodArray<z.ZodString>;
1195
+ anchor: z.ZodDiscriminatedUnion<[z.ZodObject<{
1196
+ position: z.ZodLiteral<"before">;
1197
+ clip_id: z.ZodString;
1198
+ }, z.core.$strip>, z.ZodObject<{
1199
+ position: z.ZodLiteral<"after">;
1200
+ clip_id: z.ZodString;
1201
+ }, z.core.$strip>, z.ZodObject<{
1202
+ position: z.ZodLiteral<"track_start">;
1203
+ }, z.core.$strip>], "position">;
1204
+ on_anchored: z.ZodEnum<{
1205
+ follow: "follow";
1206
+ keep_absolute: "keep_absolute";
1207
+ }>;
1208
+ }, z.core.$strip>;
1209
+ type MoveAnchor = z.infer<typeof moveAnchorSchema>;
1210
+ type MovedClipAnchoredPolicy = z.infer<typeof movedClipAnchoredPolicySchema>;
1211
+ type MoveVideoClipsByAnchorInput = z.infer<typeof moveVideoClipsByAnchorInputSchema>;
1212
+ //#endregion
1213
+ //#region src/editor/schemas/move-video-clips.d.ts
1214
+ declare const moveVideoClipsInputSchema: z.ZodObject<{
1215
+ clips: z.ZodArray<z.ZodObject<{
1216
+ clip_id: z.ZodString;
1217
+ new_start_ms: z.ZodNumber;
1218
+ new_track_id: z.ZodOptional<z.ZodString>;
1219
+ }, z.core.$strip>>;
1220
+ }, z.core.$strip>;
1221
+ type MoveVideoClipsInput = z.infer<typeof moveVideoClipsInputSchema>;
1222
+ //#endregion
1223
+ //#region src/editor/schemas/replace-video-clip-content.d.ts
1224
+ /**
1225
+ * Replace the media backing existing video clips. The media import runs upstream
1226
+ * (Director); its stable result — the new media id, intrinsic length, and the
1227
+ * reset trim window — arrives materialized (see
1228
+ * `results/phase-4-side-effect-payload-contract.md` §4). Director resets
1229
+ * `play_in=0` / `play_out=media_duration_ms` and clears `speed_shift` on
1230
+ * replacement. The clip `part_id`s (hence their track items) are unchanged; the
1231
+ * editor reflows the main track from the new effective durations.
1232
+ */
1233
+ declare const replaceVideoClipContentInputSchema: z.ZodObject<{
1234
+ clips: z.ZodArray<z.ZodObject<{
1235
+ clip_id: z.ZodString;
1236
+ origin_media_id: z.ZodString;
1237
+ media_duration_ms: z.ZodNumber;
1238
+ play_in: z.ZodNumber;
1239
+ play_out: z.ZodNumber;
1240
+ volume: z.ZodNumber;
1241
+ }, z.core.$strip>>;
1242
+ }, z.core.$strip>;
1243
+ type ReplaceVideoClipContentInput = z.infer<typeof replaceVideoClipContentInputSchema>;
1244
+ //#endregion
1245
+ //#region src/editor/schemas/replace-video-clip-sequence.d.ts
1246
+ /**
1247
+ * What happens to the speeches anchored to the clips being replaced.
1248
+ *
1249
+ * - `remap` re-anchors each surviving speech to the new clip in the SAME POSITION
1250
+ * of the sequence, keeping its offset — old[i]'s children become new[i]'s
1251
+ * children. An old clip with no counterpart (fewer new clips than old) has its
1252
+ * subtree deleted, because there is nothing left to anchor to.
1253
+ * - `cascade` deletes every anchored speech (and its captions) outright, like
1254
+ * `deleteVideoClips`.
1255
+ *
1256
+ * **Required, with no default.** The two branches differ in whether the user's
1257
+ * narration survives, and the agent tool that drives this op makes its
1258
+ * `preserve_speeches` flag required for that reason. A default here would let a
1259
+ * caller that forgot the field silently delete speech.
1260
+ */
1261
+ declare const anchoredReplacePolicySchema: z.ZodEnum<{
1262
+ cascade: "cascade";
1263
+ remap: "remap";
1264
+ }>;
1265
+ type AnchoredReplacePolicy = z.infer<typeof anchoredReplacePolicySchema>;
886
1266
  /**
887
- * Part-id generation, aligned with the online ecosystem.
1267
+ * Replace a contiguous run of main-track clips with a new run.
888
1268
  *
889
- * The authoritative online producers agent-harness (`@harness/shared`
890
- * `genObjId`) and director.v2 (`common/obj_id.py` `gen_obj_id`) both mint part
891
- * ids as `` `${prefix}_${ulid()}` ``, and real captured drafts use exactly that
892
- * shape (`clip_…` / `spe_…` / `cap_…` / `bgm_…`, each a 26-char ULID). The engine
893
- * previously emitted `vc_<base36 timestamp><6 random>`, a different prefix AND a
894
- * different encoding — the sole cross-repo id divergence. This module removes it
895
- * by emitting the same `<prefix>_<ULID>` bytes.
1269
+ * A composite of delete + insert that cannot be expressed as the two ops in
1270
+ * sequence, because the anchored speeches have to survive *across* the swap: with
1271
+ * `remap` they are re-anchored positionally, which needs both the old and the new
1272
+ * ids in the same transaction (ADR 0009 the cascade stays in the editor, callers
1273
+ * never re-wire anchors themselves).
1274
+ *
1275
+ * Duration facts follow `addVideoClips`: `media_duration_ms` is the source's
1276
+ * intrinsic length and the trim window defaults to the whole media. The effective
1277
+ * timeline duration is derived by the projection, never an input.
1278
+ *
1279
+ * `media_id` is optional: omitting it creates a **deliberate empty placeholder
1280
+ * clip** (`origin_media_id: ''`) — structure with no picture. This is the only op
1281
+ * that can produce one, and it is authoritative state, unlike the gap fillers the
1282
+ * read-side solve mints (which never enter the document).
1283
+ */
1284
+ declare const replaceVideoClipSequenceInputSchema: z.ZodObject<{
1285
+ old_clip_ids: z.ZodArray<z.ZodString>;
1286
+ new_clips: z.ZodArray<z.ZodObject<{
1287
+ media_id: z.ZodOptional<z.ZodString>;
1288
+ media_duration_ms: z.ZodNumber;
1289
+ play_in: z.ZodOptional<z.ZodNumber>;
1290
+ play_out: z.ZodOptional<z.ZodNumber>;
1291
+ }, z.core.$strip>>;
1292
+ on_anchored: z.ZodEnum<{
1293
+ cascade: "cascade";
1294
+ remap: "remap";
1295
+ }>;
1296
+ }, z.core.$strip>;
1297
+ type ReplaceVideoClipSequenceInput = z.infer<typeof replaceVideoClipSequenceInputSchema>;
1298
+ //#endregion
1299
+ //#region src/editor/schemas/set-bgm.d.ts
1300
+ /**
1301
+ * Set the document BGM. The media's stable result (storage key) arrives
1302
+ * materialized from upstream (see
1303
+ * `results/phase-4-side-effect-payload-contract.md` §3). The op upserts the bgm
1304
+ * part and seats it on the bgm lane; its effective length is always the whole
1305
+ * timeline, derived by the projection on read — so there is no `duration_ms`
1306
+ * input or fact (RFC 02 / `reference/16` §0b). A `bgm_id` lets the op replace an
1307
+ * existing bgm part by id.
1308
+ */
1309
+ declare const setBgmInputSchema: z.ZodObject<{
1310
+ bgm_id: z.ZodString;
1311
+ audio_storage_key: z.ZodString;
1312
+ origin_media_id: z.ZodString;
1313
+ volume: z.ZodNumber;
1314
+ }, z.core.$strip>;
1315
+ type SetBgmInput = z.infer<typeof setBgmInputSchema>;
1316
+ //#endregion
1317
+ //#region src/editor/schemas/set-caption-style.d.ts
1318
+ /**
1319
+ * Set the caption visual style. GLOBAL by design: the style applies to every
1320
+ * caption part in the document — it carries NO `caption_id`. This mirrors the FE,
1321
+ * whose caption-style store (`caption-style.ts:persistCaptionStylePatch`) iterates
1322
+ * ALL captions and writes the same normalized style to each; the product has a
1323
+ * single document-wide caption style, not per-caption styling.
1324
+ *
1325
+ * Every field is optional and maps to a `CaptionStyle` attribute (snake_case
1326
+ * IDL). A field present in the input is written to every caption; a field ABSENT
1327
+ * from the input is left untouched on each caption (the editor merges the patch
1328
+ * onto each caption's existing style — this is a value edit, not a full-style
1329
+ * replace, so a partial patch such as "recolor only" does not wipe font size).
1330
+ *
1331
+ * Pure document edit, no cascade — captions keep their positions; only the style
1332
+ * sub-map of each caption part changes.
1333
+ */
1334
+ declare const setCaptionStyleInputSchema: z.ZodObject<{
1335
+ font_id: z.ZodOptional<z.ZodString>;
1336
+ font_size: z.ZodOptional<z.ZodNumber>;
1337
+ font_color: z.ZodOptional<z.ZodString>;
1338
+ font_weight: z.ZodOptional<z.ZodNumber>;
1339
+ entrance_animation: z.ZodOptional<z.ZodString>;
1340
+ entrance_animation_duration_ms: z.ZodOptional<z.ZodNumber>;
1341
+ stroke_color: z.ZodOptional<z.ZodString>;
1342
+ stroke_width: z.ZodOptional<z.ZodNumber>;
1343
+ position_x: z.ZodOptional<z.ZodNumber>;
1344
+ position_y: z.ZodOptional<z.ZodNumber>;
1345
+ }, z.core.$strip>;
1346
+ type SetCaptionStyleInput = z.infer<typeof setCaptionStyleInputSchema>;
1347
+ //#endregion
1348
+ //#region src/editor/schemas/set-caption-visibility.d.ts
1349
+ /**
1350
+ * Toggle caption visibility (the caption track's `is_hidden` flag). Pure
1351
+ * document edit, no cascade — captions keep their positions; only the lane's
1352
+ * hidden flag changes.
1353
+ */
1354
+ declare const setCaptionVisibilityInputSchema: z.ZodObject<{
1355
+ is_hidden: z.ZodBoolean;
1356
+ }, z.core.$strip>;
1357
+ type SetCaptionVisibilityInput = z.infer<typeof setCaptionVisibilityInputSchema>;
1358
+ //#endregion
1359
+ //#region src/editor/schemas/set-video-clip-speed-shift.d.ts
1360
+ /**
1361
+ * Set the playback speed of existing video clips. Per the speed-shift decision
1362
+ * (`reference/16` §0): the op writes only the `speed_shift` fact — it does NOT
1363
+ * store an effective `duration_ms` (projection derives it from the trim window /
1364
+ * speed) and does NOT scale anchored speeches' relative offsets (offsets stay
1365
+ * put; the cascade reflows absolute positions). A `null` speed_shift clears the
1366
+ * speed back to original (1×).
1367
+ */
1368
+ declare const setVideoClipSpeedShiftInputSchema: z.ZodObject<{
1369
+ clips: z.ZodArray<z.ZodObject<{
1370
+ clip_id: z.ZodString;
1371
+ speed_shift: z.ZodNullable<z.ZodObject<{
1372
+ category: z.ZodEnum<{
1373
+ curve: "curve";
1374
+ linear: "linear";
1375
+ }>;
1376
+ mode: z.ZodString;
1377
+ config: z.ZodUnion<readonly [z.ZodObject<{
1378
+ linear: z.ZodObject<{
1379
+ speed: z.ZodNumber;
1380
+ }, z.core.$strip>;
1381
+ }, z.core.$strip>, z.ZodObject<{
1382
+ curve: z.ZodObject<{
1383
+ keyframes: z.ZodArray<z.ZodObject<{
1384
+ position: z.ZodNumber;
1385
+ rate: z.ZodNumber;
1386
+ in_tangent: z.ZodOptional<z.ZodObject<{
1387
+ x: z.ZodNumber;
1388
+ y: z.ZodNumber;
1389
+ }, z.core.$strip>>;
1390
+ out_tangent: z.ZodOptional<z.ZodObject<{
1391
+ x: z.ZodNumber;
1392
+ y: z.ZodNumber;
1393
+ }, z.core.$strip>>;
1394
+ }, z.core.$strip>>;
1395
+ }, z.core.$strip>;
1396
+ }, z.core.$strip>]>;
1397
+ }, z.core.$strip>>;
1398
+ }, z.core.$strip>>;
1399
+ }, z.core.$strip>;
1400
+ type SetVideoClipSpeedShiftInput = z.infer<typeof setVideoClipSpeedShiftInputSchema>;
1401
+ //#endregion
1402
+ //#region src/editor/schemas/shared.d.ts
1403
+ declare const clipIdSchema: z.ZodString;
1404
+ declare const clipIdsSchema: z.ZodArray<z.ZodString>;
1405
+ declare const mediaIdSchema: z.ZodString;
1406
+ declare const speechIdSchema: z.ZodString;
1407
+ declare const timelineMsSchema: z.ZodNumber;
1408
+ declare const positiveMsSchema: z.ZodNumber;
1409
+ declare const volumeSchema: z.ZodNumber;
1410
+ declare const speechIdsSchema: z.ZodArray<z.ZodString>;
1411
+ /**
1412
+ * A clip's playback-speed fact, the only thing `SetVideoClipSpeedShift` writes.
1413
+ * Mirrors the IDL `SpeedShift`: `category` is `linear` | `curve`, and `config`
1414
+ * is a discriminated union — `{ linear: { speed } }` for a constant multiplier
1415
+ * (the multiplier projection reads at `config.linear.speed`) or `{ curve: {
1416
+ * keyframes } }` for a Bezier-controlled variable speed (RFC 02 / `reference/16`
1417
+ * §0). Exactly one of `linear` / `curve` is present.
1418
+ */
1419
+ declare const speedShiftSchema: z.ZodObject<{
1420
+ category: z.ZodEnum<{
1421
+ curve: "curve";
1422
+ linear: "linear";
1423
+ }>;
1424
+ mode: z.ZodString;
1425
+ config: z.ZodUnion<readonly [z.ZodObject<{
1426
+ linear: z.ZodObject<{
1427
+ speed: z.ZodNumber;
1428
+ }, z.core.$strip>;
1429
+ }, z.core.$strip>, z.ZodObject<{
1430
+ curve: z.ZodObject<{
1431
+ keyframes: z.ZodArray<z.ZodObject<{
1432
+ position: z.ZodNumber;
1433
+ rate: z.ZodNumber;
1434
+ in_tangent: z.ZodOptional<z.ZodObject<{
1435
+ x: z.ZodNumber;
1436
+ y: z.ZodNumber;
1437
+ }, z.core.$strip>>;
1438
+ out_tangent: z.ZodOptional<z.ZodObject<{
1439
+ x: z.ZodNumber;
1440
+ y: z.ZodNumber;
1441
+ }, z.core.$strip>>;
1442
+ }, z.core.$strip>>;
1443
+ }, z.core.$strip>;
1444
+ }, z.core.$strip>]>;
1445
+ }, z.core.$strip>;
1446
+ declare const voiceSchema: z.ZodObject<{
1447
+ id: z.ZodString;
1448
+ name: z.ZodString;
1449
+ }, z.core.$strip>;
1450
+ //#endregion
1451
+ //#region src/editor/schemas/speech-assets.d.ts
1452
+ /**
1453
+ * The materialized TTS result shared by `AddSpeeches` / `ChangeSpeechScript` /
1454
+ * `ChangeSpeechVoice` (see `results/phase-4-side-effect-payload-contract.md`
1455
+ * §1/§2). The side effect (TTS/ASR + billing) runs upstream; the op receives the
1456
+ * stable speech + caption parts and writes them as authoritative facts. No
1457
+ * cascade runs on write — the projection derives absolute positions on read.
896
1458
  *
897
- * The ULID is generated inline (Crockford Base32, 48-bit time + 80-bit random)
898
- * rather than pulling the `ulid` npm package: the randomness class matches the
899
- * old generator (both `Math.random`-based) and it keeps `@mengine/medeo-client`
900
- * dependency-free for a purely mechanical id string. Part ids only need to be
901
- * unique and lexicographically time-sortable, which this satisfies.
902
- */
903
- /**
904
- * Online part-id semantic prefixes. `clip` (video clip) is the only value the
905
- * engine currently mints (see `addVideoClips`); the rest are declared so the
906
- * type documents the shared vocabulary and guards against reintroducing the old
907
- * `vc`/`sp`/`cp`/`bg` names. Speech/caption/bgm ids arrive pre-minted in op
908
- * payloads, so the engine never generates them itself.
909
- */
910
- type PartIdPrefix = 'clip' | 'spe' | 'cap' | 'bgm' | 'ti';
911
- declare function generatePartId(prefix: PartIdPrefix): string;
912
- /** Injectable part-id mint; defaults to `generatePartId` on `SemanticEditor`. */
913
- type PartIdFactory = (prefix: PartIdPrefix) => string;
1459
+ * Each speech carries the anchoring fact directly (RFC 02 §4): the host video
1460
+ * clip `anchor_part_id` and the `offset_ms` within it. The upstream caller
1461
+ * already knows which clip a speech attaches to, so the op writes
1462
+ * `{ mode:'anchored', anchorPartId, offsetMs }` verbatim no write-time
1463
+ * host-picking. Captions anchor to their speech via the caption part's
1464
+ * `start_ms` (offset within the speech).
1465
+ */
1466
+ declare const speechAssetSchema: z.ZodObject<{
1467
+ speech_id: z.ZodString;
1468
+ anchor_part_id: z.ZodString;
1469
+ offset_ms: z.ZodNumber;
1470
+ audio_storage_key: z.ZodString;
1471
+ duration_ms: z.ZodNumber;
1472
+ audio_script: z.ZodString;
1473
+ volume: z.ZodNumber;
1474
+ voice: z.ZodObject<{
1475
+ id: z.ZodString;
1476
+ name: z.ZodString;
1477
+ }, z.core.$strip>;
1478
+ origin_speech_id: z.ZodString;
1479
+ caption_ids: z.ZodArray<z.ZodString>;
1480
+ }, z.core.$strip>;
1481
+ declare const captionAssetSchema: z.ZodObject<{
1482
+ caption_id: z.ZodString;
1483
+ speech_part_id: z.ZodString;
1484
+ text: z.ZodString;
1485
+ start_ms: z.ZodNumber;
1486
+ duration_ms: z.ZodNumber;
1487
+ }, z.core.$strip>;
1488
+ /** A materialized speech-subtree write (speeches + their captions). */
1489
+ declare const speechAssetsSchema: z.ZodObject<{
1490
+ speeches: z.ZodArray<z.ZodObject<{
1491
+ speech_id: z.ZodString;
1492
+ anchor_part_id: z.ZodString;
1493
+ offset_ms: z.ZodNumber;
1494
+ audio_storage_key: z.ZodString;
1495
+ duration_ms: z.ZodNumber;
1496
+ audio_script: z.ZodString;
1497
+ volume: z.ZodNumber;
1498
+ voice: z.ZodObject<{
1499
+ id: z.ZodString;
1500
+ name: z.ZodString;
1501
+ }, z.core.$strip>;
1502
+ origin_speech_id: z.ZodString;
1503
+ caption_ids: z.ZodArray<z.ZodString>;
1504
+ }, z.core.$strip>>;
1505
+ captions: z.ZodArray<z.ZodObject<{
1506
+ caption_id: z.ZodString;
1507
+ speech_part_id: z.ZodString;
1508
+ text: z.ZodString;
1509
+ start_ms: z.ZodNumber;
1510
+ duration_ms: z.ZodNumber;
1511
+ }, z.core.$strip>>;
1512
+ }, z.core.$strip>;
1513
+ type SpeechAsset = z.infer<typeof speechAssetSchema>;
1514
+ type CaptionAsset = z.infer<typeof captionAssetSchema>;
1515
+ type SpeechAssets = z.infer<typeof speechAssetsSchema>;
1516
+ declare namespace index_d_exports {
1517
+ export { AddSpeechesInput, AddVideoClipsInput, AdjustBgmVolumeInput, AdjustSpeechVolumeInput, AdjustVideoClipDurationInput, AdjustVideoClipVolumeInput, AnchoredDeletePolicy, AnchoredReplacePolicy, CaptionAsset, ChangeSpeechScriptInput, ChangeSpeechVoiceInput, DeleteBgmInput, DeleteSpeechesInput, DeleteVideoClipsInput, MoveAnchor, MoveSpeechesInput, MoveVideoClipsByAnchorInput, MoveVideoClipsInput, MovedClipAnchoredPolicy, ReplaceVideoClipContentInput, ReplaceVideoClipSequenceInput, SetBgmInput, SetCaptionStyleInput, SetCaptionVisibilityInput, SetVideoClipSpeedShiftInput, SpeechAsset, SpeechAssets, addSpeechesInputSchema, addVideoClipsInputSchema, adjustBgmVolumeInputSchema, adjustSpeechVolumeInputSchema, adjustVideoClipDurationInputSchema, adjustVideoClipVolumeInputSchema, anchoredDeletePolicySchema, anchoredReplacePolicySchema, changeSpeechScriptInputSchema, changeSpeechVoiceInputSchema, clipIdSchema, clipIdsSchema, deleteBgmInputSchema, deleteSpeechesInputSchema, deleteVideoClipsInputSchema, mediaIdSchema, moveAnchorSchema, moveSpeechesInputSchema, moveVideoClipsByAnchorInputSchema, moveVideoClipsInputSchema, movedClipAnchoredPolicySchema, positiveMsSchema, replaceVideoClipContentInputSchema, replaceVideoClipSequenceInputSchema, setBgmInputSchema, setCaptionStyleInputSchema, setCaptionVisibilityInputSchema, setVideoClipSpeedShiftInputSchema, speechAssetsSchema, speechIdSchema, speechIdsSchema, speedShiftSchema, timelineMsSchema, voiceSchema, volumeSchema };
1518
+ }
914
1519
  //#endregion
915
1520
  //#region src/editor/schema-validator.d.ts
916
1521
  interface SnapshotReadable {
@@ -923,11 +1528,33 @@ declare class ValidationError extends Error {
923
1528
  }
924
1529
  declare class SchemaValidator {
925
1530
  validateMoveVideoClips(input: MoveVideoClipsInput, doc: SnapshotReadable): void;
1531
+ /**
1532
+ * The anchor must not be one of the clips being moved. Unlike the schema's
1533
+ * mutual exclusions this needs the input read as a whole, so it stays here.
1534
+ *
1535
+ * Rejected rather than normalized: "put this block before itself" has no
1536
+ * defensible outcome — treating it as a no-op hides a caller bug behind a
1537
+ * success, and picking any surviving neighbour invents an intent. The legacy
1538
+ * `applyBatchMoveVideoClips` omits this check (a self-anchor there silently
1539
+ * lands the block at its own pre-move index); director's original move tool
1540
+ * did enforce it (`_validate_position`: "不是被移动的clips"), and that is the
1541
+ * behaviour worth keeping.
1542
+ */
1543
+ validateMoveVideoClipsByAnchor(input: MoveVideoClipsByAnchorInput, doc: SnapshotReadable): void;
926
1544
  validateDeleteVideoClips(input: DeleteVideoClipsInput, doc: SnapshotReadable): void;
927
1545
  validateAddVideoClips(input: AddVideoClipsInput, doc: SnapshotReadable): void;
928
1546
  validateAdjustVideoClipVolume(input: AdjustVideoClipVolumeInput, doc: SnapshotReadable): void;
929
1547
  validateSetVideoClipSpeedShift(input: SetVideoClipSpeedShiftInput, doc: SnapshotReadable): void;
930
1548
  validateReplaceVideoClipContent(input: ReplaceVideoClipContentInput, doc: SnapshotReadable): void;
1549
+ /**
1550
+ * `old_clip_ids` must name a **contiguous run of the main track, in track
1551
+ * order**. A sparse or reordered selection is rejected rather than normalized:
1552
+ * a sparse selection has no single stretch to swap, so the insert index, the
1553
+ * positional speech remap, and the resulting order would each need a different
1554
+ * arbitrary choice. Mirrors the agent tool's own contract ("must be a
1555
+ * contiguous main-track sequence listed in timeline order").
1556
+ */
1557
+ validateReplaceVideoClipSequence(input: ReplaceVideoClipSequenceInput, doc: SnapshotReadable): void;
931
1558
  validateAdjustVideoClipDuration(input: AdjustVideoClipDurationInput, doc: SnapshotReadable): void;
932
1559
  validateAdjustSpeechVolume(input: AdjustSpeechVolumeInput, doc: SnapshotReadable): void;
933
1560
  validateAdjustBgmVolume(input: AdjustBgmVolumeInput, doc: SnapshotReadable): void;
@@ -987,7 +1614,7 @@ declare class SchemaValidator {
987
1614
  * side-effect payload contract in
988
1615
  * `docs/projects/medeo-integration/results/phase-4-side-effect-payload-contract.md`.
989
1616
  */
990
- type ImplementedSemanticOpKind = 'MoveVideoClips' | 'DeleteVideoClips' | 'AddVideoClips' | 'AdjustVideoClipVolume' | 'SetVideoClipSpeedShift' | 'ReplaceVideoClipContent' | 'AdjustVideoClipDuration' | 'AddSpeeches' | 'DeleteSpeeches' | 'MoveSpeeches' | 'ChangeSpeechScript' | 'ChangeSpeechVoice' | 'AdjustSpeechVolume' | 'SetCaptionVisibility' | 'SetCaptionStyle' | 'SetBgm' | 'DeleteBgm' | 'AdjustBgmVolume';
1617
+ type ImplementedSemanticOpKind = 'MoveVideoClips' | 'MoveVideoClipsByAnchor' | 'DeleteVideoClips' | 'AddVideoClips' | 'AdjustVideoClipVolume' | 'SetVideoClipSpeedShift' | 'ReplaceVideoClipContent' | 'ReplaceVideoClipSequence' | 'AdjustVideoClipDuration' | 'AddSpeeches' | 'DeleteSpeeches' | 'MoveSpeeches' | 'ChangeSpeechScript' | 'ChangeSpeechVoice' | 'AdjustSpeechVolume' | 'SetCaptionVisibility' | 'SetCaptionStyle' | 'SetBgm' | 'DeleteBgm' | 'AdjustBgmVolume';
991
1618
  /**
992
1619
  * Operations on the roadmap but NOT yet implemented by `SemanticEditor`. Empty:
993
1620
  * Phase 4 covers every op with a real entry point. Kept as a named type so the
@@ -1007,24 +1634,56 @@ declare const IMPLEMENTED_SEMANTIC_OP_KINDS: readonly ImplementedSemanticOpKind[
1007
1634
  declare function isImplementedSemanticOpKind(kind: string): kind is ImplementedSemanticOpKind;
1008
1635
  //#endregion
1009
1636
  //#region src/editor/semantic-editor.d.ts
1637
+ /**
1638
+ * Who authored an op, recorded in the commit message alongside `semantic_op`.
1639
+ *
1640
+ * Rides the Loro commit message rather than a wire field or a storage column,
1641
+ * for the same reason the semantic metadata does (`rfc/05 §5`): the message
1642
+ * travels inside the update bytes, so attribution cannot disagree with the ops
1643
+ * it describes, and it survives log truncation — a normal snapshot carries the
1644
+ * full history, so folding and deleting update rows keeps every Change's message
1645
+ * readable. A per-row column would lose the attribution at the first re-snapshot
1646
+ * and could only record one author per push, while one push may carry several
1647
+ * Changes.
1648
+ *
1649
+ * `role` deliberately mirrors the legacy `video_draft_op_records.operator_role`
1650
+ * enum (`'user' | 'agent'`) so the two engines answer "was this a person or the
1651
+ * agent" with the same vocabulary. It is declared rather than inferred from the
1652
+ * Loro peer id: peer ranges separate the writers today, but that is an audit aid
1653
+ * by convention, not an enforced fact, and peer ids are deliberately NOT derived
1654
+ * from identity (`PeerId != userid`, adr/0012).
1655
+ *
1656
+ * A deliberately narrow shape: no client/platform field. That is telemetry, and
1657
+ * document history is permanent and undeletable — the wrong place for it. Adding
1658
+ * one later costs nothing (the message is JSON; older Changes simply lack the
1659
+ * key), so the omission does not close the door.
1660
+ *
1661
+ * **Self-reported.** The client writes this and no server verifies it. The
1662
+ * authorization principal is the `medeo-user-id` header, which mengine-server
1663
+ * owner-checks; this field is the author, which it does not. The two hold the
1664
+ * same value today but answer different questions — see the harness's mengine
1665
+ * repository for why they are passed separately.
1666
+ */
1667
+ interface OpActor {
1668
+ user_id: string;
1669
+ role: 'user' | 'agent';
1670
+ }
1010
1671
  interface CommitOptions {
1011
- /**
1012
- * Caller-supplied intent attached to the op's audit message.
1013
- *
1014
- * Typed as `unknown` to match `TransactAudit.intent`. The wire (real
1015
- * mengine-server and the in-memory test double) only surfaces **string**
1016
- * intents onto the audit log — non-string values stay in the Loro commit
1017
- * message but parse to `null`. Agent callers should pass a string.
1018
- */
1019
- intent?: unknown;
1672
+ intent?: {
1673
+ kind: string;
1674
+ payload: unknown;
1675
+ };
1676
+ /** Author of this op; omitted for writes with no acting user (bootstrap, repair). */
1677
+ actor?: OpActor;
1020
1678
  }
1021
- type SemanticOpInput = MoveVideoClipsInput | DeleteVideoClipsInput | AddVideoClipsInput | AdjustVideoClipVolumeInput | AdjustSpeechVolumeInput | AdjustBgmVolumeInput | SetVideoClipSpeedShiftInput | ReplaceVideoClipContentInput | AdjustVideoClipDurationInput | AddSpeechesInput | DeleteSpeechesInput | MoveSpeechesInput | SetCaptionVisibilityInput | SetCaptionStyleInput | SetBgmInput | DeleteBgmInput;
1679
+ type SemanticOpInput = MoveVideoClipsInput | MoveVideoClipsByAnchorInput | DeleteVideoClipsInput | AddVideoClipsInput | AdjustVideoClipVolumeInput | AdjustSpeechVolumeInput | AdjustBgmVolumeInput | SetVideoClipSpeedShiftInput | ReplaceVideoClipContentInput | ReplaceVideoClipSequenceInput | AdjustVideoClipDurationInput | AddSpeechesInput | DeleteSpeechesInput | MoveSpeechesInput | SetCaptionVisibilityInput | SetCaptionStyleInput | SetBgmInput | DeleteBgmInput;
1022
1680
  type SemanticOpName = ImplementedSemanticOpKind;
1023
1681
  /** Audit metadata committed alongside an op's writes. */
1024
1682
  interface TransactAudit {
1025
1683
  kind: SemanticOpName;
1026
1684
  payload: unknown;
1027
1685
  intent?: unknown;
1686
+ actor?: OpActor;
1028
1687
  }
1029
1688
  /**
1030
1689
  * Narrow write surface the `SemanticEditor` drives (ADR 0008 / 0009). It stays
@@ -1058,11 +1717,46 @@ interface SemanticDocumentAdapter extends SnapshotReadable {
1058
1717
  declare class SemanticEditor {
1059
1718
  private readonly doc;
1060
1719
  private readonly validator;
1061
- private readonly idFactory;
1062
- constructor(doc: SemanticDocumentAdapter, validator?: SchemaValidator, idFactory?: PartIdFactory);
1720
+ constructor(doc: SemanticDocumentAdapter, validator?: SchemaValidator);
1063
1721
  moveVideoClips(input: MoveVideoClipsInput, options?: CommitOptions): Promise<void>;
1722
+ /**
1723
+ * Reorder main-track clips relative to an anchor clip, moving them as one block.
1724
+ *
1725
+ * The ordinal sibling of `moveVideoClips`: main-track clips are
1726
+ * `sequential`-positioned, so their order is the authoritative fact and absolute
1727
+ * time is derived on read (RFC 02 §4/§7). A caller that already knows "put these
1728
+ * after that one" should say so, instead of computing a timeline offset that this
1729
+ * editor would only have to resolve back into an index.
1730
+ *
1731
+ * `on_anchored` decides the speech treatment and is required. Note the asymmetry
1732
+ * in cost: `follow` writes nothing to the speeches (their `{ anchorPartId,
1733
+ * offsetMs }` facts stay valid and the derived absolute time moves with the host),
1734
+ * while `keep_absolute` runs the extra re-parent pass that preserves each
1735
+ * speech's absolute landing. `moveVideoClips` is permanently `keep_absolute`,
1736
+ * matching the FE timeline it serves.
1737
+ */
1738
+ moveVideoClipsByAnchor(input: MoveVideoClipsByAnchorInput, options?: CommitOptions): Promise<void>;
1064
1739
  deleteVideoClips(input: DeleteVideoClipsInput, options?: CommitOptions): Promise<void>;
1065
1740
  addVideoClips(input: AddVideoClipsInput, options?: CommitOptions): Promise<void>;
1741
+ /**
1742
+ * Swap a contiguous run of main-track clips for a new run, in one transaction.
1743
+ *
1744
+ * Composite by necessity, not convenience: with `on_anchored: 'remap'` each
1745
+ * surviving speech is re-anchored to the new clip in the **same position of the
1746
+ * sequence** (old[i] → new[i]), which needs both id sets live at once. Splitting
1747
+ * it into `deleteVideoClips` + `addVideoClips` would leave the caller holding the
1748
+ * anchor re-wiring — the cascade-in-the-caller mistake ADR 0009 retires.
1749
+ *
1750
+ * Positional pairing, not by count: when there are fewer new clips than old, the
1751
+ * unmatched old clips have no counterpart, so their anchored subtrees are deleted
1752
+ * (there is nothing to anchor to). When there are more new clips than old, the
1753
+ * extra ones simply arrive with no children.
1754
+ *
1755
+ * The new clips are inserted where the run started, so surrounding order is
1756
+ * preserved. Every position is derived on read (RFC 02 §7) — this writes only the
1757
+ * facts: track order, the trim windows, and the surviving anchors.
1758
+ */
1759
+ replaceVideoClipSequence(input: ReplaceVideoClipSequenceInput, options?: CommitOptions): Promise<void>;
1066
1760
  adjustVideoClipVolume(input: AdjustVideoClipVolumeInput, options?: CommitOptions): Promise<void>;
1067
1761
  setVideoClipSpeedShift(input: SetVideoClipSpeedShiftInput, options?: CommitOptions): Promise<void>;
1068
1762
  adjustSpeechVolume(input: AdjustSpeechVolumeInput, options?: CommitOptions): Promise<void>;
@@ -1105,12 +1799,23 @@ declare class SemanticEditor {
1105
1799
  /**
1106
1800
  * Set the document BGM. The media's stable result arrives materialized.
1107
1801
  *
1108
- * KNOWN GAP (non-blocking): when the BGM comes from the public library, the
1109
- * Director must also register project-level stock media ownership. That is a
1110
- * separate "register-only, no draft write" side effect Director does not yet
1111
- * expose; until it does, a public-library BGM set here will not auto-appear in
1112
- * the project media library. The document edit itself is complete and correct.
1113
- * See `docs/projects/medeo-integration/results/phase-4-op-side-effect-classification.md`.
1802
+ * KNOWN GAP (non-blocking, FE callers only): a public-library BGM also needs
1803
+ * project-level media ownership registered, or it plays but never appears in
1804
+ * the project's media library. This editor deliberately does not do it — the
1805
+ * document edit is complete and correct, and ownership is a side effect owned
1806
+ * by whoever holds the authoritative media data (memota), not by a CRDT write.
1807
+ *
1808
+ * Who is affected, as of 2026-08-13: the agent harness registers it on BOTH
1809
+ * its legacy and mengine paths (shared `resolveMutation` calls memota
1810
+ * `attachMedia` directly), and FE's legacy REST path gets it from Director.
1811
+ * Only FE's mengine path is missing it. FE cannot call memota directly:
1812
+ * `attachMedia` is exposed on memota's internal contract only, so the fix is a
1813
+ * Director proxy route — NOT a new Director business endpoint, since Director
1814
+ * stopped owning media ownership entirely (`a2951355`, 2026-08-05).
1815
+ *
1816
+ * Tracked as a Phase 7 gate (it must land before "new documents default to
1817
+ * mengine" makes public-library BGM a routine operation). See
1818
+ * `docs/projects/medeo-integration/results/phase-6-m4-legacy-refresh-isolation.md`.
1114
1819
  */
1115
1820
  setBgm(input: SetBgmInput, options?: CommitOptions): Promise<void>;
1116
1821
  /** Remove the document BGM; clears the bgm lane and removes the part. */
@@ -1183,57 +1888,6 @@ declare class MirrorVideoDocumentAdapter implements SemanticDocumentAdapter {
1183
1888
  declare function createMirrorVideoDocument(document: VideoDocument, options?: MirrorVideoDocumentOptions): LoroDoc;
1184
1889
  /** Build a `MirrorVideoDocumentAdapter` over a fresh doc seeded with `document`. */
1185
1890
  declare function createMirrorVideoDocumentAdapter(document: VideoDocument, options?: MirrorVideoDocumentOptions): MirrorVideoDocumentAdapter;
1186
- /** Write a whole `VideoDocument` into a draft (seed a fresh doc / plain-memory state). */
1187
- declare function writeVideoDocumentToDraft(draft: VideoDocumentDraft, document: VideoDocument): void;
1188
- //#endregion
1189
- //#region src/document/plain-memory-adapter.d.ts
1190
- /**
1191
- * A `TransactAudit` widened with the ordered ids minted during that transact.
1192
- * `generated_ids` is empty when the op never called the id factory.
1193
- */
1194
- interface JournalEntry extends TransactAudit {
1195
- /** Ids produced by the adapter's id factory during this transact, in mint order. */
1196
- generated_ids: string[];
1197
- }
1198
- interface PlainMemoryAdapterOptions {
1199
- /** Underlying id mint; wrapped so each call inside a transact is journaled. */
1200
- idFactory?: PartIdFactory;
1201
- }
1202
- /**
1203
- * Pure in-memory `SemanticDocumentAdapter` — no Loro/WASM. Holds a
1204
- * `VideoDocumentDraft` object and applies each `transact` via immer `produce`,
1205
- * journaling every audit that actually mutated state (plus any ids minted
1206
- * during that transact).
1207
- *
1208
- * Known benign difference vs `MirrorVideoDocumentAdapter`: the editor's
1209
- * `setPart` assigns a fresh part object on every call, so a same-value rewrite
1210
- * produces a new immer state and IS journaled here, while the mirror's deep
1211
- * diff emits no commit. Terminal `snapshot()` stays equal; replay is idempotent.
1212
- */
1213
- declare class PlainMemoryAdapter implements SemanticDocumentAdapter {
1214
- private state;
1215
- private readonly _journal;
1216
- private readonly baseIdFactory;
1217
- /** Non-null only while a `transact` edit callback is running. */
1218
- private pendingIds;
1219
- /**
1220
- * Recording wrapper around the underlying factory. Callers (sandbox editor)
1221
- * use this so every minted id is appended to the current transact's list.
1222
- */
1223
- readonly idFactory: PartIdFactory;
1224
- constructor(document: VideoDocument, options?: PlainMemoryAdapterOptions);
1225
- get journal(): readonly JournalEntry[];
1226
- hasContent(): boolean;
1227
- snapshot(): VideoDocument;
1228
- /**
1229
- * Apply one op via immer. A throw in `edit` discards the draft (state and
1230
- * journal unchanged). When `produce` returns the same reference, there was
1231
- * no structural change — skip journal, matching mirror "no change, no commit".
1232
- */
1233
- transact(edit: (draft: VideoDocumentDraft) => void, audit: TransactAudit): void;
1234
- }
1235
- /** Build a `PlainMemoryAdapter` seeded with `document`. */
1236
- declare function createPlainMemoryAdapter(document: VideoDocument, options?: PlainMemoryAdapterOptions): PlainMemoryAdapter;
1237
1891
  //#endregion
1238
1892
  //#region src/document/mirror-read.d.ts
1239
1893
  /**
@@ -1418,4 +2072,4 @@ declare const videoDocumentSchema: z.ZodObject<{
1418
2072
  }, z.core.$loose>]>>>;
1419
2073
  }, z.core.$loose>;
1420
2074
  //#endregion
1421
- export { VideoClipPart as $, TrackItemDraft as A, derivePositionFromAbs as B, isImplementedSemanticOpKind as C, PartIdFactory as D, ValidationError as E, assertValidVideoDocument as F, PartKind as G, toVideoDocument as H, validateVideoDocument as I, Timeline as J, PartUnion as K, DerivedItemPosition as L, VideoDocumentMirrorSchema as M, videoDocumentMirrorSchema as N, generatePartId as O, VideoDocumentValidationError as P, VIDEO_DOCUMENT_SCHEMA_VERSION as Q, SpeechHostMap as R, SemanticOpKind as S, SnapshotReadable as T, BgmPart as U, fromVideoDocument as V, CaptionPart as W, TrackItem as X, Track as Y, TrackItemTimePosition as Z, SemanticOpName as _, PlainMemoryAdapter as a, effectiveVideoClipDurationMs as at, ImplementedSemanticOpKind as b, MirrorVideoDocumentAdapter as c, CaptionPart$1 as ct, createMirrorVideoDocumentAdapter as d, SpeedShift as dt, VideoDocument as et, writeVideoDocumentToDraft as f, Timeline$1 as ft, SemanticOpInput as g, SemanticEditor as h, JournalEntry as i, VideoDraft as it, VideoDocumentDraft as j, TrackDraft as k, MirrorVideoDocumentOptions as l, CaptionStyle as lt, SemanticDocumentAdapter as m, TrackItem$1 as mt, videoDocumentSchema as n, VideoDocumentValidationIssue as nt, PlainMemoryAdapterOptions as o, speedOf as ot, CommitOptions as p, Track$1 as pt, SpeechPart as q, readVideoDocumentFromDraft as r, VideoDocumentValidationIssueCode as rt, createPlainMemoryAdapter as s, Attachment as st, partUnionSchema as t, VideoDocumentSchemaVersion as tt, createMirrorVideoDocument as u, PartAggregation as ut, TransactAudit as v, SchemaValidator as w, PlannedSemanticOpKind as x, IMPLEMENTED_SEMANTIC_OP_KINDS as y, buildSpeechHostMap as z };
2075
+ export { VideoDocumentValidationIssue as $, assertValidVideoDocument as A, BgmPart as B, index_d_exports as C, VideoDocumentMirrorSchema as D, VideoDocumentDraft as E, SpeechHostMap as F, Timeline as G, PartKind as H, buildSpeechHostMap as I, TrackItemTimePosition as J, Track as K, derivePositionFromAbs as L, InitialDocumentFacts as M, buildInitialVideoDocument as N, videoDocumentMirrorSchema as O, DerivedItemPosition as P, VideoDocumentSchemaVersion as Q, fromVideoDocument as R, ValidationError as S, TrackItemDraft as T, PartUnion as U, CaptionPart as V, SpeechPart as W, VideoClipPart as X, VIDEO_DOCUMENT_SCHEMA_VERSION as Y, VideoDocument as Z, PlannedSemanticOpKind as _, MirrorVideoDocumentOptions as a, CaptionStyle as at, SchemaValidator as b, CommitOptions as c, Timeline$1 as ct, SemanticEditor as d, VideoDocumentValidationIssueCode as et, SemanticOpInput as f, ImplementedSemanticOpKind as g, IMPLEMENTED_SEMANTIC_OP_KINDS as h, MirrorVideoDocumentAdapter as i, CaptionPart$1 as it, validateVideoDocument as j, VideoDocumentValidationError as k, OpActor as l, Track$1 as lt, TransactAudit as m, videoDocumentSchema as n, VideoDraftPartUnion as nt, createMirrorVideoDocument as o, PartAggregation as ot, SemanticOpName as p, TrackItem as q, readVideoDocumentFromDraft as r, Attachment as rt, createMirrorVideoDocumentAdapter as s, SpeedShift as st, partUnionSchema as t, VideoDraft as tt, SemanticDocumentAdapter as u, TrackItem$1 as ut, SemanticOpKind as v, TrackDraft as w, SnapshotReadable as x, isImplementedSemanticOpKind as y, toVideoDocument as z };