@mengine/medeo-tool 1.4.1-alpha.1 → 1.4.1-alpha.3

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/README.md CHANGED
@@ -1,142 +1,90 @@
1
1
  # `@mengine/medeo-tool`
2
2
 
3
- Node tool surface for editing the authoritative Medeo Entity/Relation graph
4
- through the deterministic edit sandbox. Timeline editing is entity editing,
5
- not a legacy document mutation followed by optional entity registration.
3
+ The Node host executes scripts against assembled Medeo entities and relations,
4
+ compiles their operation journal against the original Loro causal snapshot, and
5
+ publishes the original native update. It owns the model API declaration and
6
+ editor context; consumers supply authentication, transport and resource loaders.
6
7
 
7
- The package owns the complete tool path:
8
+ The sandbox API consists of:
8
9
 
9
- - compact snapshot projection;
10
- - trusted worker execution of an agent-authored JavaScript edit script;
11
- - entity-command `ChangePlan` caching;
12
- - complete Entity/Relation snapshot reads and revision-CAS commits;
13
- - per-document serialization, document cache, and shutdown.
10
+ - `entities.list/get/create/update/declareFields/delete`
11
+ - `relations.list/of/link/update/unlink`
12
+ - `rgetAssetFromEntity`, `checkpoint`, `rollbackTo`
14
13
 
15
- A host supplies only environment facts: Mengine HTTP origin, optional request
16
- identity headers, `fetch`, and a stable agent peer id.
14
+ `inputs` and the four console methods are execution context. There is no separate
15
+ business editing facade. The generated declarations are available through
16
+ `@mengine/medeo-tool/sandbox-api` and in every model context.
17
17
 
18
18
  ```ts
19
- import { materializeResources, createMedeoTool } from '@mengine/medeo-tool';
19
+ import { createMedeoTool } from '@mengine/medeo-tool';
20
20
 
21
- const medeo = createMedeoTool({
22
- httpOrigin: process.env.MENGINE_HTTP_ORIGIN!,
23
- userId: () => process.env.MED_USER_ID,
24
- peerId: () => process.env.MED_AGENT_PEER_ID,
25
- });
26
-
27
- const snapshot = await medeo.handle({ op: 'snapshot', doc_id: docId });
21
+ const medeo = createMedeoTool({ httpOrigin, userId, authToken, loadEntityAsset });
28
22
  const run = await medeo.handle({
29
23
  op: 'run-edit-script',
30
24
  doc_id: docId,
31
- script: 'edit.updateClip({ clipEntityId: inputs.clip_id, payload: { volume: -6 } })',
32
- inputs: { clip_id: existingClipEntityId },
33
- });
34
- if (!run.ok || run.op !== 'run-edit-script') throw new Error('edit script failed');
35
- const commit = await medeo.handle({
36
- op: 'commit-plan',
37
- doc_id: docId,
38
- plan_id: run.plan_id,
39
- });
40
-
41
- // Only the host imports resource facts. Model inputs contain domain identities.
42
- const [contentEntityId] = await materializeResources({ docId, httpOrigin }, [
43
- { assetId: 'asset-from-host', kind: 'image' },
44
- ]);
45
- const entityRun = await medeo.handle({
46
- op: 'run-edit-script',
47
- doc_id: docId,
48
- inputs: { content_entity_id: contentEntityId, track_entity_id: existingTrackEntityId },
49
- script: `edit.insertClip({
50
- trackEntityId: inputs.track_entity_id,
51
- contentEntityId: inputs.content_entity_id,
52
- sourceRange: { start: 0, end: 1 }, duration: { mode: 'fixed', value: 5000 },
53
- targetRange: { start: 0, end: 5000 },
25
+ inputs: { clipId },
26
+ script: `entities.update({
27
+ entity_id: inputs.clipId,
28
+ changes: [{ op: 'set', path: ['volume'], value: -6 }],
54
29
  });`,
55
30
  });
