@mengine/medeo-tool 1.4.1-alpha.2 → 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,164 +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
- `await rgetAssetFromEntity(entityId)` reads the immutable resource attached to an
69
- Entity through host I/O and returns `{ assetId, content }`. It does not accept an
70
- Asset ID. For a new Caption, pass `payload.external` to `entities.create`, then
71
- await this function before placing it. The host reads the resource and the
72
- session initializes Caption intrinsic timing, the composed project AudioScript
73
- text and its annotation Marker. No Asset row or physical-asset relation is
74
- created. Pending Caption initialization also completes before final validation.
75
- Failure publishes no partial plan. Repeated reads preserve edited text; raw
76
- resource content remains immutable. Native Loro updates retain the original
77
- causal baseline and merge independent collaborator edits.
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:
78
58
 
79
59
  ```js
80
60
  const caption = entities.create({
81
61
  entity_kind: 'caption',
82
- payload: {
83
- external: { system: 'memota', key: inputs.captionAssetId },
84
- },
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
+ ],
85
77
  });
86
- const asset = await rgetAssetFromEntity(caption);
87
- console.log(entities.readCaptionContent(caption));
88
78
  ```
89
79
 
90
- `materializeResources` is a host API. It creates media entities from factual
91
- resources, or Caption with composed AudioScript text, ASR annotation markers
92
- and an optional physical-asset binding. It publishes a native Loro update
93
- before returning domain Entity IDs. Text edits through Caption version its
94
- AudioScript base and rewire composition; Caption's own ID stays unchanged.
95
- Repeated materialization reuses the persisted resource binding. Generation
96
- facts, when supplied, are resolved before the same commit so resource import
97
- and ordered provenance are atomic.
98
-
99
- Before model context/snapshot/editing, the host ensures one Timeline and the four
100
- fixed Tracks (`video_clip`, `speech`, `caption`, `bgm`). Existing IDs, settings and
101
- relations are retained; repeated calls add no rows or revisions. First initialization
102
- uses revision CAS and may write. An empty legacy draft is initialized through the
103
- version-guarded migration path to preserve configured track identities/visibility;
104
- non-empty legacy content still requires explicit `migrate-legacy`. Missing Tracks
105
- are filled in a subsequent CAS when necessary. A lost initialization response is
106
- reconciled by a fresh snapshot, never by assuming success.
107
-
108
- Each placement still creates an independent Clip and SequenceMarker. Reusing a
109
- media variant does not share per-placement trim, speed, volume or placement state.
110
-
111
- The production tool exposes native Clip/Marker editing plus visual placement,
112
- voiceover, caption, and BGM helpers through its generated sandbox interface.
113
- They preserve structural and anchor relations in the same plan. Timeline targets
114
- are Entity IDs, never raw asset IDs or URLs. Asset import, media creation, and
115
- Clip insertion belong in the **same entity plan**.
116
-
117
- Generation lineage is program-synced, not model-authored. After a confirmed
118
- entity commit the tool resolves the plan's diff against host-supplied generation
119
- facts and commits missing `generated` Relations between fact-matched media
120
- Entities already present in the document (endpoint 0 output, endpoint 1 input;
121
- lookup can use either endpoint). The diff covers newly exposed media Asset
122
- identities; placement-only edits and
123
- pairs already fact-resolvable before the plan stay untouched. Models
124
- do not pass generation history through inputs — the host queries it with
125
- `loadGenerationFacts(docId, assetIds)`, returning every known generation record
126
- involving the given asset ids in either role. Each record must carry an explicit
127
- `inputAssetIds` array: an explicit empty array declares text-only generation
128
- with no lineage edge, while a missing or non-array field is a malformed record
129
- that fails the whole query instead of being silently read as text-only.
130
- One-sided facts are skipped without creating entities or blocking the commit —
131
- lineage sync never backfills a missing source or output Entity; entity creation
132
- stays a model decision inside the edit plan. Repeated commits are idempotent,
133
- and deletions and revision conflicts are respected: a CAS-conflict retry reads
134
- the fresh graph and recomputes missing edges without recreating deleted endpoints.
135
- Asset identities are immutable, so each synchronization queries its facts once.
136
- An empty result array means no known lineage; a rejection means the lineage
137
- query failed and is reported as `generation_sync: {status:'failed'}` plus a
138
- `generation_sync_failed` warning — never as synced state.
139
-
140
- Entities own their facts: SequenceMarker owns source/target ranges, duration,
141
- and time remapping; Clip owns volume; Track owns role/visibility. Cross-entity
142
- references are Relations. There is no separate orientation or timing profile.
143
-
144
- The server validates and commits the graph and its read-only Loro projection in
145
- one database transaction. `deleted_entity_ids` and `deleted_relation_ids` make
146
- deletion explicit; dropped rows without deletion intent are rejected. After
147
- cutover, raw Loro `/updates` cannot mutate the document. Legacy standalone
148
- sandbox exports remain library compatibility APIs, not a production-tool mode.
149
-
150
- Existing nonempty legacy documents require a separate version-CAS migration
151
- whose projection preserves existing editing facts. `snapshot` identifies that
152
- requirement and the media IDs needing factual metadata. Call
153
- `{op:'migrate-legacy',doc_id,asset_facts}` explicitly, then take a fresh snapshot
154
- before editing. The package reads the canonical document and current Loro
155
- version itself; it does not accept a caller-supplied snapshot or version, use a
156
- stale cached document after a failed pull, or combine migration with a new edit.
157
- Repeating migration on an entity timeline is read-only. An uncertain submission
158
- must be inspected and retried, never reported as committed.
159
-
160
- The compatibility reader covers the existing four lanes: Image/Video, Voice,
161
- Caption, and background Audio. Coordinates are whole milliseconds for this
162
- reader. It preserves linear visual remapping (`{kind:'linear',rate:2}`), including
163
- image display speed, and explicit anchor chains. Nonlinear remapping and multiple
164
- 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;
@@ -164,50 +154,32 @@ interface UpdateEntityInput {
164
154
  interface DeleteEntityInput {
165
155
  entity_id: string;
166
156
  }
167
- type EmptyRelationKind = 'timeline-track' | 'track-clip' | 'clip-marker' | 'marker-content' | 'axvideo-marker' | 'marker-timeline' | 'audio-script-marker';
168
157
  interface LinkRelationBase {
169
158
  relation_id?: string;
170
159
  endpoint_0_entity_id: string;
171
160
  endpoint_1_entity_id: string;
172
161
  trace?: JsonObject;
173
162
  }
174
- type LinkRelationInput = (LinkRelationBase & {
175
- relation_kind: EmptyRelationKind;
176
- metadata?: {
177
- [key: string]: never;
178
- };
179
- }) | (LinkRelationBase & {
180
- relation_kind: 'physical-asset';
163
+ interface LinkRelationInput extends LinkRelationBase {
164
+ relation_kind: KnownRelationKind;
181
165
  metadata?: JsonObject;
182
- }) | (LinkRelationBase & {
183
- relation_kind: 'caption-alignment';
184
- metadata: JsonObject & {
185
- alignment: JsonValue;
186
- };
187
- });
188
- interface LinkGeneratedRelationInput {
189
- relation_id?: string;
190
- output_entity_id: string;
191
- input_entity_id: string;
192
- trace?: JsonObject;
193
166
  }
194
- interface LinkClipAnchorRelationInput {
195
- relation_id?: string;
196
- child_clip_entity_id: string;
197
- host_clip_entity_id: string;
198
- trace?: JsonObject;
167
+ interface EntityUpdateInput {
168
+ entity_id: string;
169
+ changes: FieldChange[];
199
170
  }
200
- interface LinkPhoneticScriptRenderRelationInput {
201
- relation_id?: string;
202
- output_entity_id: string;
203
- phonetic_script_entity_id: string;
204
- 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;
205
178
  }
206
- /** `audio-script-source(script, source)`; the script was transcribed from the source media. */
207
- interface LinkAudioScriptSourceRelationInput {
179
+ interface LinkGeneratedRelationInput {
208
180
  relation_id?: string;
209
- script_entity_id: string;
210
- source_entity_id: string;
181
+ output_entity_id: string;
182
+ input_entity_id: string;
211
183
  trace?: JsonObject;
212
184
  }
213
185
  interface UnlinkRelationInput {
@@ -220,6 +192,14 @@ type EntityCommand = {
220
192
  kind: 'update-entity';
221
193
  entity_id: string;
222
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[];
223
203
  } | {
224
204
  kind: 'delete-entity';
225
205
  entity_id: string;
@@ -237,45 +217,25 @@ interface EntityPlanState {
237
217
  deleted_entity_ids: readonly string[];
238
218
  deleted_relation_ids: readonly string[];
239
219
  }
240
- interface EntityFacade {
241
- /** 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 {
242
222
  list(): SandboxEntity[];
243
223
  get(entityId: string): SandboxEntity | null;
244
- /** Find document resources by external Memota asset id, including directly composed media variants. */
245
- findByAssetId(assetId: string): SandboxEntity<ResourceEntityKind>[];
246
- /** Assemble selected Caption text; missing composition is an error. */
247
- readCaptionContent(entityId: string): ComposedScriptContent;
248
- /** Assemble base text and pronunciation fields before generating Voice. */
249
- readPhoneticScriptContent(entityId: string): ComposedPhoneticContent;
250
- create(input: CreateEntityInput): string;
251
- /** Patch assembled fields, routing inherited fields to their declaring entity. */
252
- update(input: UpdateEntityInput): void;
253
- /** 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;
254
228
  declareFields(input: UpdateEntityInput): void;
255
- /** Delete an Entity only after all of its incident Relations have been explicitly unlinked. */
256
229
  delete(input: DeleteEntityInput): void;
257
- /** Get or create one typed Asset by factual external id and return its single content identity. Never creates a Clip. */
258
- ensureMedia(fact: MediaAssetFact): {
259
- contentEntityId: string;
260
- };
261
230
  }
262
- interface RelationFacade {
231
+ /** Endpoint order is retained; each relation kind defines its endpoint semantics. */
232
+ interface BusinessRelationFacade {
263
233
  list(): SandboxRelation[];
264
- /** Incident lookup is endpoint-agnostic; persisted endpoint positions stay unchanged. */
265
234
  of(entityId: string, relationKind?: KnownRelationKind): SandboxRelation[];
266
- /** Link existing entities through ordinary associations; variant bases are stored directly on the variant. */
267
235
  link(input: LinkRelationInput): string;
268
- /** Author ordered generated(output,input); generic link() deliberately rejects this kind. */
269
- linkGenerated(input: LinkGeneratedRelationInput): string;
270
- /** Author ordered clip-anchor(child,host) without positional endpoint ambiguity. */
271
- linkClipAnchor(input: LinkClipAnchorRelationInput): string;
272
- /** Author ordered phonetic-script-render(output,script) without positional endpoint ambiguity. */
273
- linkPhoneticScriptRender(input: LinkPhoneticScriptRenderRelationInput): string;
274
- /** Author ordered audio-script-source(script,source) without positional endpoint ambiguity. */
275
- linkAudioScriptSource(input: LinkAudioScriptSourceRelationInput): string;
276
- /** Remove a Relation by identity; endpoint replacement is an explicit unlink plus link. */
236
+ update(input: RelationUpdateInput): void;
277
237
  unlink(input: UnlinkRelationInput): void;
278
238
  }
279
239
  //#endregion
280
- 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 };
281
- //# sourceMappingURL=entity-contract-Cpf3P69H.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