@xpert-ai/plugin-motion 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/.xpertai-plugin/plugin.json +16 -6
  2. package/README.md +19 -5
  3. package/assets/upstream/UPSTREAM-BASELINE.md +27 -0
  4. package/assets/upstream/app/data/reicon-LICENSE.txt +21 -0
  5. package/dist/assets/upstream/UPSTREAM-BASELINE.md +27 -0
  6. package/dist/assets/upstream/app/data/reicon-LICENSE.txt +21 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +18 -9
  10. package/dist/index.js.map +1 -1
  11. package/dist/lib/constants.d.ts +7 -1
  12. package/dist/lib/constants.d.ts.map +1 -1
  13. package/dist/lib/constants.js +7 -0
  14. package/dist/lib/constants.js.map +1 -1
  15. package/dist/lib/entities/motion-export.entity.d.ts +13 -1
  16. package/dist/lib/entities/motion-export.entity.d.ts.map +1 -1
  17. package/dist/lib/entities/motion-export.entity.js +49 -1
  18. package/dist/lib/entities/motion-export.entity.js.map +1 -1
  19. package/dist/lib/entities/motion-project-version.entity.d.ts +3 -1
  20. package/dist/lib/entities/motion-project-version.entity.d.ts.map +1 -1
  21. package/dist/lib/entities/motion-project-version.entity.js +8 -0
  22. package/dist/lib/entities/motion-project-version.entity.js.map +1 -1
  23. package/dist/lib/entities/motion-project.entity.d.ts +4 -1
  24. package/dist/lib/entities/motion-project.entity.d.ts.map +1 -1
  25. package/dist/lib/entities/motion-project.entity.js +8 -0
  26. package/dist/lib/entities/motion-project.entity.js.map +1 -1
  27. package/dist/lib/hyperframes-composition.d.ts +16 -0
  28. package/dist/lib/hyperframes-composition.d.ts.map +1 -0
  29. package/dist/lib/hyperframes-composition.js +106 -0
  30. package/dist/lib/hyperframes-composition.js.map +1 -0
  31. package/dist/lib/motion-agent-response.d.ts +8 -0
  32. package/dist/lib/motion-agent-response.d.ts.map +1 -1
  33. package/dist/lib/motion-agent-response.js +13 -0
  34. package/dist/lib/motion-agent-response.js.map +1 -1
  35. package/dist/lib/motion-render.processor.d.ts +9 -0
  36. package/dist/lib/motion-render.processor.d.ts.map +1 -0
  37. package/dist/lib/motion-render.processor.js +25 -0
  38. package/dist/lib/motion-render.processor.js.map +1 -0
  39. package/dist/lib/motion-view.provider.d.ts.map +1 -1
  40. package/dist/lib/motion-view.provider.js +31 -2
  41. package/dist/lib/motion-view.provider.js.map +1 -1
  42. package/dist/lib/motion.middleware.d.ts.map +1 -1
  43. package/dist/lib/motion.middleware.js +20 -6
  44. package/dist/lib/motion.middleware.js.map +1 -1
  45. package/dist/lib/motion.plugin.d.ts.map +1 -1
  46. package/dist/lib/motion.plugin.js +2 -1
  47. package/dist/lib/motion.plugin.js.map +1 -1
  48. package/dist/lib/motion.service.d.ts +174 -3
  49. package/dist/lib/motion.service.d.ts.map +1 -1
  50. package/dist/lib/motion.service.js +376 -19
  51. package/dist/lib/motion.service.js.map +1 -1
  52. package/dist/lib/remote-components/motion-workbench/app.css +1 -1
  53. package/dist/lib/remote-components/motion-workbench/app.js +553 -38
  54. package/dist/lib/types.d.ts +26 -1
  55. package/dist/lib/types.d.ts.map +1 -1
  56. package/dist/lib/types.js.map +1 -1
  57. package/dist/sandbox-actions/hyperframes-render/action.json +10 -0
  58. package/dist/sandbox-actions/hyperframes-render/bundle/runner.mjs +188 -0
  59. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@esbuild/darwin-arm64/README.md +3 -0
  60. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@esbuild/darwin-arm64/bin/esbuild +0 -0
  61. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@esbuild/darwin-arm64/package.json +20 -0
  62. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@esbuild/linux-x64/README.md +3 -0
  63. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@esbuild/linux-x64/bin/esbuild +0 -0
  64. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@esbuild/linux-x64/package.json +20 -0
  65. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/LICENSE +190 -0
  66. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/README.md +156 -0
  67. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/dist/distributed.js +156122 -0
  68. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/dist/hyperframe.manifest.json +22 -0
  69. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/dist/hyperframe.runtime.iife.js +356 -0
  70. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/dist/index.js +169375 -0
  71. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/dist/public-server.js +167618 -0
  72. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/dist/services/healthWorkerThread.js +53 -0
  73. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/dist/services/shaderTransitionWorker.js +606 -0
  74. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/package.json +109 -0
  75. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/esbuild/LICENSE.md +21 -0
  76. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/esbuild/README.md +3 -0
  77. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/esbuild/bin/esbuild +0 -0
  78. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/esbuild/install.js +289 -0
  79. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/esbuild/lib/main.js +2532 -0
  80. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/esbuild/package.json +49 -0
  81. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/LICENSE +20 -0
  82. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/README.md +36 -0
  83. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/async/index.browser.cjs +42 -0
  84. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/async/index.browser.js +42 -0
  85. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/async/index.cjs +44 -0
  86. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/async/index.js +44 -0
  87. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/async/index.native.js +35 -0
  88. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/async/package.json +12 -0
  89. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/bin/nanoid.cjs +55 -0
  90. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/index.browser.cjs +44 -0
  91. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/index.browser.js +44 -0
  92. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/index.cjs +57 -0
  93. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/index.d.cts +91 -0
  94. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/index.js +62 -0
  95. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/nanoid.js +1 -0
  96. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/non-secure/index.cjs +24 -0
  97. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/non-secure/index.js +24 -0
  98. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/non-secure/package.json +6 -0
  99. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/package.json +89 -0
  100. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/url-alphabet/index.cjs +4 -0
  101. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/url-alphabet/index.js +4 -0
  102. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/url-alphabet/package.json +6 -0
  103. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/picocolors/LICENSE +15 -0
  104. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/picocolors/README.md +21 -0
  105. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/picocolors/package.json +25 -0
  106. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/picocolors/picocolors.browser.js +4 -0
  107. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/picocolors/picocolors.js +75 -0
  108. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/LICENSE +20 -0
  109. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/README.md +25 -0
  110. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/at-rule.js +25 -0
  111. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/comment.js +13 -0
  112. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/container.js +447 -0
  113. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/css-syntax-error.js +133 -0
  114. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/declaration.js +24 -0
  115. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/document.js +33 -0
  116. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/fromJSON.js +68 -0
  117. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/input.js +276 -0
  118. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/lazy-result.js +563 -0
  119. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/list.js +58 -0
  120. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/map-generator.js +376 -0
  121. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/no-work-result.js +137 -0
  122. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/node.js +456 -0
  123. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/parse.js +42 -0
  124. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/parser.js +618 -0
  125. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/postcss.d.mts +66 -0
  126. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/postcss.js +101 -0
  127. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/postcss.mjs +30 -0
  128. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/previous-map.js +161 -0
  129. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/processor.js +67 -0
  130. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/result.js +42 -0
  131. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/root.js +61 -0
  132. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/rule.js +27 -0
  133. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/stringifier.js +374 -0
  134. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/stringify.js +11 -0
  135. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/symbols.js +5 -0
  136. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/terminal-highlight.js +70 -0
  137. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/tokenize.js +270 -0
  138. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/warn-once.js +13 -0
  139. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/warning.js +37 -0
  140. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/package.json +88 -0
  141. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/LICENSE +28 -0
  142. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/README.md +765 -0
  143. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/array-set.js +121 -0
  144. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/base64-vlq.js +140 -0
  145. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/base64.js +67 -0
  146. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/binary-search.js +111 -0
  147. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/mapping-list.js +79 -0
  148. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/quick-sort.js +132 -0
  149. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/source-map-consumer.js +1188 -0
  150. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/source-map-generator.js +444 -0
  151. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/source-node.js +413 -0
  152. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/util.js +594 -0
  153. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/package.json +71 -0
  154. package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/source-map.js +8 -0
  155. package/dist/xpert-motion-assistant.yaml +240 -37
  156. package/package.json +17 -9
  157. package/skills/motion-agent-skill/SKILL.md +19 -7
