@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.
@@ -1,34 +1,40 @@
1
- import { a as createEntityId, n as EntitySandbox, o as createRelationId, r as businessFacades, t as EditSandboxSession } from "./script-session-AukLN7x7.mjs";
2
- import { EntityTimelineEditor, assertCanonicalEditorResources, assertMediaAssetWritePolicy, base64ToBytes, bytesToBase64, compileEntityRows, projectEntityTimeline } from "@mengine/medeo-client";
1
+ import { n as toDslRows, r as businessFacades, t as EntitySandbox } from "./entity-sandbox-BH-7F5C8.mjs";
2
+ import { LoroEntityDocument, assertCanonicalEditorResources, assertMediaAssetWritePolicy, base64ToBytes, bytesToBase64, projectEntityTimeline } from "@mengine/medeo-client";
3
3
  import { parentPort, workerData } from "node:worker_threads";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import vm from "node:vm";
6
+ //#region src/entity/entity-asset.ts
7
+ /** MCAP's output_caption JSON contract; unknown formats never become invented captions. */
8
+ function captionAssetSegments(content) {
9
+ if (!content || typeof content !== "object" || Array.isArray(content) || !Array.isArray(content.segments)) throw new Error("Caption Asset must contain an output_caption object with segments");
10
+ if (!content.segments.length) throw new Error("Caption Asset contains no speech segments");
11
+ return content.segments.map((value) => {
12
+ if (!value || typeof value !== "object" || Array.isArray(value) || typeof value.text !== "string" || !value.text.trim() || !Number.isSafeInteger(value.start_time_ms) || !Number.isSafeInteger(value.end_time_ms) || value.start_time_ms < 0 || value.end_time_ms <= value.start_time_ms) throw new Error("Caption Asset has invalid text or millisecond timing");
13
+ return {
14
+ text: value.text,
15
+ start_time_ms: value.start_time_ms,
16
+ end_time_ms: value.end_time_ms
17
+ };
18
+ });
19
+ }
20
+ //#endregion
6
21
  //#region src/sandbox/entity-script-session.ts
7
22
  const LOG_LINE_MAX = 2e3;
8
23
  const LOG_LINE_CAP = 1e3;
9
24
  const LOG_BYTE_CAP = 64 * 1024;
10
25
  const TRUNCATE_MARK = "[truncated]";
11
26
  const LOG_TRUNCATED = "[log truncated]";
