@volter/editor-core 0.5.59 → 0.5.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/BUNDLED_NOTICES CHANGED
@@ -31,7 +31,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31
31
  SOFTWARE.
32
32
 
33
33
 
34
- @volter/editor-project@0.5.59
34
+ @volter/editor-project@0.5.61
35
35
  Declared license: Apache-2.0
36
36
 
37
37
  --- packages/editor-project/LICENSE ---
@@ -240,7 +240,7 @@ Declared license: Apache-2.0
240
240
  limitations under the License.
241
241
 
242
242
 
243
- @volter/editor-sdk@0.5.59
243
+ @volter/editor-sdk@0.5.61
244
244
  Declared license: Apache-2.0
245
245
 
246
246
  --- packages/editor-sdk/LICENSE ---
@@ -461,7 +461,7 @@ The project output-root policy is Apache-2.0, transferred from the original
461
461
  SDK; its exact source is recorded in provenance/editor-server.json.
462
462
 
463
463
 
464
- @volter/editor-threejs@0.5.59
464
+ @volter/editor-threejs@0.5.61
465
465
  Declared license: AGPL-3.0-only AND Apache-2.0
466
466
 
467
467
  --- packages/editor-threejs/LICENSE ---
@@ -77339,11 +77339,12 @@ function createControlPlane(router, ctx) {
77339
77339
  if (typeof type === "string") {
77340
77340
  const contributed = contributedCommandTimeouts.get(type);
77341
77341
  if (contributed !== void 0) return contributed;
77342
+ if (!isRelayCommandType(type)) return RELAY_DELIVERY_MAX_WAIT_MS;
77342
77343
  }
77343
77344
  return relayCommandTimeoutMs(type);
77344
77345
  }
77345
77346
  function commandAckDeadlineMs(type) {
77346
- if (typeof type === "string" && contributedCommandTimeouts.has(type))
77347
+ if (typeof type === "string" && !isRelayCommandType(type))
77347
77348
  return commandTimeoutMs(type) > RELAY_DELIVERY_ACK_MS ? RELAY_DELIVERY_ACK_MS : null;
77348
77349
  return relayCommandAckDeadlineMs(type);
77349
77350
  }
@@ -77365,6 +77366,10 @@ function createControlPlane(router, ctx) {
77365
77366
  }
77366
77367
  contributedCommandTimeouts.clear();
77367
77368
  for (const [type, timeoutMs] of next) contributedCommandTimeouts.set(type, timeoutMs);
77369
+ for (const pending of pendingCommands.values()) {
77370
+ const type = pending.command?.["type"];
77371
+ if (typeof type === "string" && next.has(type)) pending.refreshWorkTimer?.();
77372
+ }
77368
77373
  return CONTROL_OK;
77369
77374
  }
77370
77375
  function armDeliveryReceiptWindow(requestId, type) {
@@ -78089,7 +78094,8 @@ data: ${data}
78089
78094
  }
78090
78095
  const requestId = randomUUID11();
78091
78096
  const commandWithId = { ...body, _requestId: requestId };
78092
- const timeoutMs = commandTimeoutMs(body["type"]);
78097
+ let timeoutMs = commandTimeoutMs(body["type"]);
78098
+ let workStartedAt = Date.now();
78093
78099
  const hasSeparateDeliveryBudget = commandAckDeadlineMs(body["type"]) !== null;
78094
78100
  const resultPromise = new Promise((resolve37) => {
78095
78101
  const expire = () => {
@@ -78128,18 +78134,24 @@ data: ${data}
78128
78134
  });
78129
78135
  };
78130
78136
  const timer = setTimeout(expire, timeoutMs);