@@ -1,23 +1,25 @@
1
1
  import { __decorate, __metadata, __param } from "tslib";
2
- import { BadRequestException, Inject, Injectable, NotFoundException, Optional } from '@nestjs/common';
2
+ import { BadRequestException, ConflictException, Inject, Injectable, NotFoundException, Optional, ServiceUnavailableException } from '@nestjs/common';
3
3
  import { InjectRepository } from '@nestjs/typeorm';
4
4
  import { createHash, randomUUID } from 'node:crypto';
5
5
  import { Repository } from 'typeorm';
6
- import { XPERT_RUNTIME_CAPABILITIES_TOKEN } from '@xpert-ai/plugin-sdk';
6
+ import { MANAGED_QUEUE_SERVICE_TOKEN, SandboxJobsRuntimeCapability, SYSTEM_GLOBAL_SCOPE, WorkspaceFilesRuntimeCapability, XPERT_RUNTIME_CAPABILITIES_TOKEN } from '@xpert-ai/plugin-sdk';
7
+ import { MOTION_PLUGIN_NAME, MOTION_RENDER_JOB, MOTION_RENDER_QUEUE, MOTION_RENDER_SANDBOX_ACTION, MOTION_RENDER_SANDBOX_ACTION_VERSION } from './constants.js';
7
8
  import { MotionActionLog, MotionExport, MotionProject, MotionProjectVersion, MotionStyle } from './entities/index.js';
8
- import { MOTION_WORKSPACE_FILES_RUNTIME_CAPABILITY } from './types.js';
9
9
  import { compactJson, normalizeJsonObject, normalizeStringArray, stableJson } from './json-utils.js';
10
10
  import { exportTextArtifact, injectMotionRuntime, validateHtmlArtifact } from './html-motion.js';