56
-
31
+ if (!run.ok || run.op !== 'run-edit-script') throw new Error('Edit failed');
32
+ await medeo.handle({ op: 'commit-plan', doc_id: docId, plan_id: run.plan_id });
57
33
  await medeo.close();
58
34
  ```
59
35
 
60
- The sandbox exposes only business Entity/Relation operations. It has no Asset
61
- lookup, read, create, update, delete, import or binding API. `ensureMedia`,
62
- `findByAssetId` and Asset-based editor shortcuts remain host-only.
63
- Generation tools may return Asset IDs. Entity operations accept these IDs in
64
- `external: { system: 'memota', key: assetId }`; assembled entity reads preserve
65
- that reference. An Asset ID is not an Entity ID, a composition base or a Relation
66
- endpoint. Physical `storageKey` access and Asset rows remain host-only.
67
-
68
- `materializeResources` is a host API. It creates media entities from factual
69
- resources, or Caption with composed AudioScript text, ASR annotation markers
70
- and an optional physical-asset binding. It publishes a native Loro update
71
- before returning domain Entity IDs. Text edits through Caption version its
72
- AudioScript base and rewire composition; Caption's own ID stays unchanged.
73
- Repeated materialization reuses the persisted resource binding. Generation
74
- facts, when supplied, are resolved before the same commit so resource import
75
- and ordered provenance are atomic.
76
-
77
- Before model context/snapshot/editing, the host ensures one Timeline and the four
78
- fixed Tracks (`video_clip`, `speech`, `caption`, `bgm`). Existing IDs, settings and
79
- relations are retained; repeated calls add no rows or revisions. First initialization
80
- uses revision CAS and may write. An empty legacy draft is initialized through the
81
- version-guarded migration path to preserve configured track identities/visibility;
82
- non-empty legacy content still requires explicit `migrate-legacy`. Missing Tracks
83
- are filled in a subsequent CAS when necessary. A lost initialization response is
84
- reconciled by a fresh snapshot, never by assuming success.
85
-
86
- Each placement still creates an independent Clip and SequenceMarker. Reusing a
87
- media variant does not share per-placement trim, speed, volume or placement state.
88
-
89
- The production tool exposes native Clip/Marker editing plus visual placement,
90
- voiceover, caption, and BGM helpers through its generated sandbox interface.
91
- They preserve structural and anchor relations in the same plan. Timeline targets
92
- are Entity IDs, never raw asset IDs or URLs. Asset import, media creation, and
93
- Clip insertion belong in the **same entity plan**.
94
-
95
- Generation lineage is program-synced, not model-authored. After a confirmed
96
- entity commit the tool resolves the plan's diff against host-supplied generation
97
- facts and commits missing `generated` Relations between fact-matched media
98
- Entities already present in the document (endpoint 0 output, endpoint 1 input;
99
- lookup can use either endpoint). The diff covers newly exposed media Asset
100
- identities; placement-only edits and
101
- pairs already fact-resolvable before the plan stay untouched. Models
102
- do not pass generation history through inputs — the host queries it with
103
- `loadGenerationFacts(docId, assetIds)`, returning every known generation record
104
- involving the given asset ids in either role. Each record must carry an explicit
105
- `inputAssetIds` array: an explicit empty array declares text-only generation
106
- with no lineage edge, while a missing or non-array field is a malformed record
107
- that fails the whole query instead of being silently read as text-only.
108
- One-sided facts are skipped without creating entities or blocking the commit —
109
- lineage sync never backfills a missing source or output Entity; entity creation
110
- stays a model decision inside the edit plan. Repeated commits are idempotent,
111
- and deletions and revision conflicts are respected: a CAS-conflict retry reads
112
- the fresh graph and recomputes missing edges without recreating deleted endpoints.
113
- Asset identities are immutable, so each synchronization queries its facts once.
114
- An empty result array means no known lineage; a rejection means the lineage
115
- query failed and is reported as `generation_sync: {status:'failed'}` plus a
116
- `generation_sync_failed` warning — never as synced state.
117
-
118
- Entities own their facts: SequenceMarker owns source/target ranges, duration,
119
- and time remapping; Clip owns volume; Track owns role/visibility. Cross-entity
120
- references are Relations. There is no separate orientation or timing profile.
121
-
122
- The server validates and commits the graph and its read-only Loro projection in
123
- one database transaction. `deleted_entity_ids` and `deleted_relation_ids` make
124
- deletion explicit; dropped rows without deletion intent are rejected. After
125
- cutover, raw Loro `/updates` cannot mutate the document. Legacy standalone
126
- sandbox exports remain library compatibility APIs, not a production-tool mode.
127
-
128
- Existing nonempty legacy documents require a separate version-CAS migration
129
- whose projection preserves existing editing facts. `snapshot` identifies that
130
- requirement and the media IDs needing factual metadata. Call
131
- `{op:'migrate-legacy',doc_id,asset_facts}` explicitly, then take a fresh snapshot
132
- before editing. The package reads the canonical document and current Loro
133
- version itself; it does not accept a caller-supplied snapshot or version, use a
134
- stale cached document after a failed pull, or combine migration with a new edit.
135
- Repeating migration on an entity timeline is read-only. An uncertain submission
136
- must be inspected and retried, never reported as committed.
36
+ Reads are assembled snapshots. `update` applies a nonempty, atomic group of
37
+ field operations, routing inherited fields to the declaring entity. Explicit
38
+ own-field declarations use `declareFields`; duplicate fields from multiple bases
39
+ are errors even if the variant overrides them. An owned-field change versions
40
+ its entity. Automatic base-reference advancement preserves an unchanged variant's
41
+ identity and advances project attachments.
42
+
43
+ Use `text.splice` for Unicode code-point edits and `list.insert/move/remove` for
44
+ collaborative lists, addressing members by `{elementId}`. `set` accepts scalars
45
+ and schema atomic values such as complete time ranges. It cannot replace a
46
+ collaborative map, text or list snapshot. Composition bases are unordered
47
+ membership: insert/remove preserve independent concurrent additions.
48
+
49
+ Relations retain endpoint positions; their kind defines endpoint roles.
50
+ `relations.update` edits metadata/trace fields only. Explicit endpoint changes
51
+ require unlink and a new relation. Deletion refuses live business relations,
52
+ variant dependencies and protected project attachments. It does not cascade or
53
+ delete external resources.
54
+
55
+ An Asset ID is permitted in `payload.external`, but is not an entity ID, base ID
56
+ or relation endpoint. Asset lookup/CRUD and physical storage paths are not
57
+ sandbox APIs. The host resolves the resource already attached to an entity:
58
+
59
+ ```js
60
+ const caption = entities.create({
61
+ entity_kind: 'caption',
62
+ payload: { external: { system: 'memota', key: inputs.captionAssetId } },
63
+ });
64
+ const original = await rgetAssetFromEntity(caption);
65
+ const view = entities.get(caption);
66
+ entities.update({
67
+ entity_id: caption,
68
+ changes: [
69
+ {
70
+ op: 'text.splice',
71
+ path: ['segments', { elementId: view.payload.segments[0].segmentId }, 'text'],
72
+ index: 0,
73
+ deleteCount: 0,
74
+ text: 'Edited: ',
75
+ },
76
+ ],
77
+ });
78
+ ```
137
79
 
138
- The compatibility reader covers the existing four lanes: Image/Video, Voice,
139
- Caption, and background Audio. Coordinates are whole milliseconds for this
140
- reader. It preserves linear visual remapping (`{kind:'linear',rate:2}`), including
141
- image display speed, and explicit anchor chains. Nonlinear remapping and multiple
142
- visual overlay tracks are not existing editor features and remain unsupported.
80
+ Caption resource initialization supplies intrinsic timing and the composed
81
+ project AudioScript text in the same plan. Repeated reads return the original
82
+ resource and preserve edited entity content. Failure prevents publication.
83
+ Resource-free Caption composition from AudioScript is also supported. Each
84
+ on-screen placement has its own Clip and display SequenceMarker, connected by
85
+ ordinary relations; intrinsic Caption timing remains independent.
86
+
87
+ `materializeResources` and generation/resource loaders are host APIs. The model
88
+ never receives these capabilities. Project initialization and historical data
89
+ migration also remain outside script execution. Retrying an unconfirmed commit
90
+ uses the same plan ID and native update, never a recompiled final JSON snapshot.
@@ -1,4 +1,4 @@
1
- import { MediaAssetFact } from "@mengine/medeo-client";
1
+ import { FieldChange, MediaAssetFact } from "@mengine/medeo-client";
2
2
 
3
3
  //#region src/entity/entity-contract.d.ts
4
4
  type JsonPrimitive = string | number | boolean | null;
@@ -10,7 +10,7 @@ type KnownEntityKind = 'axvideo' | 'timeline' | 'track' | 'clip' | 'asset' | 'vi
10
10
  /** Asset identity, either an old physical-only row or a directly composed media variant. */
11
11
  type ResourceEntityKind = 'image' | 'video' | 'audio' | 'voice';
12
12
  type KnownRelationKind = 'timeline-track' | 'track-clip' | 'clip-marker' | 'marker-content' | 'axvideo-marker' | 'marker-timeline' | 'physical-asset' | 'generated' | 'caption-alignment' | 'clip-anchor' | 'phonetic-script-render' | 'audio-script-source' | 'audio-script-marker';
13
- type AuthorableRelationKind = Exclude<KnownRelationKind, 'generated'>;
13
+ type AuthorableRelationKind = KnownRelationKind;
14
14
  interface BoundedNativeSequencePayload extends JsonObject {
15
15
  /** Factual coordinates from recalled media metadata; never invent an end/duration. */
16
16
  extent: {
@@ -57,16 +57,6 @@ type CaptionTextSelection = JsonObject & {
57
57
  end: number;
58
58
  };
59
59
  };
60
- /** Read result only: base text is assembled from the real AudioScript row. */
61
- interface ComposedScriptContent {
62
- audio_script_entity_id: string;
63
- text: string;
64
- segments: ScriptTextSegment[];
65
- }
66
- interface ComposedPhoneticContent extends ComposedScriptContent {
67
- phonemeScript?: string;
68
- prosody?: JsonObject;
69
- }
70
60
  interface EntityPayloadByKind {
71
61
  axvideo: BoundedDerivedSequencePayload;
72
62
  timeline: JsonObject;
@@ -118,10 +108,15 @@ interface EntityPayloadByKind {
118
108
  baseEntityIds: string[];
119
109
  selections: CaptionTextSelection[];
120
110
  style?: JsonObject;
111
+ segmentRanges?: {
112
+ segmentId: string;
113
+ startMs: number;
114
+ endMs: number;
115
+ }[];
121
116
  };
122
117
  }
123
118
  /** Stored own fields; a variant may obtain required content fields from its declared bases. */
124
- type StoredEntityPayload<K extends KnownEntityKind> = EntityPayloadByKind[K] | (JsonObject & Partial<EntityPayloadByKind[K]> & {
119
+ type StoredEntityPayload<K extends KnownEntityKind> = EntityPayloadByKind[K] | (K extends 'caption' ? JsonObject & Pick<MediaAssetPayload, 'external'> : never) | (JsonObject & Partial<EntityPayloadByKind[K]> & {
125
120
  baseEntityIds: string[];
126
121
  });
127
122
  interface SandboxEntity<K extends KnownEntityKind = KnownEntityKind> {
@@ -159,50 +154,32 @@ interface UpdateEntityInput {
159
154
  interface DeleteEntityInput {
160
155
  entity_id: string;
161
156
  }
162
- type EmptyRelationKind = 'timeline-track' | 'track-clip' | 'clip-marker' | 'marker-content' | 'axvideo-marker' | 'marker-timeline' | 'audio-script-marker';
163
157
  interface LinkRelationBase {
164
158
  relation_id?: string;
165
159
  endpoint_0_entity_id: string;
166
160
  endpoint_1_entity_id: string;
167
161
  trace?: JsonObject;
168
162
  }
169
- type LinkRelationInput = (LinkRelationBase & {
170
- relation_kind: EmptyRelationKind;
171
- metadata?: {
172
- [key: string]: never;
173
- };
174
- }) | (LinkRelationBase & {
175
- relation_kind: 'physical-asset';
163
+ interface LinkRelationInput extends LinkRelationBase {
164
+ relation_kind: KnownRelationKind;
176
165
  metadata?: JsonObject;
177
- }) | (LinkRelationBase & {
178
- relation_kind: 'caption-alignment';
179
- metadata: JsonObject & {
180
- alignment: JsonValue;
181
- };
182
- });
183
- interface LinkGeneratedRelationInput {
184
- relation_id?: string;
185
- output_entity_id: string;
186
- input_entity_id: string;
187
- trace?: JsonObject;
188
166
  }
189
- interface LinkClipAnchorRelationInput {
190
- relation_id?: string;
191
- child_clip_entity_id: string;
192
- host_clip_entity_id: string;
193
- trace?: JsonObject;
167
+ interface EntityUpdateInput {
168
+ entity_id: string;
169
+ changes: FieldChange[];
194
170
  }
195
- interface LinkPhoneticScriptRenderRelationInput {
196
- relation_id?: string;
197
- output_entity_id: string;
198
- phonetic_script_entity_id: string;
199
- trace?: JsonObject;
171
+ interface RelationUpdateInput {
172
+ relation_id: string;
173
+ changes: FieldChange[];
174
+ }
175
+ /** Tokens have no script-authored data; only the creating session can validate them. */
176
+ interface EntitySandboxCheckpoint {
177
+ readonly __checkpoint: unique symbol;
200
178
  }
201
- /** `audio-script-source(script, source)`; the script was transcribed from the source media. */
202
- interface LinkAudioScriptSourceRelationInput {
179
+ interface LinkGeneratedRelationInput {
203
180
  relation_id?: string;
204
- script_entity_id: string;
205
- source_entity_id: string;
181
+ output_entity_id: string;
182
+ input_entity_id: string;
206
183
  trace?: JsonObject;
207
184
  }
208
185
  interface UnlinkRelationInput {
@@ -215,6 +192,14 @@ type EntityCommand = {
215
192
  kind: 'update-entity';
216
193
  entity_id: string;
217
194
  payload: JsonObject;
195
+ } | {
196
+ kind: 'change-entity';
197
+ entity_id: string;
198
+ changes: FieldChange[];
199
+ } | {
200
+ kind: 'change-relation';
201
+ relation_id: string;
202
+ changes: FieldChange[];
218
203
  } | {
219
204
  kind: 'delete-entity';
220
205
  entity_id: string;
@@ -232,45 +217,25 @@ interface EntityPlanState {
232
217
  deleted_entity_ids: readonly string[];
233
218
  deleted_relation_ids: readonly string[];
234
219
  }
235
- interface EntityFacade {
236
- /** Read complete assembled fields; returned objects are snapshots. Use update to persist edits. */
220
+ /** Business editing surface. Reads are assembled snapshots; writes preserve native operation intent. */
221
+ interface BusinessEntityFacade {
237
222
  list(): SandboxEntity[];
238
223
  get(entityId: string): SandboxEntity | null;
239
- /** Find document resources by external Memota asset id, including directly composed media variants. */
240
- findByAssetId(assetId: string): SandboxEntity<ResourceEntityKind>[];
241
- /** Assemble selected Caption text; missing composition is an error. */
242
- readCaptionContent(entityId: string): ComposedScriptContent;
243
- /** Assemble base text and pronunciation fields before generating Voice. */
244
- readPhoneticScriptContent(entityId: string): ComposedPhoneticContent;
245
- create(input: CreateEntityInput): string;
246
- /** Patch assembled fields, routing inherited fields to their declaring entity. */
247
- update(input: UpdateEntityInput): void;
248
- /** Explicitly declare own fields, overriding unambiguous bases without modifying them. Ordinary edits use update. */
224
+ create(input: Exclude<CreateEntityInput, {
225
+ entity_kind: 'asset';
226
+ }>): string;
227
+ update(input: EntityUpdateInput): void;
249
228
  declareFields(input: UpdateEntityInput): void;
250
- /** Delete an Entity only after all of its incident Relations have been explicitly unlinked. */
251
229
  delete(input: DeleteEntityInput): void;
252
- /** Get or create one typed Asset by factual external id and return its single content identity. Never creates a Clip. */
253
- ensureMedia(fact: MediaAssetFact): {
254
- contentEntityId: string;
255
- };
256
230
  }
257
- interface RelationFacade {
231
+ /** Endpoint order is retained; each relation kind defines its endpoint semantics. */
232
+ interface BusinessRelationFacade {
258
233
  list(): SandboxRelation[];
259
- /** Incident lookup is endpoint-agnostic; persisted endpoint positions stay unchanged. */
260
234
  of(entityId: string, relationKind?: KnownRelationKind): SandboxRelation[];
261
- /** Link existing entities through ordinary associations; variant bases are stored directly on the variant. */
262
235
  link(input: LinkRelationInput): string;
263
- /** Author ordered generated(output,input); generic link() deliberately rejects this kind. */
264
- linkGenerated(input: LinkGeneratedRelationInput): string;
265
- /** Author ordered clip-anchor(child,host) without positional endpoint ambiguity. */
266
- linkClipAnchor(input: LinkClipAnchorRelationInput): string;
267
- /** Author ordered phonetic-script-render(output,script) without positional endpoint ambiguity. */
268
- linkPhoneticScriptRender(input: LinkPhoneticScriptRenderRelationInput): string;
269
- /** Author ordered audio-script-source(script,source) without positional endpoint ambiguity. */
270
- linkAudioScriptSource(input: LinkAudioScriptSourceRelationInput): string;
271
- /** Remove a Relation by identity; endpoint replacement is an explicit unlink plus link. */
236
+ update(input: RelationUpdateInput): void;
272
237
  unlink(input: UnlinkRelationInput): void;
273
238
  }
274
239
  //#endregion
275
- export { SandboxEntity as _, EntityFacade as a, UpdateEntityInput as b, JsonObject as c, KnownEntityKind as d, KnownRelationKind as f, ResourceEntityKind as g, RelationFacade as h, EntityCommand as i, JsonPrimitive as l, LinkRelationInput as m, CreateEntityInput as n, EntityPlanState as o, LinkGeneratedRelationInput as p, DeleteEntityInput as r, EntityStoreSnapshot as s, AuthorableRelationKind as t, JsonValue as u, SandboxRelation as v, UnlinkRelationInput as y };
276
- //# sourceMappingURL=entity-contract-DHasvrhq.d.mts.map
240
+ export { UpdateEntityInput as C, UnlinkRelationInput as S, LinkRelationInput as _, DeleteEntityInput as a, SandboxEntity as b, EntitySandboxCheckpoint as c, JsonObject as d, JsonPrimitive as f, LinkGeneratedRelationInput as g, KnownRelationKind as h, CreateEntityInput as i, EntityStoreSnapshot as l, KnownEntityKind as m, BusinessEntityFacade as n, EntityCommand as o, JsonValue as p, BusinessRelationFacade as r, EntityPlanState as s, AuthorableRelationKind as t, EntityUpdateInput as u, RelationUpdateInput as v, SandboxRelation as x, ResourceEntityKind as y };
241
+ //# sourceMappingURL=entity-contract-DQ56Ihrh.d.mts.map