78137
+ const refreshWorkTimer = () => {
78138
+ const pending = pendingCommands.get(requestId);
78139
+ if (pending === void 0) return;
78140
+ timeoutMs = commandTimeoutMs(body["type"]);
78141
+ clearTimeout(pending.timer);
78142
+ pending.timer = setTimeout(expire, Math.max(0, workStartedAt + timeoutMs - Date.now()));
78143
+ };
78131
78144
  pendingCommands.set(requestId, {
78132
78145
  resolve: resolve37,
78133
78146
  timer,
78134
78147
  tabId: targetTabId,
78135
78148
  command: commandWithId,
78149
+ refreshWorkTimer,
78136
78150
  epochAtRelay: table?.tab(targetTabId)?.epochCount ?? 0,
78137
78151
  ...hasSeparateDeliveryBudget ? {
78138
78152
  restartWorkTimerAfterReceipt: () => {
78139
- const pending = pendingCommands.get(requestId);
78140
- if (pending === void 0) return;
78141
- clearTimeout(pending.timer);
78142
- pending.timer = setTimeout(expire, timeoutMs);
78153
+ workStartedAt = Date.now();
78154
+ refreshWorkTimer();
78143
78155
  }
78144
78156
  } : {}
78145
78157
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@volter/editor-core",
3
3
  "author": "Volter AI, Inc.",
4
- "version": "0.5.59",
4
+ "version": "0.5.61",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -82,9 +82,9 @@
82
82
  "three.quarks": "0.16.0",
83
83
  "typescript": "5.9.3",
84
84
  "zod": "4.3.6",
85
- "@volter/editor-project": "0.5.59",
86
- "@volter/editor-sdk": "0.5.59",
87
- "@volter/editor-threejs": "0.5.59",
85
+ "@volter/editor-project": "0.5.61",
86
+ "@volter/editor-sdk": "0.5.61",
87
+ "@volter/editor-threejs": "0.5.61",
88
88
  "vite": "^6.4.3",
89
89
  "express": "^5.2.1",
90
90
  "chokidar": "^5.0.0",
@@ -31,6 +31,7 @@ import {
31
31
  parseEditorControlLifecycle,
32
32
  } from '@volter/editor-sdk/session/editor-control-lifecycle';
33
33
  import { COMMAND_RESULT_RECEIPT_EVENT } from '@volter/editor-sdk/session/editor-control-protocol';
34
+ import { isRelayCommandType } from '@volter/editor-sdk/session/command-table';
34
35
  import type { Request, Response } from 'express';
35
36
  import {
36
37
  ABNORMAL_SOCKET_CLOSE,
@@ -358,6 +359,8 @@ export function createControlPlane(router: EditorServerRouter, ctx: RouteContext
358
359
  * restarts it so time spent queued behind a blocked main thread cannot
359
360
  * consume the work budget before the page begins the work. */
360
361
  restartWorkTimerAfterReceipt?: () => void;
362
+ /** Adopt a late contribution's declared budget without resetting elapsed work. */
363
+ refreshWorkTimer?: () => void;
361
364
  controller?: EditorEventClient;
362
365
  controllerClientId?: string;
363
366
  }
@@ -460,11 +463,14 @@ export function createControlPlane(router: EditorServerRouter, ctx: RouteContext
460
463
  if (typeof type === 'string') {
461
464
  const contributed = contributedCommandTimeouts.get(type);
462
465
  if (contributed !== undefined) return contributed;
466
+ // The listener can receive a package command before discovery reports
467
+ // its row. Allow bounded discovery, then adopt its real work budget.
468
+ if (!isRelayCommandType(type)) return RELAY_DELIVERY_MAX_WAIT_MS;
463
469
  }
464
470
  return relayCommandTimeoutMs(type);
465
471
  }
466
472
  function commandAckDeadlineMs(type: unknown): number | null {
467
- if (typeof type === 'string' && contributedCommandTimeouts.has(type))
473
+ if (typeof type === 'string' && !isRelayCommandType(type))
468
474
  return commandTimeoutMs(type) > RELAY_DELIVERY_ACK_MS ? RELAY_DELIVERY_ACK_MS : null;
469
475
  return relayCommandAckDeadlineMs(type);
470
476
  }
@@ -486,6 +492,10 @@ export function createControlPlane(router: EditorServerRouter, ctx: RouteContext
486
492
  }
487
493
  contributedCommandTimeouts.clear();
488
494
  for (const [type, timeoutMs] of next) contributedCommandTimeouts.set(type, timeoutMs);
495
+ for (const pending of pendingCommands.values()) {
496
+ const type = pending.command?.['type'];
497
+ if (typeof type === 'string' && next.has(type)) pending.refreshWorkTimer?.();
498
+ }
489
499
  return CONTROL_OK;
490
500
  }
491
501
 
@@ -1703,7 +1713,8 @@ export function createControlPlane(router: EditorServerRouter, ctx: RouteContext
1703
1713
  const requestId = randomUUID();
1704
1714
  const commandWithId = { ...body, _requestId: requestId };
1705
1715
 
1706
- const timeoutMs = commandTimeoutMs(body['type']);
1716
+ let timeoutMs = commandTimeoutMs(body['type']);
1717
+ let workStartedAt = Date.now();
1707
1718
  const hasSeparateDeliveryBudget = commandAckDeadlineMs(body['type']) !== null;
1708
1719
  const resultPromise = new Promise<SettledCommandResult>((resolve) => {
1709
1720
  const expire = (): void => {
@@ -1752,19 +1763,25 @@ export function createControlPlane(router: EditorServerRouter, ctx: RouteContext
1752
1763
  });
1753
1764
  };
1754
1765
  const timer = setTimeout(expire, timeoutMs);
1766
+ const refreshWorkTimer = (): void => {
1767
+ const pending = pendingCommands.get(requestId);
1768
+ if (pending === undefined) return;
1769
+ timeoutMs = commandTimeoutMs(body['type']);
1770
+ clearTimeout(pending.timer);
1771
+ pending.timer = setTimeout(expire, Math.max(0, workStartedAt + timeoutMs - Date.now()));
1772
+ };
1755
1773
  pendingCommands.set(requestId, {
1756
1774
  resolve,
1757
1775
  timer,
1758
1776
  tabId: targetTabId,
1759
1777
  command: commandWithId,
1778
+ refreshWorkTimer,
1760
1779
  epochAtRelay: table?.tab(targetTabId)?.epochCount ?? 0,
1761
1780
  ...(hasSeparateDeliveryBudget
1762
1781
  ? {
1763
1782
  restartWorkTimerAfterReceipt: () => {
1764
- const pending = pendingCommands.get(requestId);
1765
- if (pending === undefined) return;
1766
- clearTimeout(pending.timer);
1767
- pending.timer = setTimeout(expire, timeoutMs);
1783
+ workStartedAt = Date.now();
1784
+ refreshWorkTimer();
1768
1785
  },
1769
1786
  }
1770
1787
  : {}),
@@ -51,7 +51,7 @@ import {
51
51
  } from './authoring/shell-document-ops';
52
52
  import { parseCameraChoice, parsePoseChoice } from './capture-camera-pose';
53
53
  import { noteCommandDispatched } from './command-dispatch';
54
- import { contributedCommand, contributedCommandDerivedRefresh } from './command-registry';
54
+ import { resolveContributedCommand, contributedCommandDerivedRefresh } from './command-registry';
55
55
  import {
56
56
  isAssetDocumentId,
57
57
  openAssetDocument,
@@ -1237,7 +1237,7 @@ export async function handleCommand(
1237
1237
  // answered by its own handler, through the same relay, ack and derivation
1238
1238
  // as the host's table below. Asked first so a contributed verb never
1239
1239
  // reads as "editor page predates this CLI".
1240
- const contributed = contributedCommand(cmd['type']);
1240
+ const contributed = await resolveContributedCommand(cmd['type']);
1241
1241
  if (contributed) {
1242
1242
  try {
1243
1243
  const answer = await contributed.handle(cmd);
@@ -55,6 +55,17 @@ export function contributedCommand(type: unknown): CommandSpec | null {
55
55
  return typeof type === 'string' ? (registered.get(type)?.spec ?? null) : null;
56
56
  }
57
57
 
58
+ /** A listener can attach before the presentation-deferred contribution pass.
59
+ * A requested package command needs that pass now; absence is only meaningful
60
+ * after the current project's contributions have finished installing. */
61
+ export async function resolveContributedCommand(type: unknown): Promise<CommandSpec | null> {
62
+ const found = contributedCommand(type);
63
+ if (found || typeof type !== 'string' || isRelayCommandType(type)) return found;
64
+ await (await import('./initial-project')).projectBootstrapSettled();
65
+ await (await import('./tool-loader')).refreshProjectToolContributions();
66
+ return contributedCommand(type);
67
+ }
68
+
58
69
  export function contributedCommandDerivedRefresh(type: unknown): CommandDerivedRefresh | null {
59
70
  const spec = contributedCommand(type);
60
71
  return spec ? (spec.derivedRefresh ?? 'always') : null;
@@ -32,6 +32,7 @@ import {
32
32
  } from './document-context-registry';
33
33
  import { openRegisteredDocumentAsync } from './document-open-registry';
34
34
  import { editorConsole } from './editor-console';
35
+ import { resolvedProjectDocumentTable } from './project-adapter';
35
36
  import { notify } from './editor-notifications';
36
37
  import { registerEditorStateFacet } from './editor-state-facets';
37
38
  import { setFilesProvider } from './files/file-provider';
@@ -259,6 +260,7 @@ export function installEditorHostDoor(): void {
259
260
  reportWorkerCallMeter: setBlenderCallMeter,
260
261
  },
261
262
  project: {
263
+ documentTable: resolvedProjectDocumentTable,
262
264
  mounts: projectMounts,
263
265
  // The ONE reading of the pinned engine version — `ProjectHeader.tsx`'s
264
266
  // `v{engineVersion}` and a package's status item are the same number
@@ -255,6 +255,19 @@ export function waitForProjectAdapter(): Promise<void> {
255
255
  });
256
256
  }
257
257
 
258
+ /** A package opening a document during boot needs the final finder-backed
259
+ * table, not the initial declaration with contributed finders still pending. */
260
+ export async function resolvedProjectDocumentTable(): Promise<ResolvedDocumentTable> {
261
+ await (await import('./initial-project')).projectBootstrapSettled();
262
+ await refreshProjectToolContributions();
263
+ await refreshProjectAdapter();
264
+ while (_inFlight) await _inFlight;
265
+ const facet = projectAdapterFacet();
266
+ if (!facet || facet.documentsPending)
267
+ throw new Error('The project document table could not finish loading; inspect the project diagnostics.');
268
+ return facet.scenes;
269
+ }
270
+
258
271
  export function subscribeProjectAdapter(fn: () => void): () => void {
259
272
  _listeners.add(fn);
260
273
  return () => _listeners.delete(fn);