11
- import { createStarterVideoComposition, validateVideoComposition } from './video-composition.js';
11
+ import { validateVideoComposition } from './video-composition.js';
12
+ import { createStarterHyperframesComposition, readHyperframesCompositionMetadata, validateHyperframesComposition } from './hyperframes-composition.js';
12
13
  import { getMotionRecipeDetail, loadDesignSystemsCount, loadMotionRecipes, readJsonDataFile, readMotionSpec, searchMotionRecipes } from './recipe-library.js';
13
14
  let MotionService = class MotionService {
14
- constructor(projectRepository, versionRepository, styleRepository, exportRepository, logRepository, runtimeCapabilities) {
15
+ constructor(projectRepository, versionRepository, styleRepository, exportRepository, logRepository, runtimeCapabilities, managedQueue) {
15
16
  this.projectRepository = projectRepository;
16
17
  this.versionRepository = versionRepository;
17
18
  this.styleRepository = styleRepository;
18
19
  this.exportRepository = exportRepository;
19
20
  this.logRepository = logRepository;
20
21
  this.runtimeCapabilities = runtimeCapabilities;
22
+ this.managedQueue = managedQueue;
21
23
  }
22
24
  getLibraryStats() {
23
25
  const htmlTemplates = readJsonDataFile('html-templates.json');
@@ -66,12 +68,21 @@ let MotionService = class MotionService {
66
68
  async createProject(scope, input) {
67
69
  const title = normalizeRequired(input.title, 'Motion project title is required.');
68
70
  const surface = normalizeSurface(input.surface);
69
- const html = input.html ? injectMotionRuntime(input.html) : surface === 'web' ? starterHtml(title, input.brief) : null;
71
+ const html = surface === 'web'
72
+ ? input.html ? injectMotionRuntime(input.html) : starterHtml(title, input.brief)
73
+ : null;
74
+ if (surface === 'video' && input.videoComposition && input.hyperframesHtml) {
75
+ throw new BadRequestException('Choose either a HyperFrames composition or a legacy Canvas composition.');
76
+ }
77
+ const videoEngine = surface === 'video'
78
+ ? input.videoComposition ? 'legacy_canvas' : 'hyperframes'
79
+ : null;
70
80
  const videoComposition = input.videoComposition !== undefined && input.videoComposition !== null
71
81
  ? validateVideoComposition(input.videoComposition)
72
- : surface === 'video'
73
- ? createStarterVideoComposition(title)
74
- : null;
82
+ : null;
83
+ const hyperframesHtml = surface === 'video' && videoEngine === 'hyperframes'
84
+ ? validateHyperframesComposition(input.hyperframesHtml ?? createStarterHyperframesComposition(title, input.brief))
85
+ : null;
75
86
  const project = await this.projectRepository.save(this.projectRepository.create({
76
87
  ...scopedCreate(scope),
77
88
  createdById: scope.userId ?? null,
@@ -85,10 +96,12 @@ let MotionService = class MotionService {
85
96
  motionProfile: normalizeOptional(input.motionProfile),
86
97
  selectedRecipeIds: normalizeStringArray(input.selectedRecipeIds ?? []),
87
98
  workingHtml: html,
99
+ videoEngine,
88
100
  videoComposition,
101
+ hyperframesHtml,
89
102
  currentVersionNumber: 0,
90
103
  workingCopyRevision: 1,
91
- artifactChecksum: checksumArtifact(surface, html, videoComposition),
104
+ artifactChecksum: checksumArtifact(surface, html, videoEngine, hyperframesHtml, videoComposition),
92
105
  lastEditedById: scope.userId ?? null,
93
106
  lastEditedAt: new Date()
94
107
  }));
@@ -124,6 +137,8 @@ let MotionService = class MotionService {
124
137
  item: compactProject(project),
125
138
  workingCopy: {
126
139
  html: project.workingHtml ?? null,
140
+ videoEngine: resolveProjectVideoEngine(project),
141
+ hyperframesHtml: project.hyperframesHtml ?? null,
127
142
  videoComposition: project.videoComposition ?? null,
128
143
  componentSelection: project.componentSelection ?? null,
129
144
  layerSelection: project.layerSelection ?? null,
@@ -141,11 +156,13 @@ let MotionService = class MotionService {
141
156
  const html = injectMotionRuntime(input.html);
142
157
  project.surface = 'web';
143
158
  project.workingHtml = html;
159
+ project.videoEngine = null;
160
+ project.hyperframesHtml = null;
144
161
  project.videoComposition = null;
145
162
  project.selectedRecipeIds = normalizeStringArray(input.selectedRecipeIds ?? project.selectedRecipeIds ?? []);
146
163
  project.componentSelection = normalizeJsonObject(input.componentSelection);
147
164
  project.workingCopyRevision = (project.workingCopyRevision ?? 0) + 1;
148
- project.artifactChecksum = checksumArtifact('web', html, null);
165
+ project.artifactChecksum = checksumArtifact('web', html, null, null, null);
149
166
  project.lastEditedById = scope.userId ?? null;
150
167
  project.lastEditedAt = new Date();
151
168
  const saved = await this.projectRepository.save(project);
@@ -167,11 +184,13 @@ let MotionService = class MotionService {
167
184
  const composition = validateVideoComposition(input.composition);
168
185
  project.surface = 'video';
169
186
  project.workingHtml = null;
187
+ project.videoEngine = 'legacy_canvas';
188
+ project.hyperframesHtml = null;
170
189
  project.videoComposition = composition;
171
190
  project.selectedRecipeIds = normalizeStringArray(input.selectedRecipeIds ?? project.selectedRecipeIds ?? []);
172
191
  project.layerSelection = normalizeJsonObject(input.layerSelection);
173
192
  project.workingCopyRevision = (project.workingCopyRevision ?? 0) + 1;
174
- project.artifactChecksum = checksumArtifact('video', null, composition);
193
+ project.artifactChecksum = checksumArtifact('video', null, 'legacy_canvas', null, composition);
175
194
  project.lastEditedById = scope.userId ?? null;
176
195
  project.lastEditedAt = new Date();
177
196
  const saved = await this.projectRepository.save(project);
@@ -188,11 +207,38 @@ let MotionService = class MotionService {
188
207
  project: compactProject(saved)
189
208
  };
190
209
  }
210
+ async saveHyperframesComposition(scope, input) {
211
+ const project = await this.requireProject(scope, input.projectId);
212
+ const html = validateHyperframesComposition(input.html);
213
+ project.surface = 'video';
214
+ project.workingHtml = null;
215
+ project.videoEngine = 'hyperframes';
216
+ project.hyperframesHtml = html;
217
+ project.videoComposition = null;
218
+ project.layerSelection = null;
219
+ project.selectedRecipeIds = normalizeStringArray(input.selectedRecipeIds ?? project.selectedRecipeIds ?? []);
220
+ project.workingCopyRevision = (project.workingCopyRevision ?? 0) + 1;
221
+ project.artifactChecksum = checksumArtifact('video', null, 'hyperframes', html, null);
222
+ project.lastEditedById = scope.userId ?? null;
223
+ project.lastEditedAt = new Date();
224
+ const saved = await this.projectRepository.save(project);
225
+ await this.writeLog(scope, {
226
+ motionProjectId: saved.id,
227
+ action: 'hyperframes_composition_saved',
228
+ actorType: scope.assistantId ? 'agent' : 'user',
229
+ message: input.changeSummary ?? 'HyperFrames composition was saved.',
230
+ snapshot: { selectedRecipeIds: saved.selectedRecipeIds, bytes: Buffer.byteLength(html), engine: 'hyperframes' }
231
+ });
232
+ return { success: true, message: 'HyperFrames composition was saved.', project: compactProject(saved) };
233
+ }
191
234
  async finalizeVersion(scope, input) {
192
235
  const project = await this.requireProject(scope, input.projectId);
193
236
  if (project.surface === 'web') {
194
237
  validateHtmlArtifact(project.workingHtml ?? '');
195
238
  }
239
+ else if (resolveProjectVideoEngine(project) === 'hyperframes') {
240
+ validateHyperframesComposition(project.hyperframesHtml ?? '');
241
+ }
196
242
  else {
197
243
  validateVideoComposition(project.videoComposition);
198
244
  }
@@ -205,10 +251,12 @@ let MotionService = class MotionService {
205
251
  sourceType: input.sourceType ?? inferVersionSource(scope, project.surface),
206
252
  surface: project.surface ?? 'web',
207
253
  html: project.workingHtml ?? null,
254
+ videoEngine: resolveProjectVideoEngine(project),
255
+ hyperframesHtml: project.hyperframesHtml ?? null,
208
256
  videoComposition: project.videoComposition ?? null,
209
257
  selectedRecipeIds: normalizeStringArray(project.selectedRecipeIds ?? []),
210
258
  selectionSummary: project.surface === 'web' ? project.componentSelection ?? null : project.layerSelection ?? null,
211
- artifactChecksum: project.artifactChecksum ?? checksumArtifact(project.surface ?? 'web', project.workingHtml, project.videoComposition),
259
+ artifactChecksum: project.artifactChecksum ?? checksumArtifact(project.surface ?? 'web', project.workingHtml, resolveProjectVideoEngine(project), project.hyperframesHtml, project.videoComposition),
212
260
  changeSummary: normalizeOptional(input.changeSummary) ?? `Motion version ${versionNumber}`,
213
261
  workspaceCatalog: project.workspaceCatalog,
214
262
  workspaceScopeId: project.workspaceScopeId,
@@ -246,6 +294,8 @@ let MotionService = class MotionService {
246
294
  }
247
295
  project.surface = version.surface ?? project.surface;
248
296
  project.workingHtml = version.html ?? null;
297
+ project.videoEngine = resolveVersionVideoEngine(version);
298
+ project.hyperframesHtml = version.hyperframesHtml ?? null;
249
299
  project.videoComposition = version.videoComposition ?? null;
250
300
  project.selectedRecipeIds = normalizeStringArray(version.selectedRecipeIds ?? []);
251
301
  project.currentVersionId = version.id;
@@ -270,10 +320,242 @@ let MotionService = class MotionService {
270
320
  version: compactVersion(version)
271
321
  };
272
322
  }
323
+ async getProductionRenderCapability() {
324
+ const sandbox = this.sandboxJobs(false);
325
+ if (!sandbox)
326
+ return unavailableRenderCapability('PROVIDER_UNAVAILABLE', 'Platform Sandbox Jobs capability is unavailable.');
327
+ const action = await sandbox.getActionHealth({
328
+ pluginName: MOTION_PLUGIN_NAME,
329
+ action: MOTION_RENDER_SANDBOX_ACTION,
330
+ actionVersion: MOTION_RENDER_SANDBOX_ACTION_VERSION
331
+ }).catch((error) => ({
332
+ available: false,
333
+ reason: 'PROFILE_UNHEALTHY',
334
+ message: errorMessage(error)
335
+ }));
336
+ if (!action.available) {
337
+ return unavailableRenderCapability(action.reason ?? 'PROFILE_UNHEALTHY', action.message ?? 'Motion render Action is unavailable.');
338
+ }
339
+ if (!this.managedQueue)
340
+ return unavailableRenderCapability('WORKER_UNAVAILABLE', 'Managed Queue is unavailable.');
341
+ const pool = await this.managedQueue.getExecutionPoolHealth({ executionPool: 'sandbox-browser' }).catch((error) => ({
342
+ executionPool: 'sandbox-browser',
343
+ available: false,
344
+ workerCount: 0,
345
+ warning: errorMessage(error)
346
+ }));
347
+ return pool.available
348
+ ? {
349
+ available: true,
350
+ backend: 'sandbox-job',
351
+ action: MOTION_RENDER_SANDBOX_ACTION,
352
+ actionVersion: MOTION_RENDER_SANDBOX_ACTION_VERSION,
353
+ runtimeProfile: action.runtimeProfile ?? null,
354
+ sandboxRuntimeVersion: action.sandboxRuntimeVersion ?? null,
355
+ workerCount: pool.workerCount
356
+ }
357
+ : unavailableRenderCapability('WORKER_UNAVAILABLE', pool.warning ?? 'No worker is consuming sandbox-browser.');
358
+ }
359
+ async requestProductionRender(scope, input) {
360
+ const project = await this.requireProject(scope, input.projectId);
361
+ if (project.surface !== 'video' || resolveProjectVideoEngine(project) !== 'hyperframes') {
362
+ throw new BadRequestException('Production rendering requires a native HyperFrames video project.');
363
+ }
364
+ const hyperframesHtml = validateHyperframesComposition(project.hyperframesHtml ?? '');
365
+ const inputChecksum = project.artifactChecksum ?? checksumArtifact('video', null, 'hyperframes', hyperframesHtml, null);
366
+ if (input.expectedChecksum && input.expectedChecksum !== inputChecksum) {
367
+ throw new ConflictException('Motion composition changed; reload before starting production render.');
368
+ }
369
+ const capability = await this.getProductionRenderCapability();
370
+ if ('reason' in capability)
371
+ throw new ServiceUnavailableException(`${capability.reason}: ${capability.message}`);
372
+ if (!this.managedQueue)
373
+ throw new ServiceUnavailableException('Managed Queue is required for production render.');
374
+ const kind = normalizeRenderKind(input.kind);
375
+ const quality = normalizeRenderQuality(input.quality);
376
+ const fps = normalizeRenderFps(input.fps);
377
+ const metadata = readHyperframesCompositionMetadata(hyperframesHtml);
378
+ const record = await this.exportRepository.save(this.exportRepository.create({
379
+ ...scopedCreate(scope),
380
+ projectId: scope.projectId ?? project.projectId ?? null,
381
+ motionProjectId: requireId(project.id),
382
+ versionId: project.currentVersionId ?? null,
383
+ kind,
384
+ status: 'queued',
385
+ backend: 'hyperframes',
386
+ progress: 0,
387
+ stage: 'queued',
388
+ inputChecksum,
389
+ mimeType: mimeTypeForKind(kind),
390
+ changeSummary: normalizeOptional(input.changeSummary),
391
+ report: { quality, fps, fileName: sanitizeFileName(input.fileName ?? `${project.title}.${kind}`), ...metadata },
392
+ createdById: scope.userId ?? null,
393
+ assistantId: scope.assistantId ?? null,
394
+ conversationId: scope.conversationId ?? null
395
+ }));
396
+ const exportId = requireId(record.id);
397
+ const queued = await this.managedQueue.enqueue({
398
+ pluginName: MOTION_PLUGIN_NAME,
399
+ queueName: MOTION_RENDER_QUEUE,
400
+ jobName: MOTION_RENDER_JOB,
401
+ payload: { exportId, tenantId: scope.tenantId, organizationId: scope.organizationId ?? null },
402
+ tenantId: scope.tenantId,
403
+ organizationId: scope.organizationId,
404
+ scopeKey: SYSTEM_GLOBAL_SCOPE,
405
+ userId: scope.userId,
406
+ jobId: `motion-render-${exportId}`,
407
+ attempts: 1,
408
+ executionPool: 'sandbox-browser',
409
+ removeOnComplete: { age: 24 * 60 * 60, count: 100 },
410
+ removeOnFail: { age: 7 * 24 * 60 * 60, count: 100 }
411
+ }).catch(async (error) => {
412
+ record.status = 'failed';
413
+ record.stage = 'queueing';
414
+ record.errorMessage = errorMessage(error);
415
+ record.completedAt = new Date();
416
+ await this.exportRepository.save(record);
417
+ throw error;
418
+ });
419
+ record.jobId = queued.jobId;
420
+ await this.exportRepository.save(record);
421
+ await this.writeLog(scope, {
422
+ motionProjectId: project.id,
423
+ versionId: project.currentVersionId,
424
+ action: 'production_render_queued',
425
+ actorType: scope.assistantId ? 'agent' : 'user',
426
+ message: input.changeSummary ?? `Queued ${kind.toUpperCase()} production render.`,
427
+ snapshot: { exportId, jobId: queued.jobId, kind, quality, fps, engine: 'hyperframes', inputChecksum }
428
+ });
429
+ return { success: true, message: 'HyperFrames production render was queued.', export: compactExport(record), capability };
430
+ }
431
+ async processProductionRender(job) {
432
+ const data = job.data;
433
+ const scope = { tenantId: data.tenantId, organizationId: data.organizationId ?? null };
434
+ const record = await this.exportRepository.findOne({ where: scopedWhere(scope, { id: data.exportId }) });
435
+ if (!record || record.status === 'cancelled' || record.status === 'succeeded')
436
+ return;
437
+ const project = await this.requireProject(scope, record.motionProjectId);
438
+ if (resolveProjectVideoEngine(project) !== 'hyperframes')
439
+ throw new BadRequestException('Motion render source is not HyperFrames.');
440
+ const html = validateHyperframesComposition(project.hyperframesHtml ?? '');
441
+ const checksum = checksumArtifact('video', null, 'hyperframes', html, null);
442
+ if (checksum !== record.inputChecksum)
443
+ throw new ConflictException('Motion render source checksum changed after queueing.');
444
+ const settings = renderSettings(record.report);
445
+ const outputPath = `motion.${record.kind}`;
446
+ record.status = 'running';
447
+ record.stage = 'sandbox-starting';
448
+ record.progress = 10;
449
+ record.sandboxJobId = requireId(record.id);
450
+ record.errorMessage = null;
451
+ await this.exportRepository.save(record);
452
+ try {
453
+ const result = await this.sandboxJobs().run({
454
+ jobId: requireId(record.id),
455
+ action: MOTION_RENDER_SANDBOX_ACTION,
456
+ actionVersion: MOTION_RENDER_SANDBOX_ACTION_VERSION,
457
+ idempotencyKey: `motion-hyperframes:${record.id}:${checksum}`,
458
+ scope: {
459
+ tenantId: scope.tenantId,
460
+ organizationId: scope.organizationId,
461
+ userId: record.createdById ?? null,
462
+ pluginName: MOTION_PLUGIN_NAME,
463
+ businessResourceType: 'motion-render',
464
+ businessResourceId: requireId(record.id)
465
+ },
466
+ payload: {
467
+ compositionHtml: html,
468
+ kind: record.kind,
469
+ quality: settings.quality,
470
+ fps: settings.fps
471
+ },
472
+ files: [],
473
+ outputs: [{
474
+ path: outputPath,
475
+ originalName: settings.fileName,
476
+ mimeType: mimeTypeForKind(record.kind),
477
+ destination: {
478
+ ...resolveProjectWorkspaceScope({ ...scope, userId: record.createdById }, project),
479
+ folder: buildMotionExportFolder(requireId(project.id))
480
+ }
481
+ }, {
482
+ path: 'report.json',
483
+ originalName: `${settings.fileName}.report.json`,
484
+ mimeType: 'application/json',
485
+ destination: {
486
+ ...resolveProjectWorkspaceScope({ ...scope, userId: record.createdById }, project),
487
+ folder: `${buildMotionExportFolder(requireId(project.id))}/reports`
488
+ }
489
+ }],
490
+ timeoutMs: 20 * 60_000
491
+ });
492
+ const output = result.outputs.find((candidate) => candidate.path === outputPath);
493
+ if (!output)
494
+ throw new Error(`HyperFrames render did not return ${outputPath}.`);
495
+ record.status = 'succeeded';
496
+ record.stage = 'complete';
497
+ record.progress = 100;
498
+ record.sandboxJobId = result.id;
499
+ record.filePath = output.workspacePath ?? null;
500
+ record.fileReference = output.reference;
501
+ record.fileUrl = output.fileUrl ?? null;
502
+ record.mimeType = output.mimeType;
503
+ record.size = output.size;
504
+ record.checksum = output.sha256;
505
+ record.errorMessage = null;
506
+ record.completedAt = new Date();
507
+ record.report = {
508
+ ...settings,
509
+ renderer: '@hyperframes/producer',
510
+ action: result.action,
511
+ actionVersion: result.actionVersion,
512
+ runtimeProfile: result.runtimeProfile,
513
+ sandboxRuntimeVersion: result.sandboxRuntimeVersion,
514
+ sandboxJobId: result.id,
515
+ attempt: result.attempt
516
+ };
517
+ await this.exportRepository.save(record);
518
+ project.lastExportKind = record.kind;
519
+ project.lastExportPath = output.workspacePath ?? output.fileUrl ?? project.lastExportPath;
520
+ await this.projectRepository.save(project);
521
+ await this.writeLog(scope, {
522
+ motionProjectId: project.id,
523
+ versionId: record.versionId,
524
+ action: 'production_render_completed',
525
+ message: `Completed ${record.kind.toUpperCase()} production render.`,
526
+ snapshot: { exportId: record.id, sandboxJobId: result.id, size: output.size, checksum: output.sha256 }
527
+ });
528
+ }
529
+ catch (error) {
530
+ record.status = 'failed';
531
+ record.stage = 'failed';
532
+ record.errorMessage = errorMessage(error);
533
+ record.completedAt = new Date();
534
+ await this.exportRepository.save(record);
535
+ await this.writeLog(scope, {
536
+ motionProjectId: project.id,
537
+ versionId: record.versionId,
538
+ action: 'production_render_failed',
539
+ message: 'HyperFrames production render failed.',
540
+ errorMessage: record.errorMessage,
541
+ snapshot: { exportId: record.id, inputChecksum: record.inputChecksum }
542
+ });
543
+ throw error;
544
+ }
545
+ }
273
546
  async exportArtifact(scope, input) {
274
547
  const project = await this.requireProject(scope, input.projectId);
275
548
  const kind = normalizeExportKind(input.kind);
276
549
  if (kind === 'mp4' || kind === 'gif') {
550
+ if (project.surface === 'video' && resolveProjectVideoEngine(project) === 'hyperframes') {
551
+ return this.requestProductionRender(scope, {
552
+ projectId: input.projectId,
553
+ kind,
554
+ fileName: input.fileName,
555
+ expectedChecksum: project.artifactChecksum,
556
+ changeSummary: input.changeSummary
557
+ });
558
+ }
277
559
  return {
278
560
  success: true,
279
561
  message: 'Use the Motion Workbench browser exporter for MP4/GIF, then save the generated file through save_export_file.',
@@ -332,7 +614,7 @@ let MotionService = class MotionService {
332
614
  }
333
615
  async saveMediaFile(scope, input) {
334
616
  const project = await this.requireProject(scope, input.projectId);
335
- const workspaceFiles = this.runtimeCapabilities?.get(MOTION_WORKSPACE_FILES_RUNTIME_CAPABILITY);
617
+ const workspaceFiles = this.runtimeCapabilities?.get(WorkspaceFilesRuntimeCapability);
336
618
  if (!workspaceFiles) {
337
619
  throw new BadRequestException('Motion media upload requires platform workspace file storage.');
338
620
  }
@@ -509,9 +791,15 @@ let MotionService = class MotionService {
509
791
  }
510
792
  return project;
511
793
  }
794
+ sandboxJobs(required = true) {
795
+ const jobs = this.runtimeCapabilities?.get(SandboxJobsRuntimeCapability);
796
+ if (!jobs && required)
797
+ throw new ServiceUnavailableException('Platform Sandbox Jobs capability is unavailable.');
798
+ return jobs;
799
+ }
512
800
  async saveExportBuffer(scope, input) {
513
801
  const checksum = sha256(input.buffer);
514
- const workspaceFiles = this.runtimeCapabilities?.get(MOTION_WORKSPACE_FILES_RUNTIME_CAPABILITY);
802
+ const workspaceFiles = this.runtimeCapabilities?.get(WorkspaceFilesRuntimeCapability);
515
803
  let workspaceFile = null;
516
804
  if (workspaceFiles) {
517
805
  workspaceFile = await workspaceFiles.uploadBuffer({
@@ -539,6 +827,10 @@ let MotionService = class MotionService {
539
827
  motionProjectId: requireId(input.project.id),
540
828
  versionId: input.versionId ?? null,
541
829
  kind: input.kind,
830
+ status: 'succeeded',
831
+ backend: 'browser',
832
+ progress: 100,
833
+ stage: 'complete',
542
834
  filePath: workspaceFile?.filePath ?? null,
543
835
  fileUrl: workspaceFile?.fileUrl ?? workspaceFile?.url ?? null,
544
836
  mimeType: input.mimeType,
@@ -549,7 +841,8 @@ let MotionService = class MotionService {
549
841
  changeSummary: normalizeOptional(input.changeSummary),
550
842
  createdById: scope.userId ?? null,
551
843
  assistantId: scope.assistantId ?? null,
552
- conversationId: scope.conversationId ?? null
844
+ conversationId: scope.conversationId ?? null,
845
+ completedAt: new Date()
553
846
  }));
554
847
  input.project.lastExportKind = input.kind;
555
848
  input.project.lastExportPath = workspaceFile?.filePath ?? input.project.lastExportPath;
@@ -588,11 +881,13 @@ MotionService = __decorate([
588
881
  __param(4, InjectRepository(MotionActionLog)),
589
882
  __param(5, Optional()),
590
883
  __param(5, Inject(XPERT_RUNTIME_CAPABILITIES_TOKEN)),
884
+ __param(6, Optional()),
885
+ __param(6, Inject(MANAGED_QUEUE_SERVICE_TOKEN)),
591
886
  __metadata("design:paramtypes", [Repository,
592
887
  Repository,
593
888
  Repository,
594
889
  Repository,
595
- Repository, Object])
890
+ Repository, Object, Object])
596
891
  ], MotionService);
597
892
  export { MotionService };
598
893
  function scopedCreate(scope) {
@@ -619,6 +914,7 @@ function compactProject(project) {
619
914
  title: project.title,
620
915
  brief: project.brief ?? null,
621
916
  surface: project.surface ?? 'web',
917
+ videoEngine: resolveProjectVideoEngine(project),
622
918
  status: project.status ?? 'draft',
623
919
  designSystemId: project.designSystemId ?? null,
624
920
  motionProfile: project.motionProfile ?? null,
@@ -640,6 +936,7 @@ function compactVersion(version) {
640
936
  versionNumber: version.versionNumber,
641
937
  sourceType: version.sourceType ?? 'workbench',
642
938
  surface: version.surface ?? 'web',
939
+ videoEngine: resolveVersionVideoEngine(version),
643
940
  selectedRecipeIds: version.selectedRecipeIds ?? [],
644
941
  artifactChecksum: version.artifactChecksum ?? null,
645
942
  changeSummary: version.changeSummary ?? null,
@@ -651,11 +948,21 @@ function compactExport(record) {
651
948
  id: record.id,
652
949
  versionId: record.versionId ?? null,
653
950
  kind: record.kind,
951
+ status: record.status ?? 'succeeded',
952
+ backend: record.backend ?? 'browser',
953
+ progress: record.progress ?? 100,
954
+ stage: record.stage ?? null,
955
+ jobId: record.jobId ?? null,
956
+ sandboxJobId: record.sandboxJobId ?? null,
957
+ inputChecksum: record.inputChecksum ?? null,
654
958
  filePath: record.filePath ?? null,
959
+ fileReference: record.fileReference ?? null,
655
960
  fileUrl: record.fileUrl ?? null,
656
961
  mimeType: record.mimeType ?? null,
657
962
  size: record.size ?? null,
658
963
  checksum: record.checksum ?? null,
964
+ errorMessage: record.errorMessage ?? null,
965
+ report: record.report ?? null,
659
966
  changeSummary: record.changeSummary ?? null,
660
967
  createdAt: record.createdAt?.toISOString?.() ?? null
661
968
  };
@@ -709,8 +1016,58 @@ function normalizeExportKind(kind) {
709
1016
  }
710
1017
  throw new BadRequestException('Unsupported Motion export kind.');
711
1018
  }
712
- function checksumArtifact(surface, html, composition) {
713
- return sha256(Buffer.from(surface === 'web' ? html ?? '' : stableJson(composition), 'utf8'));
1019
+ function checksumArtifact(surface, html, videoEngine, hyperframesHtml, composition) {
1020
+ const source = surface === 'web'
1021
+ ? `web\n${html ?? ''}`
1022
+ : videoEngine === 'hyperframes'
1023
+ ? `video\nhyperframes\n${hyperframesHtml ?? ''}`
1024
+ : `video\nlegacy_canvas\n${stableJson(composition)}`;
1025
+ return sha256(Buffer.from(source, 'utf8'));
1026
+ }
1027
+ function resolveProjectVideoEngine(project) {
1028
+ if (project.surface !== 'video')
1029
+ return null;
1030
+ // All video rows written before the discriminator existed used Canvas/WebCodecs.
1031
+ return project.videoEngine === 'hyperframes' ? 'hyperframes' : 'legacy_canvas';
1032
+ }
1033
+ function resolveVersionVideoEngine(version) {
1034
+ if (version.surface !== 'video')
1035
+ return null;
1036
+ return version.videoEngine === 'hyperframes' ? 'hyperframes' : 'legacy_canvas';
1037
+ }
1038
+ function normalizeRenderKind(kind) {
1039
+ if (!kind || kind === 'mp4')
1040
+ return 'mp4';
1041
+ if (kind === 'gif')
1042
+ return 'gif';
1043
+ throw new BadRequestException('Production render kind must be MP4 or GIF.');
1044
+ }
1045
+ function normalizeRenderQuality(quality) {
1046
+ if (!quality || quality === 'standard')
1047
+ return 'standard';
1048
+ if (quality === 'draft' || quality === 'high')
1049
+ return quality;
1050
+ throw new BadRequestException('Unsupported HyperFrames render quality.');
1051
+ }
1052
+ function normalizeRenderFps(fps) {
1053
+ if (fps === undefined || fps === 30)
1054
+ return 30;
1055
+ if (fps === 24 || fps === 60)
1056
+ return fps;
1057
+ throw new BadRequestException('HyperFrames render fps must be 24, 30, or 60.');
1058
+ }
1059
+ function unavailableRenderCapability(reason, message) {
1060
+ return { available: false, backend: 'sandbox-job', reason, message };
1061
+ }
1062
+ function renderSettings(report) {
1063
+ return {
1064
+ quality: normalizeRenderQuality(typeof report?.quality === 'string' ? report.quality : undefined),
1065
+ fps: normalizeRenderFps(typeof report?.fps === 'number' ? report.fps : undefined),
1066
+ fileName: sanitizeFileName(typeof report?.fileName === 'string' ? report.fileName : 'motion.mp4')
1067
+ };
1068
+ }
1069
+ function errorMessage(error) {
1070
+ return error instanceof Error && error.message ? error.message : String(error);
714
1071
  }
715
1072
  function sha256(buffer) {
716
1073
  return createHash('sha256').update(buffer).digest('hex');