12
- /**
13
- * Entity-only sandbox session used by the production model path.
14
- *
15
- * Native timeline operations execute against a detached graph editor and are
16
- * then journaled as ordinary EntitySandbox commands. VideoDocument is retained
17
- * only for the request's document identity; it is never edited or projected
18
- * back into the graph.
19
- */
27
+ /** Entity/relation script session; compilation retains the ordered operation journal. */
20
28
  var EntityEditSandboxSession = class {
21
29
  document;
22
30
  entitySandbox;
23
- entityRevision;
24
31
  baseRows;
25
32
  domainIdFactory;
26
33
  logs = [];
27
34
  onLog;
28
35
  logBytes = 0;
36
+ resolvedCaptionAssets = /* @__PURE__ */ new Set();
29
37
  logCapped = false;
30
- edit;
31
- timeline;
32
38
  entities;
33
39
  relations;
34
40
  console;
@@ -36,7 +42,6 @@ var EntityEditSandboxSession = class {
36
42
  rollbackTo;
37
43
  constructor(document, options) {
38
44
  this.document = structuredClone(document);
39
- this.entityRevision = options?.entityState?.revision ?? 0;
40
45
  this.baseRows = toDslRows(options?.entityState ?? {
41
46
  revision: 0,
42
47
  audioScriptEntityId: null,
@@ -53,14 +58,105 @@ var EntityEditSandboxSession = class {
53
58
  onCommand: options?.onEntityCommand,
54
59
  onTruncate: options?.onEntityTruncate
55
60
  });
56
- this.edit = this.buildEditFacade();
57
- this.timeline = { snapshot: () => this.snapshot() };
58
61
  const business = businessFacades(this.entitySandbox.entities, this.entitySandbox.relations);
59
62
  this.entities = business.entities;
60
63
  this.relations = business.relations;
61
64
  this.console = this.buildConsoleShim();
62
- this.checkpoint = () => ({ index: this.entitySandbox.commandCount });
63
- this.rollbackTo = (cp) => this.entitySandbox.rollbackTo(cp.index);
65
+ const checkpoints = /* @__PURE__ */ new Map();
66
+ this.checkpoint = () => {
67
+ const token = Object.freeze({});
68
+ checkpoints.set(token, this.entitySandbox.commandCount);
69
+ return token;
70
+ };
71
+ this.rollbackTo = (cp) => {
72
+ const index = checkpoints.get(cp);
73
+ if (index === void 0) throw new Error("Invalid or expired sandbox checkpoint");
74
+ this.entitySandbox.rollbackTo(index);
75
+ let later = false;
76
+ for (const token of checkpoints.keys()) {
77
+ if (later) checkpoints.delete(token);
78
+ if (token === cp) later = true;
79
+ }
80
+ };
81
+ }
82
+ /** Resolve only the resource attached to this Entity; I/O remains in the parent host. */
83
+ async rgetAssetFromEntity(entityId, load) {
84
+ const entity = this.entitySandbox.entities.get(entityId);
85
+ if (!entity || entity.entity_kind === "asset") throw new Error(`Business Entity not found: ${entityId}`);
86
+ const external = entity.payload.external;
87
+ if (!external || typeof external !== "object" || Array.isArray(external) || typeof external.key !== "string") throw new Error(`Entity ${entityId} has no attached Asset`);
88
+ const assetId = external.key;
89
+ const result = await load(entity);
90
+ if (result.assetId !== external.key) throw new Error("Host returned a different Entity Asset");
91
+ const current = this.entitySandbox.entities.get(entityId);
92
+ if (!current || JSON.stringify(current.payload.external) !== JSON.stringify(external)) throw new Error("Entity resource changed during its read");
93
+ if (entity.entity_kind === "caption" && !this.initializedAsset(entityId, external.key)) {
94
+ const timed = captionAssetSegments(result.content);
95
+ const scriptId = this.entitySandbox.audioScriptEntityId;
96
+ if (!scriptId) throw new Error("Project AudioScript is missing");
97
+ const script = this.entitySandbox.entities.get(scriptId);
98
+ const segments = timed.map((segment, index) => ({
99
+ segmentId: `asset:${assetId}:${index}`,
100
+ text: segment.text
101
+ }));
102
+ const existing = script.payload.segments;
103
+ const additions = segments.filter((segment) => !existing.some((item) => item.segmentId === segment.segmentId));
104
+ this.entitySandbox.entities.update({
105
+ entity_id: scriptId,
106
+ payload: { segments: [...existing, ...additions] }
107
+ });
108
+ const ranges = timed.map((segment, index) => ({
109
+ segmentId: segments[index].segmentId,
110
+ startMs: segment.start_time_ms,
111
+ endMs: segment.end_time_ms
112
+ }));
113
+ this.entitySandbox.entities.update({
114
+ entity_id: entityId,
115
+ payload: {
116
+ baseEntityIds: [scriptId],
117
+ selections: segments.map(({ segmentId }) => ({ segmentId })),
118
+ extent: {
119
+ kind: "bounded",
120
+ start: Math.min(...ranges.map((r) => r.startMs)),
121
+ end: Math.max(...ranges.map((r) => r.endMs))
122
+ },
123
+ sampling: "native",
124
+ coordinateSpace: "milliseconds",
125
+ segmentRanges: ranges
126
+ }
127
+ });
128
+ const markerId = this.entitySandbox.entities.create({
129
+ entity_kind: "sequence-marker",
130
+ payload: {
131
+ sourceRange: {
132
+ start: Math.min(...ranges.map((r) => r.startMs)),
133
+ end: Math.max(...ranges.map((r) => r.endMs))
134
+ },
135
+ duration: { mode: "from-source" },
136
+ segmentRanges: ranges
137
+ }
138
+ });
139
+ this.entitySandbox.relations.link({
140
+ relation_kind: "audio-script-marker",
141
+ endpoint_0_entity_id: scriptId,
142
+ endpoint_1_entity_id: markerId
143
+ });
144
+ }
145
+ this.resolvedCaptionAssets.add(`${entityId}:${assetId}`);
146
+ return result;
147
+ }
148
+ initializedAsset(entityId, assetId) {
149
+ const current = this.entitySandbox.entities.get(entityId);
150
+ if (this.resolvedCaptionAssets.has(`${entityId}:${assetId}`) && Array.isArray(current?.payload.selections) && Array.isArray(current?.payload.baseEntityIds)) return true;
151
+ const external = this.baseRows.entities.find((row) => row.entityId === entityId)?.payload.external;
152
+ return !!external && typeof external === "object" && !Array.isArray(external) && "key" in external && external.key === assetId;
153
+ }
154
+ /** Finish unawaited Caption initialization before validation; no partial rows are published. */
155
+ async prepareEntityAssets(load) {
156
+ for (const entity of this.entitySandbox.entities.list()) {
157
+ const external = entity.payload.external;
158
+ if (entity.entity_kind === "caption" && external && typeof external === "object" && !Array.isArray(external) && typeof external.key === "string" && !this.initializedAsset(entity.entity_id, external.key)) await this.rgetAssetFromEntity(entity.entity_id, load);
159
+ }
64
160
  }
65
161
  buildPlan(baseVersion) {
66
162
  const entityPlan = this.entitySandbox.buildPlan();
@@ -68,7 +164,7 @@ var EntityEditSandboxSession = class {
68
164
  assertMediaAssetWritePolicy(this.baseRows, toDslRows(entityPlan.rows));
69
165
  if (entityPlan.rows.loroSnapshot) projectEntityTimeline(toDslRows(entityPlan.rows), this.document.meta);
70
166
  return {
71
- ...entityPlan.rows.loroSnapshot ? { loro_update: bytesToBase64(compileEntityRows(base64ToBytes(entityPlan.rows.loroSnapshot), toDslRows(entityPlan.rows)).update) } : {},
167
+ ...entityPlan.rows.loroSnapshot ? { loro_update: bytesToBase64(this.compileJournal(entityPlan)) } : {},
72
168
  plan_kind: "entities",
73
169
  doc_id: this.document.meta.draft_id ?? "",
74
170
  base_version: baseVersion,
@@ -82,59 +178,55 @@ var EntityEditSandboxSession = class {
82
178
  logs: this.logs.slice()
83
179
  };
84
180
  }
181
+ compileJournal(plan) {
182
+ return LoroEntityDocument.fromSnapshot(base64ToBytes(plan.rows.loroSnapshot), (state) => {
183
+ assertCanonicalEditorResources(state.rows);
184
+ projectEntityTimeline(state.rows, this.document.meta);
185
+ }).transact((draft) => {
186
+ for (const command of plan.commands) switch (command.kind) {
187
+ case "create-entity": {
188
+ const rows = toDslRows({
189
+ revision: 0,
190
+ audioScriptEntityId: null,
191
+ entities: [command.entity],
192
+ relations: []
193
+ });
194
+ draft.create(rows.entities[0]);
195
+ if (command.entity.entity_kind === "audio-script") draft.attach("audioScriptEntityId", command.entity.entity_id);
196
+ break;
197
+ }
198
+ case "update-entity":
199
+ draft.replaceOwned(command.entity_id, command.payload);
200
+ break;
201
+ case "change-entity":
202
+ draft.change(command.entity_id, command.changes);
203
+ break;
204
+ case "delete-entity":
205
+ draft.delete(command.entity_id);
206
+ break;
207
+ case "link-relation": {
208
+ const rows = toDslRows({
209
+ revision: 0,
210
+ audioScriptEntityId: null,
211
+ entities: [],
212
+ relations: [command.relation]
213
+ });
214
+ draft.link(rows.relations[0]);
215
+ break;
216
+ }
217
+ case "change-relation":
218
+ draft.changeRelation(command.relation_id, command.changes);
219
+ break;
220
+ case "unlink-relation":
221
+ draft.unlink(command.relation_id);
222
+ break;
223
+ }
224
+ draft.reconcileOrder(toDslRows(plan.rows));
225
+ });
226
+ }
85
227
  getLogs() {
86
228
  return this.logs;
87
229
  }
88
- buildEditFacade() {
89
- return {
90
- insertClip: (input) => this.runNativeEdit((editor) => editor.insertClip(input)),
91
- insertPlacedClip: (input) => this.runNativeEdit((editor) => editor.insertPlacedClip(input)),
92
- updateClipMarker: (input) => this.runNativeEdit((editor) => editor.updateClipMarker(input)),
93
- setClipPlacement: (input) => this.runNativeEdit((editor) => editor.setClipPlacement(input)),
94
- moveSequentialClips: (input) => this.runNativeEdit((editor) => editor.moveSequentialClips(input)),
95
- moveClip: (input) => this.runNativeEdit((editor) => editor.moveClip(input)),
96
- replaceClipContent: (input) => this.runNativeEdit((editor) => editor.replaceClipContent(input)),
97
- setClipVolume: (input) => this.runNativeEdit((editor) => editor.setClipVolume(input)),
98
- setClipSpeed: (input) => this.runNativeEdit((editor) => editor.setClipSpeed(input)),
99
- trimClip: (input) => this.runNativeEdit((editor) => editor.trimClip(input)),
100
- deleteClip: (input) => this.runNativeEdit((editor) => editor.deleteClip(input)),
101
- deleteClipTree: (input) => this.runNativeEdit((editor) => editor.deleteClipTree(input)),
102
- updateClip: (input) => this.runNativeEdit((editor) => editor.updateClip(input)),
103
- moveVoiceover: (input) => this.runNativeEdit((editor) => editor.moveVoiceover(input)),
104
- moveClipsToStarts: (input) => this.runNativeEdit((editor) => editor.moveClipsToStarts(input)),
105
- deleteVoiceover: (input) => this.runNativeEdit((editor) => editor.deleteVoiceover(input)),
106
- deleteBgm: (input) => this.runNativeEdit((editor) => editor.deleteBgm(input)),
107
- setCaptionVisibility: (input) => this.runNativeEdit((editor) => editor.setCaptionVisibility(input)),
108
- patchCaptionStyle: (input) => this.runNativeEdit((editor) => editor.patchCaptionStyle(input)),
109
- insertCaptionClip: (input) => this.runNativeEdit((editor) => editor.insertCaptionClip(input))
110
- };
111
- }
112
- runNativeEdit(mutate) {
113
- const checkpoint = this.entitySandbox.commandCount;
114
- try {
115
- const before = this.entitySandbox.buildPlan().rows;
116
- const editor = new EntityTimelineEditor(toDslRows(before), this.domainIdFactory);
117
- const result = mutate(editor);
118
- const expected = fromDslRows(editor.rows(), before.revision, before.audioScriptEntityId);
119
- applyGraphDiff(this.entitySandbox, before, expected);
120
- const applied = this.entitySandbox.buildPlan().rows;
121
- if (!graphRowsEqual(applied, expected)) throw new Error("Entity timeline command diff did not reproduce the editor result");
122
- return result;
123
- } catch (error) {
124
- this.entitySandbox.rollbackTo(checkpoint);
125
- throw error;
126
- }
127
- }
128
- snapshot() {
129
- const audioScriptEntityId = this.entitySandbox.audioScriptEntityId;
130
- if (audioScriptEntityId === null) throw new Error("Document AudioScript is not initialized");
131
- return {
132
- revision: this.entityRevision,
133
- audioScriptEntityId,
134
- entities: this.entities.list(),
135
- relations: this.relations.list()
136
- };
137
- }
138
230
  appendLog(line) {
139
231
  if (this.logCapped) return;
140
232
  if (this.logs.length >= LOG_LINE_CAP || this.logBytes >= LOG_BYTE_CAP) {
@@ -158,141 +250,6 @@ var EntityEditSandboxSession = class {
158
250
  };
159
251
  }
160
252
  };
161
- function applyGraphDiff(sandbox, before, after) {
162
- const beforeEntities = new Map(before.entities.map((entity) => [entity.entity_id, entity]));
163
- const afterEntities = new Map(after.entities.map((entity) => [entity.entity_id, entity]));
164
- const beforeRelations = new Map(before.relations.map((relation) => [relation.relation_id, relation]));
165
- const afterRelations = new Map(after.relations.map((relation) => [relation.relation_id, relation]));
166
- for (const [entityId, previous] of beforeEntities) {
167
- const next = afterEntities.get(entityId);
168
- if (next != null && next.entity_kind !== previous.entity_kind) throw new Error(`Entity id "${entityId}" cannot change kind from "${previous.entity_kind}" to "${next.entity_kind}"`);
169
- }
170
- for (const [relationId, previous] of beforeRelations) {
171
- const next = afterRelations.get(relationId);
172
- if (next != null && (next.relation_kind !== previous.relation_kind || next.endpoint_0_entity_id !== previous.endpoint_0_entity_id || next.endpoint_1_entity_id !== previous.endpoint_1_entity_id)) throw new Error(`Relation id "${relationId}" cannot change kind or persisted endpoint positions`);
173
- }
174
- const relationIdsToReplace = new Set([...beforeRelations].filter(([relationId, previous]) => {
175
- const next = afterRelations.get(relationId);
176
- return next != null && (!jsonEqual(previous.metadata, next.metadata) || !jsonEqual(previous.trace, next.trace));
177
- }).map(([relationId]) => relationId));
178
- const relationIdsToUnlink = [...beforeRelations.keys()].filter((relationId) => !afterRelations.has(relationId) || relationIdsToReplace.has(relationId)).sort();
179
- for (const relationId of relationIdsToUnlink) sandbox.relations.unlink({ relation_id: relationId });
180
- const entityIdsToDelete = [...beforeEntities.keys()].filter((entityId) => !afterEntities.has(entityId)).sort();
181
- for (const entityId of entityIdsToDelete) sandbox.entities.delete({ entity_id: entityId });
182
- const entitiesToCreate = [...afterEntities.values()].filter((entity) => !beforeEntities.has(entity.entity_id)).sort((left, right) => left.entity_id.localeCompare(right.entity_id));
183
- for (const entity of entitiesToCreate) sandbox.entities.create(entity);
184
- const entitiesToUpdate = [...afterEntities.values()].filter((entity) => {
185
- const previous = beforeEntities.get(entity.entity_id);
186
- return previous != null && !jsonEqual(previous.payload, entity.payload);
187
- }).sort((left, right) => left.entity_id.localeCompare(right.entity_id));
188
- for (const entity of entitiesToUpdate) sandbox.replaceOwnedPayload({
189
- entity_id: entity.entity_id,
190
- payload: entity.payload
191
- });
192
- const relationsToLink = [...afterRelations.values()].filter((relation) => !beforeRelations.has(relation.relation_id) || relationIdsToReplace.has(relation.relation_id)).sort((left, right) => left.relation_id.localeCompare(right.relation_id));
193
- for (const relation of relationsToLink) linkRelation(sandbox, relation);
194
- }
195
- function linkRelation(sandbox, relation) {
196
- if (relation.relation_kind === "generated") {
197
- sandbox.relations.linkGenerated({
198
- relation_id: relation.relation_id,
199
- output_entity_id: relation.endpoint_0_entity_id,
200
- input_entity_id: relation.endpoint_1_entity_id,
201
- trace: relation.trace
202
- });
203
- return;
204
- }
205
- if (relation.relation_kind === "clip-anchor") {
206
- sandbox.relations.linkClipAnchor({
207
- relation_id: relation.relation_id,
208
- child_clip_entity_id: relation.endpoint_0_entity_id,
209
- host_clip_entity_id: relation.endpoint_1_entity_id,
210
- trace: relation.trace
211
- });
212
- return;
213
- }
214
- if (relation.relation_kind === "phonetic-script-render") {
215
- const firstIsVoice = sandbox.entities.get(relation.endpoint_0_entity_id)?.entity_kind === "voice";
216
- sandbox.relations.linkPhoneticScriptRender({
217
- relation_id: relation.relation_id,
218
- output_entity_id: firstIsVoice ? relation.endpoint_0_entity_id : relation.endpoint_1_entity_id,
219
- phonetic_script_entity_id: firstIsVoice ? relation.endpoint_1_entity_id : relation.endpoint_0_entity_id,
220
- trace: relation.trace
221
- });
222
- return;
223
- }
224
- if (relation.relation_kind === "audio-script-source") {
225
- const firstIsScript = sandbox.entities.get(relation.endpoint_0_entity_id)?.entity_kind === "audio-script";
226
- sandbox.relations.linkAudioScriptSource({
227
- relation_id: relation.relation_id,
228
- script_entity_id: firstIsScript ? relation.endpoint_0_entity_id : relation.endpoint_1_entity_id,
229
- source_entity_id: firstIsScript ? relation.endpoint_1_entity_id : relation.endpoint_0_entity_id,
230
- trace: relation.trace
231
- });
232
- return;
233
- }
234
- sandbox.relations.link({
235
- relation_id: relation.relation_id,
236
- relation_kind: relation.relation_kind,
237
- endpoint_0_entity_id: relation.endpoint_0_entity_id,
238
- endpoint_1_entity_id: relation.endpoint_1_entity_id,
239
- metadata: relation.metadata,
240
- trace: relation.trace
241
- });
242
- }
243
- function toDslRows(snapshot) {
244
- return {
245
- entities: snapshot.entities.map((entity) => ({
246
- entityId: createEntityId(entity.entity_id),
247
- entityKind: entity.entity_kind,
248
- payload: structuredClone(entity.payload)
249
- })),
250
- relations: snapshot.relations.map((relation) => ({
251
- relationId: createRelationId(relation.relation_id),
252
- relationKind: relation.relation_kind,
253
- endpoint0EntityId: createEntityId(relation.endpoint_0_entity_id),
254
- endpoint1EntityId: createEntityId(relation.endpoint_1_entity_id),
255
- metadata: structuredClone(relation.metadata),
256
- trace: structuredClone(relation.trace)
257
- }))
258
- };
259
- }
260
- function fromDslRows(rows, revision, audioScriptEntityId) {
261
- return {
262
- revision,
263
- audioScriptEntityId,
264
- entities: rows.entities.map((entity) => ({
265
- entity_id: entity.entityId,
266
- entity_kind: entity.entityKind,
267
- payload: structuredClone(entity.payload)
268
- })),
269
- relations: rows.relations.map((relation) => ({
270
- relation_id: relation.relationId,
271
- relation_kind: relation.relationKind,
272
- endpoint_0_entity_id: relation.endpoint0EntityId,
273
- endpoint_1_entity_id: relation.endpoint1EntityId,
274
- metadata: structuredClone(relation.metadata),
275
- trace: structuredClone(relation.trace)
276
- }))
277
- };
278
- }
279
- function jsonEqual(left, right) {
280
- if (Object.is(left, right)) return true;
281
- if (Array.isArray(left) || Array.isArray(right)) return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => jsonEqual(value, right[index]));
282
- if (!isRecord(left) || !isRecord(right)) return false;
283
- const leftKeys = Object.keys(left).sort();
284
- const rightKeys = Object.keys(right).sort();
285
- return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => key === rightKeys[index] && jsonEqual(left[key], right[key]));
286
- }
287
- function graphRowsEqual(left, right) {
288
- if (left.revision !== right.revision || left.audioScriptEntityId !== right.audioScriptEntityId || left.entities.length !== right.entities.length || left.relations.length !== right.relations.length) return false;
289
- const rightEntities = new Map(right.entities.map((entity) => [entity.entity_id, entity]));
290
- const rightRelations = new Map(right.relations.map((relation) => [relation.relation_id, relation]));
291
- return left.entities.every((entity) => jsonEqual(entity, rightEntities.get(entity.entity_id))) && left.relations.every((relation) => jsonEqual(relation, rightRelations.get(relation.relation_id)));
292
- }
293
- function isRecord(value) {
294
- return value !== null && typeof value === "object" && !Array.isArray(value);
295
- }
296
253
  function formatLogArg(value) {
297
254
  if (typeof value === "string") return value;
298
255
  if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0) return String(value);
@@ -307,6 +264,29 @@ function formatLogArg(value) {
307
264
  const data = workerData;
308
265
  if (parentPort == null) throw new Error("worker-entry must run inside a worker_threads Worker");
309
266
  const port = parentPort;
267
+ let requestId = 0;
268
+ const pending = /* @__PURE__ */ new Map();
269
+ port.on("message", (message) => {
270
+ if (message.t !== "entity-asset-result") return;
271
+ const waiter = pending.get(message.requestId);
272
+ pending.delete(message.requestId);
273
+ if (message.error) waiter?.reject(new Error(message.error));
274
+ else waiter?.resolve(message.result);
275
+ });
276
+ function loadEntityAsset(entity) {
277
+ return new Promise((resolve, reject) => {
278
+ const id = ++requestId;
279
+ pending.set(id, {
280
+ resolve,
281
+ reject
282
+ });
283
+ port.postMessage({
284
+ t: "entity-asset",
285
+ requestId: id,
286
+ entity
287
+ });
288
+ });
289
+ }
310
290
  function post(message) {
311
291
  port.postMessage(message);
312
292
  }
@@ -372,8 +352,8 @@ async function main() {
372
352
  index
373
353
  })
374
354
  };
375
- const session = data.entityOnly ? new EntityEditSandboxSession(data.document, options) : new EditSandboxSession(data.document, options);
376
- const wrapped = `(async (edit, timeline, entities, relations, checkpoint, rollbackTo, inputs, console) => {${data.script}\n})`;
355
+ const session = new EntityEditSandboxSession(data.document, options);
356
+ const wrapped = `(async (entities, relations, checkpoint, rollbackTo, inputs, console, rgetAssetFromEntity) => {${data.script}\n})`;
377
357
  const ctx = vm.createContext(Object.create(null));
378
358
  let run;
379
359
  try {
@@ -397,7 +377,10 @@ async function main() {
397
377
  try {
398
378
  const invoke = run;
399
379
  post({ t: "ready" });
400
- await invoke(session.edit, session.timeline, session.entities, session.relations, session.checkpoint, session.rollbackTo, data.inputs ?? {}, session.console);
380
+ await invoke(session.entities, session.relations, session.checkpoint, session.rollbackTo, data.inputs ?? {}, session.console, (entityId) => {
381
+ return session.rgetAssetFromEntity(entityId, loadEntityAsset);
382
+ });
383
+ await session.prepareEntityAssets(loadEntityAsset);
401
384
  } catch (error) {
402
385
  post({
403
386
  t: "fail",
@@ -1 +1 @@
1
- {"version":3,"file":"worker-entry.mjs","names":[],"sources":["../src/sandbox/entity-script-session.ts","../src/sandbox/worker-entry.ts"],"sourcesContent":["import {\n EntityTimelineEditor,\n compileEntityRows,\n projectEntityTimeline,\n base64ToBytes,\n bytesToBase64,\n assertCanonicalEditorResources,\n assertMediaAssetWritePolicy,\n type ClipEntityId,\n type DeleteBgmInput,\n type DeleteClipInput,\n type DeleteClipTreeInput,\n type DeleteVoiceoverInput,\n type InsertCaptionClipInput,\n type InsertClipInput,\n type InsertPlacedClipInput,\n type MoveClipInput,\n type MoveClipsToStartsInput,\n type MoveSequentialClipsInput,\n type MoveVoiceoverInput,\n type PatchCaptionStyleInput,\n type ReplaceClipContentInput,\n type SetCaptionVisibilityInput,\n type SetClipPlacementInput,\n type SetClipSpeedInput,\n type SetClipVolumeInput,\n type TrimClipInput,\n type UpdateClipInput,\n type UpdateClipMarkerInput,\n type VideoDocument,\n} from '@mengine/medeo-client';\nimport {\n createEntityId,\n createRelationId,\n type EntityRelationRows,\n type EntityRow,\n type RelationRow,\n} from '@mengine/medeo-dsl';\n\nimport type {\n CreateEntityInput,\n BusinessEntityFacade,\n EntityStoreSnapshot,\n LinkRelationInput,\n BusinessRelationFacade,\n SandboxEntity,\n SandboxRelation,\n} from '../entity/entity-contract.ts';\nimport { EntitySandbox, type DomainIdFactory } from '../entity/entity-sandbox.ts';\nimport { businessFacades } from './business-facades.ts';\nimport type { ChangePlan, ConsoleShim, EditSandboxSessionOptions, SandboxCheckpoint } from './script-session.ts';\n\nconst LOG_LINE_MAX = 2000;\nconst LOG_LINE_CAP = 1000;\nconst LOG_BYTE_CAP = 64 * 1024;\nconst TRUNCATE_MARK = '[truncated]';\nconst LOG_TRUNCATED = '[log truncated]';\n\n/** Model-facing timeline mutations whose source of truth is the Medeo Entity graph. */\nexport interface EntityEditFacade {\n insertClip(input: InsertClipInput): ClipEntityId;\n insertPlacedClip(input: InsertPlacedClipInput): ClipEntityId;\n updateClipMarker(input: UpdateClipMarkerInput): void;\n setClipPlacement(input: SetClipPlacementInput): void;\n moveSequentialClips(input: MoveSequentialClipsInput): void;\n moveClip(input: MoveClipInput): void;\n replaceClipContent(input: ReplaceClipContentInput): void;\n setClipVolume(input: SetClipVolumeInput): void;\n setClipSpeed(input: SetClipSpeedInput): void;\n trimClip(input: TrimClipInput): void;\n deleteClip(input: DeleteClipInput): void;\n deleteClipTree(input: DeleteClipTreeInput): void;\n updateClip(input: UpdateClipInput): void;\n moveVoiceover(input: MoveVoiceoverInput): void;\n moveClipsToStarts(input: MoveClipsToStartsInput): void;\n deleteVoiceover(input: DeleteVoiceoverInput): void;\n deleteBgm(input: DeleteBgmInput): void;\n setCaptionVisibility(input: SetCaptionVisibilityInput): void;\n patchCaptionStyle(input: PatchCaptionStyleInput): void;\n insertCaptionClip(input: InsertCaptionClipInput): ClipEntityId;\n}\n\nexport interface EntityTimelineFacade {\n /** Return the current graph draft, including uncommitted commands. */\n snapshot(): EntityStoreSnapshot & { audioScriptEntityId: string };\n}\n\n/**\n * Entity-only sandbox session used by the production model path.\n *\n * Native timeline operations execute against a detached graph editor and are\n * then journaled as ordinary EntitySandbox commands. VideoDocument is retained\n * only for the request's document identity; it is never edited or projected\n * back into the graph.\n */\nexport class EntityEditSandboxSession {\n private readonly document: VideoDocument;\n private readonly entitySandbox: EntitySandbox;\n private readonly entityRevision: number;\n private readonly baseRows: EntityRelationRows;\n private readonly domainIdFactory: DomainIdFactory;\n private readonly logs: string[] = [];\n private readonly onLog: ((line: string) => void) | undefined;\n private logBytes = 0;\n private logCapped = false;\n\n readonly edit: EntityEditFacade;\n readonly timeline: EntityTimelineFacade;\n readonly entities: BusinessEntityFacade;\n readonly relations: BusinessRelationFacade;\n readonly console: ConsoleShim;\n readonly checkpoint: () => SandboxCheckpoint;\n readonly rollbackTo: (cp: SandboxCheckpoint) => void;\n\n constructor(document: VideoDocument, options?: EditSandboxSessionOptions) {\n this.document = structuredClone(document);\n this.entityRevision = options?.entityState?.revision ?? 0;\n this.baseRows = toDslRows(\n options?.entityState ?? { revision: 0, audioScriptEntityId: null, entities: [], relations: [] },\n );\n this.domainIdFactory =\n options?.domainIdFactory ??\n (() => {\n throw new Error('Entity id factory is unavailable in this sandbox host');\n });\n this.onLog = options?.onLog;\n this.entitySandbox = new EntitySandbox({\n state: options?.entityState,\n idFactory: this.domainIdFactory,\n onCommand: options?.onEntityCommand,\n onTruncate: options?.onEntityTruncate,\n });\n\n this.edit = this.buildEditFacade();\n this.timeline = { snapshot: () => this.snapshot() };\n const business = businessFacades(this.entitySandbox.entities, this.entitySandbox.relations);\n this.entities = business.entities;\n this.relations = business.relations;\n this.console = this.buildConsoleShim();\n this.checkpoint = () => ({ index: this.entitySandbox.commandCount });\n this.rollbackTo = (cp) => this.entitySandbox.rollbackTo(cp.index);\n }\n\n buildPlan(baseVersion: string): ChangePlan {\n const entityPlan = this.entitySandbox.buildPlan();\n assertCanonicalEditorResources(toDslRows(entityPlan.rows));\n assertMediaAssetWritePolicy(this.baseRows, toDslRows(entityPlan.rows));\n if (entityPlan.rows.loroSnapshot) projectEntityTimeline(toDslRows(entityPlan.rows), this.document.meta);\n return {\n ...(entityPlan.rows.loroSnapshot\n ? {\n loro_update: bytesToBase64(\n compileEntityRows(base64ToBytes(entityPlan.rows.loroSnapshot), toDslRows(entityPlan.rows)).update,\n ),\n }\n : {}),\n plan_kind: 'entities',\n doc_id: this.document.meta.draft_id ?? '',\n base_version: baseVersion,\n ops: [],\n entity_base_revision: entityPlan.base_revision,\n entity_commands: entityPlan.commands,\n entity_rows: entityPlan.rows,\n deleted_entity_ids: entityPlan.deleted_entity_ids,\n deleted_relation_ids: entityPlan.deleted_relation_ids,\n preview: this.entitySandbox.renderPreview(),\n logs: this.logs.slice(),\n };\n }\n\n getLogs(): readonly string[] {\n return this.logs;\n }\n\n private buildEditFacade(): EntityEditFacade {\n return {\n insertClip: (input) => this.runNativeEdit((editor) => editor.insertClip(input)),\n insertPlacedClip: (input) => this.runNativeEdit((editor) => editor.insertPlacedClip(input)),\n updateClipMarker: (input) => this.runNativeEdit((editor) => editor.updateClipMarker(input)),\n setClipPlacement: (input) => this.runNativeEdit((editor) => editor.setClipPlacement(input)),\n moveSequentialClips: (input) => this.runNativeEdit((editor) => editor.moveSequentialClips(input)),\n moveClip: (input) => this.runNativeEdit((editor) => editor.moveClip(input)),\n replaceClipContent: (input) => this.runNativeEdit((editor) => editor.replaceClipContent(input)),\n setClipVolume: (input) => this.runNativeEdit((editor) => editor.setClipVolume(input)),\n setClipSpeed: (input) => this.runNativeEdit((editor) => editor.setClipSpeed(input)),\n trimClip: (input) => this.runNativeEdit((editor) => editor.trimClip(input)),\n deleteClip: (input) => this.runNativeEdit((editor) => editor.deleteClip(input)),\n deleteClipTree: (input) => this.runNativeEdit((editor) => editor.deleteClipTree(input)),\n updateClip: (input) => this.runNativeEdit((editor) => editor.updateClip(input)),\n moveVoiceover: (input) => this.runNativeEdit((editor) => editor.moveVoiceover(input)),\n moveClipsToStarts: (input) => this.runNativeEdit((editor) => editor.moveClipsToStarts(input)),\n deleteVoiceover: (input) => this.runNativeEdit((editor) => editor.deleteVoiceover(input)),\n deleteBgm: (input) => this.runNativeEdit((editor) => editor.deleteBgm(input)),\n setCaptionVisibility: (input) => this.runNativeEdit((editor) => editor.setCaptionVisibility(input)),\n patchCaptionStyle: (input) => this.runNativeEdit((editor) => editor.patchCaptionStyle(input)),\n insertCaptionClip: (input) => this.runNativeEdit((editor) => editor.insertCaptionClip(input)),\n };\n }\n\n private runNativeEdit<T>(mutate: (editor: EntityTimelineEditor) => T): T {\n const checkpoint = this.entitySandbox.commandCount;\n try {\n const before = this.entitySandbox.buildPlan().rows;\n const editor = new EntityTimelineEditor(toDslRows(before), this.domainIdFactory);\n const result = mutate(editor);\n const expected = fromDslRows(editor.rows(), before.revision, before.audioScriptEntityId);\n applyGraphDiff(this.entitySandbox, before, expected);\n const applied = this.entitySandbox.buildPlan().rows;\n if (!graphRowsEqual(applied, expected)) {\n throw new Error('Entity timeline command diff did not reproduce the editor result');\n }\n return result;\n } catch (error) {\n this.entitySandbox.rollbackTo(checkpoint);\n throw error;\n }\n }\n\n private snapshot(): EntityStoreSnapshot & { audioScriptEntityId: string } {\n const audioScriptEntityId = this.entitySandbox.audioScriptEntityId;\n if (audioScriptEntityId === null) throw new Error('Document AudioScript is not initialized');\n return {\n revision: this.entityRevision,\n audioScriptEntityId,\n entities: this.entities.list(),\n relations: this.relations.list(),\n };\n }\n\n private appendLog(line: string): void {\n if (this.logCapped) return;\n if (this.logs.length >= LOG_LINE_CAP || this.logBytes >= LOG_BYTE_CAP) {\n this.logs.push(LOG_TRUNCATED);\n this.logCapped = true;\n this.onLog?.(LOG_TRUNCATED);\n return;\n }\n const out =\n line.length > LOG_LINE_MAX ? `${line.slice(0, LOG_LINE_MAX - TRUNCATE_MARK.length)}${TRUNCATE_MARK}` : line;\n this.logs.push(out);\n this.logBytes += out.length;\n this.onLog?.(out);\n }\n\n private buildConsoleShim(): ConsoleShim {\n const write = (...args: unknown[]) => this.appendLog(args.map(formatLogArg).join(' '));\n return { log: write, info: write, warn: write, error: write };\n }\n}\n\nfunction applyGraphDiff(sandbox: EntitySandbox, before: EntityStoreSnapshot, after: EntityStoreSnapshot): void {\n const beforeEntities = new Map(before.entities.map((entity) => [entity.entity_id, entity]));\n const afterEntities = new Map(after.entities.map((entity) => [entity.entity_id, entity]));\n const beforeRelations = new Map(before.relations.map((relation) => [relation.relation_id, relation]));\n const afterRelations = new Map(after.relations.map((relation) => [relation.relation_id, relation]));\n\n for (const [entityId, previous] of beforeEntities) {\n const next = afterEntities.get(entityId);\n if (next != null && next.entity_kind !== previous.entity_kind) {\n throw new Error(\n `Entity id \"${entityId}\" cannot change kind from \"${previous.entity_kind}\" to \"${next.entity_kind}\"`,\n );\n }\n }\n for (const [relationId, previous] of beforeRelations) {\n const next = afterRelations.get(relationId);\n if (\n next != null &&\n (next.relation_kind !== previous.relation_kind ||\n next.endpoint_0_entity_id !== previous.endpoint_0_entity_id ||\n next.endpoint_1_entity_id !== previous.endpoint_1_entity_id)\n ) {\n throw new Error(`Relation id \"${relationId}\" cannot change kind or persisted endpoint positions`);\n }\n }\n\n const relationIdsToReplace = new Set(\n [...beforeRelations]\n .filter(([relationId, previous]) => {\n const next = afterRelations.get(relationId);\n return next != null && (!jsonEqual(previous.metadata, next.metadata) || !jsonEqual(previous.trace, next.trace));\n })\n .map(([relationId]) => relationId),\n );\n const relationIdsToUnlink = [...beforeRelations.keys()]\n .filter((relationId) => !afterRelations.has(relationId) || relationIdsToReplace.has(relationId))\n .sort();\n for (const relationId of relationIdsToUnlink) sandbox.relations.unlink({ relation_id: relationId });\n\n const entityIdsToDelete = [...beforeEntities.keys()].filter((entityId) => !afterEntities.has(entityId)).sort();\n for (const entityId of entityIdsToDelete) sandbox.entities.delete({ entity_id: entityId });\n\n const entitiesToCreate = [...afterEntities.values()]\n .filter((entity) => !beforeEntities.has(entity.entity_id))\n .sort((left, right) => left.entity_id.localeCompare(right.entity_id));\n for (const entity of entitiesToCreate) sandbox.entities.create(entity as CreateEntityInput);\n\n const entitiesToUpdate = [...afterEntities.values()]\n .filter((entity) => {\n const previous = beforeEntities.get(entity.entity_id);\n return previous != null && !jsonEqual(previous.payload, entity.payload);\n })\n .sort((left, right) => left.entity_id.localeCompare(right.entity_id));\n for (const entity of entitiesToUpdate) {\n sandbox.replaceOwnedPayload({ entity_id: entity.entity_id, payload: entity.payload });\n }\n\n const relationsToLink = [...afterRelations.values()]\n .filter((relation) => !beforeRelations.has(relation.relation_id) || relationIdsToReplace.has(relation.relation_id))\n .sort((left, right) => left.relation_id.localeCompare(right.relation_id));\n for (const relation of relationsToLink) linkRelation(sandbox, relation);\n}\n\nfunction linkRelation(sandbox: EntitySandbox, relation: SandboxRelation): void {\n if (relation.relation_kind === 'generated') {\n sandbox.relations.linkGenerated({\n relation_id: relation.relation_id,\n output_entity_id: relation.endpoint_0_entity_id,\n input_entity_id: relation.endpoint_1_entity_id,\n trace: relation.trace,\n });\n return;\n }\n if (relation.relation_kind === 'clip-anchor') {\n sandbox.relations.linkClipAnchor({\n relation_id: relation.relation_id,\n child_clip_entity_id: relation.endpoint_0_entity_id,\n host_clip_entity_id: relation.endpoint_1_entity_id,\n trace: relation.trace,\n });\n return;\n }\n if (relation.relation_kind === 'phonetic-script-render') {\n const firstIsVoice = sandbox.entities.get(relation.endpoint_0_entity_id)?.entity_kind === 'voice';\n sandbox.relations.linkPhoneticScriptRender({\n relation_id: relation.relation_id,\n output_entity_id: firstIsVoice ? relation.endpoint_0_entity_id : relation.endpoint_1_entity_id,\n phonetic_script_entity_id: firstIsVoice ? relation.endpoint_1_entity_id : relation.endpoint_0_entity_id,\n trace: relation.trace,\n });\n return;\n }\n if (relation.relation_kind === 'audio-script-source') {\n const firstIsScript = sandbox.entities.get(relation.endpoint_0_entity_id)?.entity_kind === 'audio-script';\n sandbox.relations.linkAudioScriptSource({\n relation_id: relation.relation_id,\n script_entity_id: firstIsScript ? relation.endpoint_0_entity_id : relation.endpoint_1_entity_id,\n source_entity_id: firstIsScript ? relation.endpoint_1_entity_id : relation.endpoint_0_entity_id,\n trace: relation.trace,\n });\n return;\n }\n sandbox.relations.link({\n relation_id: relation.relation_id,\n relation_kind: relation.relation_kind,\n endpoint_0_entity_id: relation.endpoint_0_entity_id,\n endpoint_1_entity_id: relation.endpoint_1_entity_id,\n metadata: relation.metadata,\n trace: relation.trace,\n } as LinkRelationInput);\n}\n\nfunction toDslRows(snapshot: EntityStoreSnapshot): EntityRelationRows {\n return {\n entities: snapshot.entities.map((entity) => ({\n entityId: createEntityId(entity.entity_id),\n entityKind: entity.entity_kind,\n payload: structuredClone(entity.payload),\n })) as EntityRow[],\n relations: snapshot.relations.map((relation) => ({\n relationId: createRelationId(relation.relation_id),\n relationKind: relation.relation_kind,\n endpoint0EntityId: createEntityId(relation.endpoint_0_entity_id),\n endpoint1EntityId: createEntityId(relation.endpoint_1_entity_id),\n metadata: structuredClone(relation.metadata),\n trace: structuredClone(relation.trace),\n })) as RelationRow[],\n };\n}\n\nfunction fromDslRows(\n rows: EntityRelationRows,\n revision: number,\n audioScriptEntityId: string | null,\n): EntityStoreSnapshot {\n return {\n revision,\n audioScriptEntityId,\n entities: rows.entities.map(\n (entity) =>\n ({\n entity_id: entity.entityId,\n entity_kind: entity.entityKind,\n payload: structuredClone(entity.payload),\n }) as SandboxEntity,\n ),\n relations: rows.relations.map(\n (relation) =>\n ({\n relation_id: relation.relationId,\n relation_kind: relation.relationKind,\n endpoint_0_entity_id: relation.endpoint0EntityId,\n endpoint_1_entity_id: relation.endpoint1EntityId,\n metadata: structuredClone(relation.metadata),\n trace: structuredClone(relation.trace),\n }) as SandboxRelation,\n ),\n };\n}\n\nfunction jsonEqual(left: unknown, right: unknown): boolean {\n if (Object.is(left, right)) return true;\n if (Array.isArray(left) || Array.isArray(right)) {\n return (\n Array.isArray(left) &&\n Array.isArray(right) &&\n left.length === right.length &&\n left.every((value, index) => jsonEqual(value, right[index]))\n );\n }\n if (!isRecord(left) || !isRecord(right)) return false;\n const leftKeys = Object.keys(left).sort();\n const rightKeys = Object.keys(right).sort();\n return (\n leftKeys.length === rightKeys.length &&\n leftKeys.every((key, index) => key === rightKeys[index] && jsonEqual(left[key], right[key]))\n );\n}\n\nfunction graphRowsEqual(left: EntityStoreSnapshot, right: EntityStoreSnapshot): boolean {\n if (\n left.revision !== right.revision ||\n left.audioScriptEntityId !== right.audioScriptEntityId ||\n left.entities.length !== right.entities.length ||\n left.relations.length !== right.relations.length\n ) {\n return false;\n }\n const rightEntities = new Map(right.entities.map((entity) => [entity.entity_id, entity]));\n const rightRelations = new Map(right.relations.map((relation) => [relation.relation_id, relation]));\n return (\n left.entities.every((entity) => jsonEqual(entity, rightEntities.get(entity.entity_id))) &&\n left.relations.every((relation) => jsonEqual(relation, rightRelations.get(relation.relation_id)))\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction formatLogArg(value: unknown): string {\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean' || value === null || value === undefined) {\n return String(value);\n }\n try {\n return JSON.stringify(value);\n } catch {\n return '[unstringifiable]';\n }\n}\n","/// <reference types=\"node\" />\n\nimport { randomUUID } from 'node:crypto';\nimport vm from 'node:vm';\nimport { parentPort, workerData } from 'node:worker_threads';\n\nimport type { JournalEntry, PartIdFactory, VideoDocument } from '@mengine/medeo-client';\n\nimport type { EntityCommand, EntityStoreSnapshot } from '../entity/entity-contract.ts';\nimport type { DomainIdFactory } from '../entity/entity-sandbox.ts';\nimport { EntityEditSandboxSession } from './entity-script-session.ts';\nimport { EditSandboxSession, type EditSandboxSessionOptions } from './script-session.ts';\n\n/**\n * Node worker entry for trusted edit scripts.\n *\n * Spawns the requested sandbox session, runs the agent script in a bare `vm`\n * context (no fetch/process/setTimeout), and streams journals + logs to the\n * host so hard timeout / OOM termination still preserves partial products.\n */\n\nexport interface WorkerData {\n document: VideoDocument;\n script: string;\n inputs?: Record<string, unknown>;\n entityState?: EntityStoreSnapshot;\n idLabel?: string;\n entityOnly?: boolean;\n}\n\ntype HostMessage =\n | { t: 'ready' }\n | { t: 'entry'; entry: JournalEntry }\n | { t: 'entity-entry'; command: EntityCommand }\n | { t: 'log'; line: string }\n | { t: 'truncate'; index: number }\n | { t: 'entity-truncate'; index: number }\n | {\n t: 'done';\n preview: string;\n opsCount: number;\n entityCommandsCount: number;\n loroUpdate?: string;\n entityBaseRevision: number;\n entityRows?: EntityStoreSnapshot;\n deletedEntityIds: readonly string[];\n deletedRelationIds: readonly string[];\n planKind: 'timeline' | 'entities';\n }\n | {\n t: 'fail';\n phase: 'parse' | 'runtime';\n error: { message: string; line?: number; column?: number; stack?: string };\n };\n\nconst data = workerData as WorkerData;\nif (parentPort == null) {\n throw new Error('worker-entry must run inside a worker_threads Worker');\n}\nconst port = parentPort;\n\nfunction post(message: HostMessage): void {\n port.postMessage(message);\n}\n\nfunction countingFactory(label: string): PartIdFactory {\n let n = 0;\n return (prefix) => `${prefix}_${label}${++n}`;\n}\n\nfunction domainIdFactory(label?: string): DomainIdFactory {\n let n = 0;\n return (prefix) => `${prefix}_${label == null ? randomUUID() : `${label}${++n}`}`;\n}\n\n/** Extract script line/column from the first `agent-script.js` stack frame. */\nfunction positionFromError(\n error: unknown,\n script?: string,\n phase?: 'parse' | 'runtime',\n): { line?: number; column?: number; stack?: string; message: string } {\n // Duck-type: vm SyntaxError in a worker may fail `instanceof Error` across realms.\n const obj = error != null && typeof error === 'object' ? (error as Record<string, unknown>) : null;\n const message =\n obj != null && typeof obj.message === 'string'\n ? obj.message\n : error instanceof Error\n ? error.message\n : String(error);\n const stack = obj != null && typeof obj.stack === 'string' ? obj.stack : undefined;\n\n let line = typeof obj?.lineNumber === 'number' ? obj.lineNumber : undefined;\n let column = typeof obj?.columnNumber === 'number' ? obj.columnNumber : undefined;\n\n if (stack != null) {\n // Prefer the header form `agent-script.js:N` (SyntaxError) or `agent-script.js:N:M`.\n const match = /agent-script\\.js:(\\d+)(?::(\\d+))?/.exec(stack);\n if (match != null) {\n line = Number(match[1]);\n if (match[2] != null) column = Number(match[2]);\n }\n }\n\n // Parse-phase refinement: V8 often points at the token after an unclosed\n // `{`/`(`/`[`; walk back one line when the previous line ends that way so\n // the reported line matches the agent-authored incomplete construct.\n if (phase === 'parse' && script != null && line != null && line >= 2) {\n const lines = script.split('\\n');\n const prev = lines[line - 2];\n if (prev != null && /[{([]\\s*$/.test(prev)) {\n line = line - 1;\n column = prev.length;\n }\n }\n\n return { message, line, column, stack };\n}\n\nasync function main(): Promise<void> {\n const idFactory = data.idLabel != null ? countingFactory(data.idLabel) : undefined;\n const options: EditSandboxSessionOptions = {\n idFactory,\n entityState: data.entityState,\n domainIdFactory: domainIdFactory(data.idLabel),\n onEntry: (entry) => post({ t: 'entry', entry }),\n onEntityCommand: (command) => post({ t: 'entity-entry', command }),\n onLog: (line) => post({ t: 'log', line }),\n onTruncate: (index) => post({ t: 'truncate', index }),\n onEntityTruncate: (index) => post({ t: 'entity-truncate', index }),\n };\n const session = data.entityOnly\n ? new EntityEditSandboxSession(data.document, options)\n : new EditSandboxSession(data.document, options);\n\n // Prelude stays on the same physical line as script line 1 so stack line\n // numbers map 1:1 onto the agent script (no leading newline).\n const wrapped = `(async (edit, timeline, entities, relations, checkpoint, rollbackTo, inputs, console) => {${data.script}\\n})`;\n\n const ctx = vm.createContext(Object.create(null) as Record<string, unknown>);\n\n let run: unknown;\n try {\n run = vm.runInContext(wrapped, ctx, { filename: 'agent-script.js' });\n } catch (error) {\n const pos = positionFromError(error, data.script, 'parse');\n post({ t: 'fail', phase: 'parse', error: pos });\n return;\n }\n\n if (typeof run !== 'function') {\n post({\n t: 'fail',\n phase: 'runtime',\n error: { message: 'agent script wrapper did not evaluate to a function' },\n });\n return;\n }\n\n try {\n const invoke = run as (\n edit: typeof session.edit,\n timeline: typeof session.timeline,\n entities: typeof session.entities,\n relations: typeof session.relations,\n checkpoint: typeof session.checkpoint,\n rollbackTo: typeof session.rollbackTo,\n inputs: Record<string, unknown>,\n console: typeof session.console,\n ) => Promise<unknown>;\n // Signal host that cold start is done; timeout wall-clock starts here.\n post({ t: 'ready' });\n await invoke(\n session.edit,\n session.timeline,\n session.entities,\n session.relations,\n session.checkpoint,\n session.rollbackTo,\n data.inputs ?? {},\n session.console,\n );\n } catch (error) {\n const pos = positionFromError(error, data.script, 'runtime');\n post({ t: 'fail', phase: 'runtime', error: pos });\n return;\n }\n\n const plan = session.buildPlan('');\n post({\n t: 'done',\n ...(plan.loro_update ? { loroUpdate: plan.loro_update } : {}),\n preview: plan.preview,\n opsCount: plan.ops.length,\n entityCommandsCount: plan.entity_commands.length,\n entityBaseRevision: plan.entity_base_revision,\n ...(plan.entity_rows !== undefined ? { entityRows: plan.entity_rows } : {}),\n deletedEntityIds: plan.deleted_entity_ids ?? [],\n deletedRelationIds: plan.deleted_relation_ids ?? [],\n planKind: plan.plan_kind,\n });\n}\n\nmain().catch((error: unknown) => {\n const pos = positionFromError(error);\n post({ t: 'fail', phase: 'runtime', error: pos });\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"worker-entry.mjs","names":[],"sources":["../src/entity/entity-asset.ts","../src/sandbox/entity-script-session.ts","../src/sandbox/worker-entry.ts"],"sourcesContent":["import type { JsonValue, SandboxEntity } from './entity-contract.ts';\n\n/** Immutable resource content resolved by the host for a document Entity. */\nexport interface EntityAssetContent {\n assetId: string;\n content: JsonValue;\n}\n\n/** Host-only I/O. The sandbox supplies an Entity, never an arbitrary Asset query. */\nexport type EntityAssetLoader = (docId: string, entity: SandboxEntity) => Promise<EntityAssetContent>;\n\nexport interface CaptionAssetSegment {\n text: string;\n start_time_ms: number;\n end_time_ms: number;\n}\n\n/** MCAP's output_caption JSON contract; unknown formats never become invented captions. */\nexport function captionAssetSegments(content: JsonValue): CaptionAssetSegment[] {\n if (!content || typeof content !== 'object' || Array.isArray(content) || !Array.isArray(content.segments))\n throw new Error('Caption Asset must contain an output_caption object with segments');\n if (!content.segments.length) throw new Error('Caption Asset contains no speech segments');\n return content.segments.map((value) => {\n if (\n !value ||\n typeof value !== 'object' ||\n Array.isArray(value) ||\n typeof value.text !== 'string' ||\n !value.text.trim() ||\n !Number.isSafeInteger(value.start_time_ms) ||\n !Number.isSafeInteger(value.end_time_ms) ||\n (value.start_time_ms as number) < 0 ||\n (value.end_time_ms as number) <= (value.start_time_ms as number)\n )\n throw new Error('Caption Asset has invalid text or millisecond timing');\n return { text: value.text, start_time_ms: value.start_time_ms as number, end_time_ms: value.end_time_ms as number };\n });\n}\n","import {\n LoroEntityDocument,\n projectEntityTimeline,\n base64ToBytes,\n bytesToBase64,\n assertCanonicalEditorResources,\n assertMediaAssetWritePolicy,\n type VideoDocument,\n} from '@mengine/medeo-client';\nimport { type EntityRelationRows } from '@mengine/medeo-dsl';\n\nimport { captionAssetSegments, type EntityAssetContent } from '../entity/entity-asset.ts';\nimport type {\n BusinessEntityFacade,\n BusinessRelationFacade,\n EntitySandboxCheckpoint,\n SandboxEntity,\n} from '../entity/entity-contract.ts';\nimport { EntitySandbox, toDslRows, type DomainIdFactory } from '../entity/entity-sandbox.ts';\nimport { businessFacades } from './business-facades.ts';\nimport type { ChangePlan, ConsoleShim, EditSandboxSessionOptions } from './script-session.ts';\n\nconst LOG_LINE_MAX = 2000;\nconst LOG_LINE_CAP = 1000;\nconst LOG_BYTE_CAP = 64 * 1024;\nconst TRUNCATE_MARK = '[truncated]';\nconst LOG_TRUNCATED = '[log truncated]';\n\n/** Entity/relation script session; compilation retains the ordered operation journal. */\nexport class EntityEditSandboxSession {\n private readonly document: VideoDocument;\n private readonly entitySandbox: EntitySandbox;\n private readonly baseRows: EntityRelationRows;\n private readonly domainIdFactory: DomainIdFactory;\n private readonly logs: string[] = [];\n private readonly onLog: ((line: string) => void) | undefined;\n private logBytes = 0;\n private readonly resolvedCaptionAssets = new Set<string>();\n private logCapped = false;\n\n readonly entities: BusinessEntityFacade;\n readonly relations: BusinessRelationFacade;\n readonly console: ConsoleShim;\n readonly checkpoint: () => EntitySandboxCheckpoint;\n readonly rollbackTo: (cp: EntitySandboxCheckpoint) => void;\n\n constructor(document: VideoDocument, options?: EditSandboxSessionOptions) {\n this.document = structuredClone(document);\n this.baseRows = toDslRows(\n options?.entityState ?? { revision: 0, audioScriptEntityId: null, entities: [], relations: [] },\n );\n this.domainIdFactory =\n options?.domainIdFactory ??\n (() => {\n throw new Error('Entity id factory is unavailable in this sandbox host');\n });\n this.onLog = options?.onLog;\n this.entitySandbox = new EntitySandbox({\n state: options?.entityState,\n idFactory: this.domainIdFactory,\n onCommand: options?.onEntityCommand,\n onTruncate: options?.onEntityTruncate,\n });\n\n const business = businessFacades(this.entitySandbox.entities, this.entitySandbox.relations);\n this.entities = business.entities;\n this.relations = business.relations;\n this.console = this.buildConsoleShim();\n const checkpoints = new Map<EntitySandboxCheckpoint, number>();\n this.checkpoint = () => {\n const token = Object.freeze({}) as EntitySandboxCheckpoint;\n checkpoints.set(token, this.entitySandbox.commandCount);\n return token;\n };\n this.rollbackTo = (cp) => {\n const index = checkpoints.get(cp);\n if (index === undefined) throw new Error('Invalid or expired sandbox checkpoint');\n this.entitySandbox.rollbackTo(index);\n let later = false;\n for (const token of checkpoints.keys()) {\n if (later) checkpoints.delete(token);\n if (token === cp) later = true;\n }\n };\n }\n\n /** Resolve only the resource attached to this Entity; I/O remains in the parent host. */\n async rgetAssetFromEntity(\n entityId: string,\n load: (entity: SandboxEntity) => Promise<EntityAssetContent>,\n ): Promise<EntityAssetContent> {\n const entity = this.entitySandbox.entities.get(entityId);\n if (!entity || entity.entity_kind === 'asset') throw new Error(`Business Entity not found: ${entityId}`);\n const external = entity.payload.external;\n if (!external || typeof external !== 'object' || Array.isArray(external) || typeof external.key !== 'string')\n throw new Error(`Entity ${entityId} has no attached Asset`);\n const assetId = external.key;\n const result = await load(entity);\n if (result.assetId !== external.key) throw new Error('Host returned a different Entity Asset');\n const current = this.entitySandbox.entities.get(entityId);\n if (!current || JSON.stringify(current.payload.external) !== JSON.stringify(external))\n throw new Error('Entity resource changed during its read');\n if (entity.entity_kind === 'caption' && !this.initializedAsset(entityId, external.key)) {\n const timed = captionAssetSegments(result.content);\n const scriptId = this.entitySandbox.audioScriptEntityId;\n if (!scriptId) throw new Error('Project AudioScript is missing');\n const script = this.entitySandbox.entities.get(scriptId)!;\n const segments = timed.map((segment, index) => ({\n segmentId: `asset:${assetId}:${index}`,\n text: segment.text,\n }));\n const existing = script.payload.segments as { segmentId: string; text: string }[];\n const additions = segments.filter((segment) => !existing.some((item) => item.segmentId === segment.segmentId));\n this.entitySandbox.entities.update({ entity_id: scriptId, payload: { segments: [...existing, ...additions] } });\n const ranges = timed.map((segment, index) => ({\n segmentId: segments[index]!.segmentId,\n startMs: segment.start_time_ms,\n endMs: segment.end_time_ms,\n }));\n this.entitySandbox.entities.update({\n entity_id: entityId,\n payload: {\n baseEntityIds: [scriptId],\n selections: segments.map(({ segmentId }) => ({ segmentId })),\n extent: {\n kind: 'bounded',\n start: Math.min(...ranges.map((r) => r.startMs)),\n end: Math.max(...ranges.map((r) => r.endMs)),\n },\n sampling: 'native',\n coordinateSpace: 'milliseconds',\n segmentRanges: ranges,\n },\n });\n const markerId = this.entitySandbox.entities.create({\n entity_kind: 'sequence-marker',\n payload: {\n sourceRange: {\n start: Math.min(...ranges.map((r) => r.startMs)),\n end: Math.max(...ranges.map((r) => r.endMs)),\n },\n duration: { mode: 'from-source' },\n segmentRanges: ranges,\n },\n });\n this.entitySandbox.relations.link({\n relation_kind: 'audio-script-marker',\n endpoint_0_entity_id: scriptId,\n endpoint_1_entity_id: markerId,\n });\n }\n this.resolvedCaptionAssets.add(`${entityId}:${assetId}`);\n return result;\n }\n\n private initializedAsset(entityId: string, assetId: string): boolean {\n const current = this.entitySandbox.entities.get(entityId);\n if (\n this.resolvedCaptionAssets.has(`${entityId}:${assetId}`) &&\n Array.isArray(current?.payload.selections) &&\n Array.isArray(current?.payload.baseEntityIds)\n )\n return true;\n const baseline = this.baseRows.entities.find((row) => row.entityId === entityId);\n const external = baseline?.payload.external;\n return (\n !!external &&\n typeof external === 'object' &&\n !Array.isArray(external) &&\n 'key' in external &&\n external.key === assetId\n );\n }\n\n /** Finish unawaited Caption initialization before validation; no partial rows are published. */\n async prepareEntityAssets(load: (entity: SandboxEntity) => Promise<EntityAssetContent>): Promise<void> {\n for (const entity of this.entitySandbox.entities.list()) {\n const external = entity.payload.external;\n if (\n entity.entity_kind === 'caption' &&\n external &&\n typeof external === 'object' &&\n !Array.isArray(external) &&\n typeof external.key === 'string' &&\n !this.initializedAsset(entity.entity_id, external.key)\n )\n await this.rgetAssetFromEntity(entity.entity_id, load);\n }\n }\n\n buildPlan(baseVersion: string): ChangePlan {\n const entityPlan = this.entitySandbox.buildPlan();\n assertCanonicalEditorResources(toDslRows(entityPlan.rows));\n assertMediaAssetWritePolicy(this.baseRows, toDslRows(entityPlan.rows));\n if (entityPlan.rows.loroSnapshot) projectEntityTimeline(toDslRows(entityPlan.rows), this.document.meta);\n return {\n ...(entityPlan.rows.loroSnapshot\n ? {\n loro_update: bytesToBase64(this.compileJournal(entityPlan)),\n }\n : {}),\n plan_kind: 'entities',\n doc_id: this.document.meta.draft_id ?? '',\n base_version: baseVersion,\n ops: [],\n entity_base_revision: entityPlan.base_revision,\n entity_commands: entityPlan.commands,\n entity_rows: entityPlan.rows,\n deleted_entity_ids: entityPlan.deleted_entity_ids,\n deleted_relation_ids: entityPlan.deleted_relation_ids,\n preview: this.entitySandbox.renderPreview(),\n logs: this.logs.slice(),\n };\n }\n\n private compileJournal(plan: ReturnType<EntitySandbox['buildPlan']>): Uint8Array {\n const editor = LoroEntityDocument.fromSnapshot(base64ToBytes(plan.rows.loroSnapshot!), (state) => {\n assertCanonicalEditorResources(state.rows);\n projectEntityTimeline(state.rows, this.document.meta);\n });\n return editor.transact((draft) => {\n for (const command of plan.commands) {\n switch (command.kind) {\n case 'create-entity': {\n const rows = toDslRows({\n revision: 0,\n audioScriptEntityId: null,\n entities: [command.entity],\n relations: [],\n });\n draft.create(rows.entities[0]!);\n if (command.entity.entity_kind === 'audio-script')\n draft.attach('audioScriptEntityId', command.entity.entity_id);\n break;\n }\n case 'update-entity':\n draft.replaceOwned(command.entity_id, command.payload);\n break;\n case 'change-entity':\n draft.change(command.entity_id, command.changes);\n break;\n case 'delete-entity':\n draft.delete(command.entity_id);\n break;\n case 'link-relation': {\n const rows = toDslRows({\n revision: 0,\n audioScriptEntityId: null,\n entities: [],\n relations: [command.relation],\n });\n draft.link(rows.relations[0]!);\n break;\n }\n case 'change-relation':\n draft.changeRelation(command.relation_id, command.changes);\n break;\n case 'unlink-relation':\n draft.unlink(command.relation_id);\n break;\n }\n }\n draft.reconcileOrder(toDslRows(plan.rows));\n });\n }\n\n getLogs(): readonly string[] {\n return this.logs;\n }\n\n private appendLog(line: string): void {\n if (this.logCapped) return;\n if (this.logs.length >= LOG_LINE_CAP || this.logBytes >= LOG_BYTE_CAP) {\n this.logs.push(LOG_TRUNCATED);\n this.logCapped = true;\n this.onLog?.(LOG_TRUNCATED);\n return;\n }\n const out =\n line.length > LOG_LINE_MAX ? `${line.slice(0, LOG_LINE_MAX - TRUNCATE_MARK.length)}${TRUNCATE_MARK}` : line;\n this.logs.push(out);\n this.logBytes += out.length;\n this.onLog?.(out);\n }\n\n private buildConsoleShim(): ConsoleShim {\n const write = (...args: unknown[]) => this.appendLog(args.map(formatLogArg).join(' '));\n return { log: write, info: write, warn: write, error: write };\n }\n}\n\nfunction formatLogArg(value: unknown): string {\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean' || value === null || value === undefined) {\n return String(value);\n }\n try {\n return JSON.stringify(value);\n } catch {\n return '[unstringifiable]';\n }\n}\n","/// <reference types=\"node\" />\nimport { randomUUID } from 'node:crypto';\nimport vm from 'node:vm';\nimport { parentPort, workerData } from 'node:worker_threads';\n\nimport type { JournalEntry, PartIdFactory, VideoDocument } from '@mengine/medeo-client';\n\nimport type { EntityAssetContent } from '../entity/entity-asset.ts';\nimport type { SandboxEntity } from '../entity/entity-contract.ts';\nimport type { EntityCommand, EntityStoreSnapshot } from '../entity/entity-contract.ts';\nimport type { DomainIdFactory } from '../entity/entity-sandbox.ts';\nimport { EntityEditSandboxSession } from './entity-script-session.ts';\nimport { type EditSandboxSessionOptions } from './script-session.ts';\n\n/**\n * Node worker entry for trusted edit scripts.\n *\n * Spawns the requested sandbox session, runs the agent script in a bare `vm`\n * context (no fetch/process/setTimeout), and streams journals + logs to the\n * host so hard timeout / OOM termination still preserves partial products.\n */\n\nexport interface WorkerData {\n document: VideoDocument;\n script: string;\n inputs?: Record<string, unknown>;\n entityState?: EntityStoreSnapshot;\n idLabel?: string;\n}\n\ntype HostMessage =\n | { t: 'entity-asset'; requestId: number; entity: SandboxEntity }\n | { t: 'ready' }\n | { t: 'entry'; entry: JournalEntry }\n | { t: 'entity-entry'; command: EntityCommand }\n | { t: 'log'; line: string }\n | { t: 'truncate'; index: number }\n | { t: 'entity-truncate'; index: number }\n | {\n t: 'done';\n preview: string;\n opsCount: number;\n entityCommandsCount: number;\n loroUpdate?: string;\n entityBaseRevision: number;\n entityRows?: EntityStoreSnapshot;\n deletedEntityIds: readonly string[];\n deletedRelationIds: readonly string[];\n planKind: 'timeline' | 'entities';\n }\n | {\n t: 'fail';\n phase: 'parse' | 'runtime';\n error: { message: string; line?: number; column?: number; stack?: string };\n };\n\nconst data = workerData as WorkerData;\nif (parentPort == null) {\n throw new Error('worker-entry must run inside a worker_threads Worker');\n}\nconst port = parentPort;\nlet requestId = 0;\nconst pending = new Map<number, { resolve: (result: EntityAssetContent) => void; reject: (error: Error) => void }>();\nport.on('message', (message: { t: string; requestId: number; result: EntityAssetContent; error?: string }) => {\n if (message.t !== 'entity-asset-result') return;\n const waiter = pending.get(message.requestId);\n pending.delete(message.requestId);\n if (message.error) waiter?.reject(new Error(message.error));\n else waiter?.resolve(message.result);\n});\nfunction loadEntityAsset(entity: SandboxEntity): Promise<EntityAssetContent> {\n return new Promise((resolve, reject) => {\n const id = ++requestId;\n pending.set(id, { resolve, reject });\n port.postMessage({ t: 'entity-asset', requestId: id, entity });\n });\n}\n\nfunction post(message: HostMessage): void {\n port.postMessage(message);\n}\n\nfunction countingFactory(label: string): PartIdFactory {\n let n = 0;\n return (prefix) => `${prefix}_${label}${++n}`;\n}\n\nfunction domainIdFactory(label?: string): DomainIdFactory {\n let n = 0;\n return (prefix) => `${prefix}_${label == null ? randomUUID() : `${label}${++n}`}`;\n}\n\n/** Extract script line/column from the first `agent-script.js` stack frame. */\nfunction positionFromError(\n error: unknown,\n script?: string,\n phase?: 'parse' | 'runtime',\n): { line?: number; column?: number; stack?: string; message: string } {\n // Duck-type: vm SyntaxError in a worker may fail `instanceof Error` across realms.\n const obj = error != null && typeof error === 'object' ? (error as Record<string, unknown>) : null;\n const message =\n obj != null && typeof obj.message === 'string'\n ? obj.message\n : error instanceof Error\n ? error.message\n : String(error);\n const stack = obj != null && typeof obj.stack === 'string' ? obj.stack : undefined;\n\n let line = typeof obj?.lineNumber === 'number' ? obj.lineNumber : undefined;\n let column = typeof obj?.columnNumber === 'number' ? obj.columnNumber : undefined;\n\n if (stack != null) {\n // Prefer the header form `agent-script.js:N` (SyntaxError) or `agent-script.js:N:M`.\n const match = /agent-script\\.js:(\\d+)(?::(\\d+))?/.exec(stack);\n if (match != null) {\n line = Number(match[1]);\n if (match[2] != null) column = Number(match[2]);\n }\n }\n\n // Parse-phase refinement: V8 often points at the token after an unclosed\n // `{`/`(`/`[`; walk back one line when the previous line ends that way so\n // the reported line matches the agent-authored incomplete construct.\n if (phase === 'parse' && script != null && line != null && line >= 2) {\n const lines = script.split('\\n');\n const prev = lines[line - 2];\n if (prev != null && /[{([]\\s*$/.test(prev)) {\n line = line - 1;\n column = prev.length;\n }\n }\n\n return { message, line, column, stack };\n}\n\nasync function main(): Promise<void> {\n const idFactory = data.idLabel != null ? countingFactory(data.idLabel) : undefined;\n const options: EditSandboxSessionOptions = {\n idFactory,\n entityState: data.entityState,\n domainIdFactory: domainIdFactory(data.idLabel),\n onEntry: (entry) => post({ t: 'entry', entry }),\n onEntityCommand: (command) => post({ t: 'entity-entry', command }),\n onLog: (line) => post({ t: 'log', line }),\n onTruncate: (index) => post({ t: 'truncate', index }),\n onEntityTruncate: (index) => post({ t: 'entity-truncate', index }),\n };\n const session = new EntityEditSandboxSession(data.document, options);\n\n // Prelude stays on the same physical line as script line 1 so stack line\n // numbers map 1:1 onto the agent script (no leading newline).\n const wrapped = `(async (entities, relations, checkpoint, rollbackTo, inputs, console, rgetAssetFromEntity) => {${data.script}\\n})`;\n\n const ctx = vm.createContext(Object.create(null) as Record<string, unknown>);\n\n let run: unknown;\n try {\n run = vm.runInContext(wrapped, ctx, { filename: 'agent-script.js' });\n } catch (error) {\n const pos = positionFromError(error, data.script, 'parse');\n post({ t: 'fail', phase: 'parse', error: pos });\n return;\n }\n\n if (typeof run !== 'function') {\n post({\n t: 'fail',\n phase: 'runtime',\n error: { message: 'agent script wrapper did not evaluate to a function' },\n });\n return;\n }\n\n try {\n const invoke = run as (\n entities: typeof session.entities,\n relations: typeof session.relations,\n checkpoint: typeof session.checkpoint,\n rollbackTo: typeof session.rollbackTo,\n inputs: Record<string, unknown>,\n console: typeof session.console,\n rgetAssetFromEntity: (entityId: string) => Promise<EntityAssetContent>,\n ) => Promise<unknown>;\n // Signal host that cold start is done; timeout wall-clock starts here.\n post({ t: 'ready' });\n await invoke(\n session.entities,\n session.relations,\n session.checkpoint,\n session.rollbackTo,\n data.inputs ?? {},\n session.console,\n (entityId) => {\n return session.rgetAssetFromEntity(entityId, loadEntityAsset);\n },\n );\n await session.prepareEntityAssets(loadEntityAsset);\n } catch (error) {\n const pos = positionFromError(error, data.script, 'runtime');\n post({ t: 'fail', phase: 'runtime', error: pos });\n return;\n }\n\n const plan = session.buildPlan('');\n post({\n t: 'done',\n ...(plan.loro_update ? { loroUpdate: plan.loro_update } : {}),\n preview: plan.preview,\n opsCount: plan.ops.length,\n entityCommandsCount: plan.entity_commands.length,\n entityBaseRevision: plan.entity_base_revision,\n ...(plan.entity_rows !== undefined ? { entityRows: plan.entity_rows } : {}),\n deletedEntityIds: plan.deleted_entity_ids ?? [],\n deletedRelationIds: plan.deleted_relation_ids ?? [],\n planKind: plan.plan_kind,\n });\n}\n\nmain().catch((error: unknown) => {\n const pos = positionFromError(error);\n post({ t: 'fail', phase: 'runtime', error: pos });\n});\n"],"mappings":"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"}