@pascal-app/mcp 1.0.1 → 1.0.4

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 (196) hide show
  1. package/README.md +47 -26
  2. package/dist/bin/pascal-mcp.js +8 -2
  3. package/dist/bridge/scene-bridge.d.ts +41 -6
  4. package/dist/bridge/scene-bridge.d.ts.map +1 -1
  5. package/dist/bridge/scene-bridge.js +230 -45
  6. package/dist/index.d.ts +3 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/operations/scene-operations.d.ts +23 -3
  10. package/dist/operations/scene-operations.d.ts.map +1 -1
  11. package/dist/operations/scene-operations.js +31 -1
  12. package/dist/prompts/from-brief.js +1 -1
  13. package/dist/prompts/iterate-on-feedback.js +1 -1
  14. package/dist/prompts/scene-guidance.js +3 -3
  15. package/dist/resources/agent-guide.d.ts.map +1 -1
  16. package/dist/resources/agent-guide.js +22 -1
  17. package/dist/resources/catalog-items.d.ts +4 -5
  18. package/dist/resources/catalog-items.d.ts.map +1 -1
  19. package/dist/resources/catalog-items.js +7 -9
  20. package/dist/resources/design-schema.d.ts +9 -0
  21. package/dist/resources/design-schema.d.ts.map +1 -0
  22. package/dist/resources/design-schema.js +21 -0
  23. package/dist/resources/index.d.ts +3 -1
  24. package/dist/resources/index.d.ts.map +1 -1
  25. package/dist/resources/index.js +5 -2
  26. package/dist/server.d.ts +19 -0
  27. package/dist/server.d.ts.map +1 -1
  28. package/dist/server.js +14 -6
  29. package/dist/storage/disk-artifact-store.d.ts +9 -0
  30. package/dist/storage/disk-artifact-store.d.ts.map +1 -0
  31. package/dist/storage/disk-artifact-store.js +46 -0
  32. package/dist/storage/index.d.ts +1 -0
  33. package/dist/storage/index.d.ts.map +1 -1
  34. package/dist/storage/index.js +1 -0
  35. package/dist/storage/sqlite-scene-store.d.ts +2 -2
  36. package/dist/storage/sqlite-scene-store.d.ts.map +1 -1
  37. package/dist/storage/sqlite-scene-store.js +4 -3
  38. package/dist/storage/types.d.ts +32 -3
  39. package/dist/storage/types.d.ts.map +1 -1
  40. package/dist/storage/types.js +12 -0
  41. package/dist/tools/add-column.d.ts +5 -0
  42. package/dist/tools/add-column.d.ts.map +1 -0
  43. package/dist/tools/add-column.js +58 -0
  44. package/dist/tools/add-object.d.ts +60 -0
  45. package/dist/tools/add-object.d.ts.map +1 -0
  46. package/dist/tools/add-object.js +156 -0
  47. package/dist/tools/apply-patch.d.ts +3 -0
  48. package/dist/tools/apply-patch.d.ts.map +1 -1
  49. package/dist/tools/apply-patch.js +28 -2
  50. package/dist/tools/asset-catalog.d.ts +9 -5
  51. package/dist/tools/asset-catalog.d.ts.map +1 -1
  52. package/dist/tools/asset-catalog.js +10 -12
  53. package/dist/tools/check-collisions.d.ts +1 -1
  54. package/dist/tools/check-collisions.d.ts.map +1 -1
  55. package/dist/tools/check-collisions.js +1 -2
  56. package/dist/tools/clear-scene.d.ts +9 -0
  57. package/dist/tools/clear-scene.d.ts.map +1 -0
  58. package/dist/tools/clear-scene.js +59 -0
  59. package/dist/tools/collections.d.ts +4 -0
  60. package/dist/tools/collections.d.ts.map +1 -0
  61. package/dist/tools/collections.js +52 -0
  62. package/dist/tools/construction-tools.d.ts +8 -41
  63. package/dist/tools/construction-tools.d.ts.map +1 -1
  64. package/dist/tools/construction-tools.js +120 -230
  65. package/dist/tools/create-unit.d.ts +2 -0
  66. package/dist/tools/create-unit.d.ts.map +1 -1
  67. package/dist/tools/describe-node.d.ts.map +1 -1
  68. package/dist/tools/describe-node.js +8 -6
  69. package/dist/tools/errors.d.ts +11 -0
  70. package/dist/tools/errors.d.ts.map +1 -1
  71. package/dist/tools/errors.js +10 -0
  72. package/dist/tools/find-nodes.d.ts +3 -19
  73. package/dist/tools/find-nodes.d.ts.map +1 -1
  74. package/dist/tools/find-nodes.js +150 -53
  75. package/dist/tools/geometry-script-worker.d.ts +2 -0
  76. package/dist/tools/geometry-script-worker.d.ts.map +1 -0
  77. package/dist/tools/geometry-script-worker.js +21 -0
  78. package/dist/tools/get-scene.d.ts +1 -0
  79. package/dist/tools/get-scene.d.ts.map +1 -1
  80. package/dist/tools/get-scene.js +3 -1
  81. package/dist/tools/honest-patch-guard.d.ts +2 -0
  82. package/dist/tools/honest-patch-guard.d.ts.map +1 -0
  83. package/dist/tools/honest-patch-guard.js +32 -0
  84. package/dist/tools/hosted-services.d.ts +4 -0
  85. package/dist/tools/hosted-services.d.ts.map +1 -0
  86. package/dist/tools/hosted-services.js +30 -0
  87. package/dist/tools/index.d.ts +10 -1
  88. package/dist/tools/index.d.ts.map +1 -1
  89. package/dist/tools/index.js +17 -26
  90. package/dist/tools/live-sync.d.ts +24 -5
  91. package/dist/tools/live-sync.d.ts.map +1 -1
  92. package/dist/tools/live-sync.js +47 -26
  93. package/dist/tools/local-geometry-scripts.d.ts +8 -0
  94. package/dist/tools/local-geometry-scripts.d.ts.map +1 -0
  95. package/dist/tools/local-geometry-scripts.js +67 -0
  96. package/dist/tools/measure.d.ts +6 -0
  97. package/dist/tools/measure.d.ts.map +1 -1
  98. package/dist/tools/measure.js +33 -65
  99. package/dist/tools/measurement.d.ts +1 -36
  100. package/dist/tools/measurement.d.ts.map +1 -1
  101. package/dist/tools/measurement.js +2 -94
  102. package/dist/tools/node-world-point.d.ts +37 -0
  103. package/dist/tools/node-world-point.d.ts.map +1 -0
  104. package/dist/tools/node-world-point.js +457 -0
  105. package/dist/tools/normalize-schema-dialect.d.ts.map +1 -1
  106. package/dist/tools/normalize-schema-dialect.js +20 -0
  107. package/dist/tools/patch-guards.d.ts +58 -0
  108. package/dist/tools/patch-guards.d.ts.map +1 -0
  109. package/dist/tools/patch-guards.js +253 -0
  110. package/dist/tools/photo-to-scene/photo-to-scene.d.ts.map +1 -1
  111. package/dist/tools/photo-to-scene/photo-to-scene.js +3 -2
  112. package/dist/tools/place-design.d.ts +34 -0
  113. package/dist/tools/place-design.d.ts.map +1 -0
  114. package/dist/tools/place-design.js +91 -0
  115. package/dist/tools/redo.d.ts +2 -0
  116. package/dist/tools/redo.d.ts.map +1 -1
  117. package/dist/tools/redo.js +2 -2
  118. package/dist/tools/room-tools.d.ts +50 -113
  119. package/dist/tools/room-tools.d.ts.map +1 -1
  120. package/dist/tools/room-tools.js +129 -565
  121. package/dist/tools/scene-lifecycle/create-project.d.ts.map +1 -1
  122. package/dist/tools/scene-lifecycle/create-project.js +26 -1
  123. package/dist/tools/scene-lifecycle/delete-scene.d.ts +2 -0
  124. package/dist/tools/scene-lifecycle/delete-scene.d.ts.map +1 -1
  125. package/dist/tools/scene-lifecycle/delete-scene.js +4 -3
  126. package/dist/tools/scene-lifecycle/save-scene.d.ts +1 -0
  127. package/dist/tools/scene-lifecycle/save-scene.d.ts.map +1 -1
  128. package/dist/tools/scene-lifecycle/save-scene.js +46 -6
  129. package/dist/tools/scene-lifecycle/test-utils.d.ts +2 -2
  130. package/dist/tools/scene-lifecycle/test-utils.d.ts.map +1 -1
  131. package/dist/tools/scene-lifecycle/test-utils.js +2 -1
  132. package/dist/tools/set-unit-members.d.ts +2 -0
  133. package/dist/tools/set-unit-members.d.ts.map +1 -1
  134. package/dist/tools/set-zone.d.ts +2 -0
  135. package/dist/tools/set-zone.d.ts.map +1 -1
  136. package/dist/tools/set-zone.js +2 -2
  137. package/dist/tools/shared-tools.d.ts +30 -0
  138. package/dist/tools/shared-tools.d.ts.map +1 -0
  139. package/dist/tools/shared-tools.js +361 -0
  140. package/dist/tools/structure-tools.d.ts +57 -0
  141. package/dist/tools/structure-tools.d.ts.map +1 -0
  142. package/dist/tools/structure-tools.js +57 -0
  143. package/dist/tools/undo.d.ts +2 -0
  144. package/dist/tools/undo.d.ts.map +1 -1
  145. package/dist/tools/undo.js +2 -2
  146. package/dist/tools/validate-design.d.ts +136 -0
  147. package/dist/tools/validate-design.d.ts.map +1 -0
  148. package/dist/tools/validate-design.js +28 -0
  149. package/dist/tools/validate-scene.d.ts +5 -0
  150. package/dist/tools/validate-scene.d.ts.map +1 -1
  151. package/dist/tools/validate-scene.js +8 -6
  152. package/dist/tools/variants/mutations.js +1 -1
  153. package/dist/tools/view-scene.d.ts +38 -0
  154. package/dist/tools/view-scene.d.ts.map +1 -0
  155. package/dist/tools/view-scene.js +63 -0
  156. package/dist/version.d.ts +5 -0
  157. package/dist/version.d.ts.map +1 -1
  158. package/dist/version.js +17 -3
  159. package/examples/embed-in-agent.ts +1 -1
  160. package/examples/generate-apartment.md +9 -11
  161. package/examples/photo-to-scene.md +1 -1
  162. package/examples/renovate-from-photos.md +3 -4
  163. package/package.json +12 -6
  164. package/dist/tools/create-level.d.ts +0 -21
  165. package/dist/tools/create-level.d.ts.map +0 -1
  166. package/dist/tools/create-level.js +0 -63
  167. package/dist/tools/create-wall.d.ts +0 -22
  168. package/dist/tools/create-wall.d.ts.map +0 -1
  169. package/dist/tools/create-wall.js +0 -57
  170. package/dist/tools/cut-opening.d.ts +0 -25
  171. package/dist/tools/cut-opening.d.ts.map +0 -1
  172. package/dist/tools/cut-opening.js +0 -61
  173. package/dist/tools/delete-node.d.ts +0 -19
  174. package/dist/tools/delete-node.d.ts.map +0 -1
  175. package/dist/tools/delete-node.js +0 -40
  176. package/dist/tools/door-clearance.d.ts +0 -135
  177. package/dist/tools/door-clearance.d.ts.map +0 -1
  178. package/dist/tools/door-clearance.js +0 -334
  179. package/dist/tools/duplicate-level.d.ts +0 -19
  180. package/dist/tools/duplicate-level.d.ts.map +0 -1
  181. package/dist/tools/duplicate-level.js +0 -60
  182. package/dist/tools/geometry.d.ts +0 -24
  183. package/dist/tools/geometry.d.ts.map +0 -1
  184. package/dist/tools/geometry.js +0 -94
  185. package/dist/tools/get-node.d.ts +0 -11
  186. package/dist/tools/get-node.d.ts.map +0 -1
  187. package/dist/tools/get-node.js +0 -29
  188. package/dist/tools/layout-clearance.d.ts +0 -105
  189. package/dist/tools/layout-clearance.d.ts.map +0 -1
  190. package/dist/tools/layout-clearance.js +0 -190
  191. package/dist/tools/place-item.d.ts +0 -22
  192. package/dist/tools/place-item.d.ts.map +0 -1
  193. package/dist/tools/place-item.js +0 -89
  194. package/dist/tools/scene-query.d.ts +0 -62
  195. package/dist/tools/scene-query.d.ts.map +0 -1
  196. package/dist/tools/scene-query.js +0 -670
@@ -1,6 +1,7 @@
1
1
  // Side-effect import MUST come first: installs RAF polyfill before core loads.
2
2
  import './node-shims.js';
3
- import { AnyNode as AnyNodeSchema, parseNode, } from '@pascal-app/core/schema';
3
+ import { adjacentLevelId, applyStructureReconciliation, assertDerivedNodeWrites, changedStairOpeningOwners, floorOpeningTargets, HIDDEN_SITE_NOTE, pauseSceneHistory, pauseSpaceDetection, planNodeDeletion, planOwnedFloorOpenings, planWallDeletion, resumeSceneHistory, resumeSpaceDetection, runAsSingleSceneHistoryStep, stairDeckLevelId, structureChangeBatch, } from '@pascal-app/core';
4
+ import { AnyNode as AnyNodeSchema, generateId, parseNode, SceneMaterial, } from '@pascal-app/core/schema';
4
5
  // Per PLAN §0.6: `useScene` is the DEFAULT export from `@pascal-app/core/store`.
5
6
  import useScene from '@pascal-app/core/store';
6
7
  /**
@@ -11,6 +12,8 @@ import useScene from '@pascal-app/core/store';
11
12
  * `flushDirty()` for observability.
12
13
  */
13
14
  export class SceneBridge {
15
+ /** `applyPatch` takes `upsert_material` patches, in the patch's one history step. */
16
+ supportsMaterialUpserts = true;
14
17
  activeScene = null;
15
18
  /**
16
19
  * Scene identity currently bound to this bridge. MCP tools use this to know
@@ -24,6 +27,7 @@ export class SceneBridge {
24
27
  ownerId: meta.ownerId,
25
28
  thumbnailUrl: meta.thumbnailUrl,
26
29
  version: meta.version,
30
+ ...(meta.graphHash === undefined ? {} : { graphHash: meta.graphHash }),
27
31
  };
28
32
  }
29
33
  getActiveScene() {
@@ -118,6 +122,17 @@ export class SceneBridge {
118
122
  getNodes() {
119
123
  return useScene.getState().nodes;
120
124
  }
125
+ getCollections() {
126
+ return useScene.getState().collections;
127
+ }
128
+ getMaterials() {
129
+ return useScene.getState().materials;
130
+ }
131
+ setCollections(collections) {
132
+ if (useScene.getState().readOnly)
133
+ return;
134
+ useScene.setState({ collections });
135
+ }
121
136
  /** Root node IDs. */
122
137
  getRootNodeIds() {
123
138
  return useScene.getState().rootNodeIds;
@@ -236,7 +251,12 @@ export class SceneBridge {
236
251
  * Returns the generated id.
237
252
  */
238
253
  createNode(node, parentId) {
239
- useScene.getState().createNode(node, parentId);
254
+ const before = useScene.getState().nodes;
255
+ assertDerivedNodeWrites(before, { create: [{ node: parentId ? { ...node, parentId } : node }] });
256
+ runAsSingleSceneHistoryStep(useScene, () => {
257
+ useScene.getState().createNode(node, parentId);
258
+ this.reconcileDependentStructure(before);
259
+ });
240
260
  return node.id;
241
261
  }
242
262
  /** Update node fields (shallow merge through the core store). */
@@ -244,7 +264,12 @@ export class SceneBridge {
244
264
  if (!useScene.getState().nodes[id]) {
245
265
  throw new Error(`node not found: ${id}`);
246
266
  }
247
- useScene.getState().updateNode(id, data);
267
+ const before = useScene.getState().nodes;
268
+ assertDerivedNodeWrites(before, { update: [{ id, data }] });
269
+ runAsSingleSceneHistoryStep(useScene, () => {
270
+ useScene.getState().updateNode(id, data);
271
+ this.reconcileDependentStructure(before);
272
+ });
248
273
  }
249
274
  /**
250
275
  * Delete a node. If the node has children and `cascade === false`, throws.
@@ -264,7 +289,18 @@ export class SceneBridge {
264
289
  throw new Error(`node has ${descendants.length - 1} descendant(s); pass cascade: true to delete recursively`);
265
290
  }
266
291
  const before = new Set(Object.keys(state.nodes));
267
- useScene.getState().deleteNode(id);
292
+ const wallLevels = [
293
+ ...new Set(descendants.flatMap((nodeId) => {
294
+ const deleted = state.nodes[nodeId];
295
+ return deleted?.type === 'wall' && deleted.parentId ? [deleted.parentId] : [];
296
+ })),
297
+ ];
298
+ runAsSingleSceneHistoryStep(useScene, () => {
299
+ useScene.getState().deleteNode(id);
300
+ if (wallLevels.length)
301
+ this.deriveStructure(wallLevels);
302
+ this.reconcileDependentStructure(state.nodes);
303
+ });
268
304
  const afterNodes = useScene.getState().nodes;
269
305
  const removed = [];
270
306
  for (const prevId of before) {
@@ -275,8 +311,8 @@ export class SceneBridge {
275
311
  }
276
312
  /**
277
313
  * Atomic multi-op patch. Validates EVERY patch first (dry run); only if all
278
- * pass does it apply in a single batch via `createNodes` / `updateNodes` /
279
- * `deleteNodes`. Throws on any validation failure without mutating state.
314
+ * pass does it apply through the store actions in one history step. Throws on
315
+ * validation failure without mutating state.
280
316
  */
281
317
  applyPatch(patches) {
282
318
  const state = useScene.getState();
@@ -289,11 +325,18 @@ export class SceneBridge {
289
325
  // Zod-normalised copy (which has a generated id if the caller omitted one)
290
326
  // instead of the unparsed input.
291
327
  const parsedCreateNodes = new Map();
328
+ const materialUpserts = new Map();
292
329
  for (let i = 0; i < patches.length; i++) {
293
330
  const p = patches[i];
294
331
  if (!p)
295
332
  throw new Error(`invalid patch: patches[${i}] is undefined`);
296
- if (p.op === 'create') {
333
+ if (p.op === 'upsert_material') {
334
+ const parsed = SceneMaterial.safeParse(p.material);
335
+ if (!parsed.success)
336
+ throw new Error(`invalid patch: patches[${i}] material failed schema: ${parsed.error.message}`);
337
+ materialUpserts.set(parsed.data.id, parsed.data);
338
+ }
339
+ else if (p.op === 'create') {
297
340
  const res = parseNode(p.node);
298
341
  if (!res.success) {
299
342
  throw new Error(`invalid patch: patches[${i}] create node failed schema: ${res.error.message}`);
@@ -301,7 +344,7 @@ export class SceneBridge {
301
344
  if (p.parentId !== undefined && !simAvailable.has(p.parentId)) {
302
345
  throw new Error(`invalid patch: patches[${i}] create parentId "${p.parentId}" not found`);
303
346
  }
304
- parsedCreateNodes.set(i, res.data);
347
+ parsedCreateNodes.set(i, p.parentId ? { ...res.data, parentId: p.parentId } : res.data);
305
348
  simAvailable.add(res.data.id);
306
349
  }
307
350
  else if (p.op === 'update') {
@@ -333,47 +376,75 @@ export class SceneBridge {
333
376
  throw new Error(`invalid patch: patches[${i}] unknown op`);
334
377
  }
335
378
  }
336
- // Dry-run succeeded — apply in order, batching adjacent ops of the same
337
- // op type so Zundo groups them tightly.
338
- const createOps = [];
339
- const updateOps = [];
340
- const deleteIds = [];
341
- const createdIds = [];
342
- // Simple approach: queue by type, flush in original order by walking
343
- // patches and interleaving flushes when the op type changes, so ids
344
- // created/updated/deleted stay temporally consistent.
345
- const flush = (kind) => {
346
- if (kind !== 'create' && createOps.length > 0) {
347
- useScene.getState().createNodes(createOps);
348
- createOps.length = 0;
349
- }
350
- if (kind !== 'update' && updateOps.length > 0) {
351
- useScene.getState().updateNodes(updateOps);
352
- updateOps.length = 0;
379
+ // Derived construction is refused BEFORE anything is applied, so a patch
380
+ // that tries to author a floor plate or an auto ceiling leaves the scene
381
+ // untouched like any other invalid patch.
382
+ assertDerivedNodeWrites(nodes, {
383
+ create: [...parsedCreateNodes.values()].map((node) => ({ node })),
384
+ update: patches.flatMap((patch) => patch.op === 'update' ? [{ id: patch.id, data: patch.data }] : []),
385
+ });
386
+ const createOps = new Map();
387
+ const updateOps = new Map();
388
+ const deleteIds = new Set();
389
+ const nextNodes = { ...nodes };
390
+ for (const [index, patch] of patches.entries()) {
391
+ if (patch.op === 'create') {
392
+ const parsed = parsedCreateNodes.get(index);
393
+ const node = { ...parsed, parentId: patch.parentId ?? parsed.parentId };
394
+ createOps.set(node.id, { node, parentId: patch.parentId });
395
+ deleteIds.delete(node.id);
396
+ nextNodes[node.id] = node;
353
397
  }
354
- if (kind !== 'delete' && deleteIds.length > 0) {
355
- useScene.getState().deleteNodes(deleteIds);
356
- deleteIds.length = 0;
398
+ else if (patch.op === 'update') {
399
+ nextNodes[patch.id] = { ...nextNodes[patch.id], ...patch.data };
400
+ const created = createOps.get(patch.id);
401
+ if (created) {
402
+ created.node = nextNodes[patch.id];
403
+ if (patch.data.parentId !== undefined)
404
+ created.parentId = patch.data.parentId;
405
+ }
406
+ else
407
+ updateOps.set(patch.id, { ...updateOps.get(patch.id), ...patch.data });
357
408
  }
358
- };
359
- for (let i = 0; i < patches.length; i++) {
360
- const p = patches[i];
361
- if (p.op === 'create') {
362
- flush('create');
363
- const parsedNode = parsedCreateNodes.get(i);
364
- createOps.push({ node: parsedNode, parentId: p.parentId });
365
- createdIds.push(parsedNode.id);
409
+ else if (patch.op === 'delete') {
410
+ deleteIds.add(patch.id);
411
+ createOps.delete(patch.id);
412
+ updateOps.delete(patch.id);
413
+ delete nextNodes[patch.id];
366
414
  }
367
- else if (p.op === 'update') {
368
- flush('update');
369
- updateOps.push({ id: p.id, data: p.data });
415
+ }
416
+ // Plan the entire deletion selection before applying anything. Interleaved
417
+ // update operations must not turn an all-boundaries deletion into balconies.
418
+ const planned = structureChangeBatch(planWallDeletion(nodes, {
419
+ nodeIds: [...deleteIds],
420
+ nextNodes,
421
+ mintId: generateId,
422
+ }).changes);
423
+ for (const { id, data } of planned.update)
424
+ updateOps.set(id, { ...updateOps.get(id), ...data });
425
+ const wallLevels = [
426
+ ...new Set(planned.delete.flatMap((id) => {
427
+ const node = nodes[id];
428
+ return node?.type === 'wall' && node.parentId ? [node.parentId] : [];
429
+ })),
430
+ ];
431
+ runAsSingleSceneHistoryStep(useScene, () => {
432
+ pauseSpaceDetection();
433
+ try {
434
+ for (const material of materialUpserts.values())
435
+ useScene.getState().addSceneMaterial(material);
436
+ useScene.getState().createNodes([...createOps.values(), ...planned.create]);
437
+ useScene.getState().updateNodes([...updateOps].map(([id, data]) => ({ id, data })));
438
+ useScene.getState().deleteNodes(planned.delete);
370
439
  }
371
- else {
372
- flush('delete');
373
- deleteIds.push(p.id);
440
+ finally {
441
+ resumeSpaceDetection();
374
442
  }
375
- }
376
- flush('none');
443
+ if (wallLevels.length)
444
+ this.deriveStructure(wallLevels);
445
+ this.reconcileDependentStructure(nodes);
446
+ });
447
+ const createdIds = Object.keys(useScene.getState().nodes).filter((id) => !nodes[id]);
377
448
  // Compute actual deleted ids by diffing pre/post snapshots.
378
449
  const postNodes = useScene.getState().nodes;
379
450
  const deletedIds = [];
@@ -387,6 +458,40 @@ export class SceneBridge {
387
458
  createdIds,
388
459
  };
389
460
  }
461
+ /**
462
+ * Preview what deleting `ids` does to `scene` with this bridge's semantics
463
+ * (core's store planner), without committing or minting default gutters.
464
+ * The apply_patch guard runs its dry run through this.
465
+ */
466
+ planDeletion(scene, ids) {
467
+ return planNodeDeletion(scene, ids, { mintDefaults: false });
468
+ }
469
+ /**
470
+ * Derive this scene's construction the way the browser's commit subscriber
471
+ * does: one pass of the shared kernel writes the room zones, the auto
472
+ * ceilings, the wall side classification and the floor plates.
473
+ *
474
+ * The headless bridge has no space-detection subscriber, so the semantic
475
+ * tools that state room intent call this to materialise the construction
476
+ * they report. Writes are history-paused, so undo returns to the state
477
+ * before the triggering tool call rather than stranding derived nodes.
478
+ */
479
+ deriveStructure(levelIds) {
480
+ pauseSceneHistory(useScene);
481
+ try {
482
+ const patches = applyStructureReconciliation(useScene, {
483
+ levelIds,
484
+ mintId: (kind) => generateId(kind),
485
+ });
486
+ return {
487
+ createdIds: patches.flatMap((patch) => patch.op === 'create' ? [patch.node.id] : []),
488
+ deletedIds: patches.flatMap((patch) => (patch.op === 'delete' ? [patch.id] : [])),
489
+ };
490
+ }
491
+ finally {
492
+ resumeSceneHistory(useScene);
493
+ }
494
+ }
390
495
  /** Undo. Returns the number of steps actually undone. */
391
496
  undo(steps = 1) {
392
497
  const before = useScene.temporal.getState().pastStates.length;
@@ -407,8 +512,12 @@ export class SceneBridge {
407
512
  */
408
513
  validateScene() {
409
514
  const errors = [];
515
+ const warnings = [];
410
516
  const nodes = useScene.getState().nodes;
411
517
  for (const [id, node] of Object.entries(nodes)) {
518
+ if (node.type === 'site' && node.visible === false) {
519
+ warnings.push({ nodeId: id, path: 'visible', message: HIDDEN_SITE_NOTE });
520
+ }
412
521
  const res = AnyNodeSchema.safeParse(node);
413
522
  if (res.success)
414
523
  continue;
@@ -420,7 +529,7 @@ export class SceneBridge {
420
529
  });
421
530
  }
422
531
  }
423
- return { valid: errors.length === 0, errors };
532
+ return { valid: errors.length === 0, errors, warnings };
424
533
  }
425
534
  /**
426
535
  * Drain the dirtyNodes set. Returns the ids that were present. No-op for
@@ -446,6 +555,82 @@ export class SceneBridge {
446
555
  clearHistory() {
447
556
  useScene.temporal.getState().clear();
448
557
  }
558
+ runAsSingleHistoryStep(run) {
559
+ return runAsSingleSceneHistoryStep(useScene, run);
560
+ }
561
+ reconcileDependentStructure(before) {
562
+ const currentNodes = this.getNodes();
563
+ const authoredInputChanged = [
564
+ ...new Set([...Object.keys(before), ...Object.keys(currentNodes)]),
565
+ ].some((id) => {
566
+ if (before[id] === currentNodes[id])
567
+ return false;
568
+ const type = (currentNodes[id] ?? before[id])?.type;
569
+ return (type === 'stair' ||
570
+ type === 'stair-segment' ||
571
+ type === 'elevator' ||
572
+ type === 'level' ||
573
+ type === 'building' ||
574
+ type === 'slab' ||
575
+ type === 'ceiling');
576
+ });
577
+ if (authoredInputChanged) {
578
+ const ownerIds = changedStairOpeningOwners(before, currentNodes);
579
+ for (const node of [...Object.values(before), ...Object.values(currentNodes)]) {
580
+ if (node.type === 'elevator' &&
581
+ (before[node.id] !== currentNodes[node.id] ||
582
+ Object.values(currentNodes).some((level) => level.type === 'level' && before[level.id] !== level)))
583
+ ownerIds.add(node.id);
584
+ }
585
+ const owned = planOwnedFloorOpenings(currentNodes, { ownerIds });
586
+ if (owned.length) {
587
+ pauseSpaceDetection();
588
+ try {
589
+ useScene.getState().applyNodeChanges(structureChangeBatch(owned));
590
+ }
591
+ finally {
592
+ resumeSpaceDetection();
593
+ }
594
+ }
595
+ }
596
+ const current = this.getNodes();
597
+ const levels = new Set();
598
+ for (const id of new Set([...Object.keys(before), ...Object.keys(current)])) {
599
+ if (before[id] === current[id])
600
+ continue;
601
+ for (const nodes of [before, current]) {
602
+ const level = stairDeckLevelId(nodes, nodes[id]);
603
+ if (level)
604
+ levels.add(level);
605
+ const node = nodes[id];
606
+ if (node?.type === 'level') {
607
+ levels.add(node.id);
608
+ const lower = adjacentLevelId(nodes, node.id, -1);
609
+ if (lower)
610
+ levels.add(lower);
611
+ }
612
+ if (node?.type === 'stair')
613
+ for (const opening of Object.values(nodes))
614
+ if (opening.type === 'floor-opening' && opening.ownerId === node.id)
615
+ for (const target of floorOpeningTargets(nodes, opening))
616
+ levels.add(target.levelId);
617
+ if (node?.type === 'floor-opening')
618
+ for (const target of floorOpeningTargets(nodes, node))
619
+ levels.add(target.levelId);
620
+ if (node?.type === 'door' || node?.type === 'window') {
621
+ const wall = nodes[node.parentId];
622
+ if (wall?.type === 'wall' && wall.parentId)
623
+ levels.add(wall.parentId);
624
+ }
625
+ else if ((node?.type === 'zone' || node?.type === 'slab' || node?.type === 'wall') &&
626
+ node.parentId) {
627
+ levels.add(node.parentId);
628
+ }
629
+ }
630
+ }
631
+ if (levels.size)
632
+ this.deriveStructure([...levels]);
633
+ }
449
634
  // ---- internal helpers ----
450
635
  /**
451
636
  * Return the node whose `children` array (string or object form) contains
package/dist/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  export { SceneBridge } from './bridge/scene-bridge.js';
2
2
  export { createSceneOperations, type SceneOperations } from './operations/index.js';
3
3
  export { type CreatePascalMcpServerOptions, createPascalMcpServer } from './server.js';
4
+ export type { GeometryScriptHost, ScriptedKind } from './tools/add-object.js';
5
+ export { type AssetCatalog, builtInCatalog } from './tools/asset-catalog.js';
6
+ export type { SceneViewCapture, SceneViewHost } from './tools/view-scene.js';
4
7
  export { version } from './version.js';
5
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,qBAAqB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA;AAC1E,OAAO,EAAE,KAAK,4BAA4B,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AACnF,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,qBAAqB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA;AAC1E,OAAO,EAAE,KAAK,4BAA4B,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AACnF,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAC1E,OAAO,EAAE,KAAK,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACzE,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACzE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { SceneBridge } from './bridge/scene-bridge.js';
2
2
  export { createSceneOperations } from './operations/index.js';
3
3
  export { createPascalMcpServer } from './server.js';
4
+ export { builtInCatalog } from './tools/asset-catalog.js';
4
5
  export { version } from './version.js';
@@ -1,7 +1,8 @@
1
+ import type { NodeDeletionPlan, NodeDeletionScene } from '@pascal-app/core';
1
2
  import type { SceneGraph } from '@pascal-app/core/clone-scene-graph';
2
- import type { AnyNode, AnyNodeId, AnyNodeType } from '@pascal-app/core/schema';
3
+ import type { AnyNode, AnyNodeId, AnyNodeType, Collection, CollectionId, SceneMaterial, SceneMaterialId } from '@pascal-app/core/schema';
3
4
  import type { ActiveSceneMeta, Patch, SceneBridge, ValidationResult } from '../bridge/scene-bridge.js';
4
- import type { ProjectCreateOptions, ProjectStatus, SceneEvent, SceneEventAppendOptions, SceneEventListOptions, SceneListOptions, SceneMeta, SceneMutateOptions, SceneSaveOptions, SceneStore, SceneWithGraph } from '../storage/types.js';
5
+ import type { ProjectCreateOptions, ProjectStatus, SceneDeleteResult, SceneEvent, SceneEventAppendOptions, SceneEventListOptions, SceneListOptions, SceneMeta, SceneMutateOptions, SceneSaveOptions, SceneStore, SceneWithGraph } from '../storage/types.js';
5
6
  export type CreateSceneOperationsOptions = {
6
7
  bridge?: SceneBridge;
7
8
  store?: SceneStore;
@@ -15,6 +16,11 @@ export interface SceneOperations {
15
16
  readonly canCreateProject: boolean;
16
17
  readonly canGetProjectStatus: boolean;
17
18
  readonly storeBackend: SceneStore['backend'] | null;
19
+ /**
20
+ * Whether `applyPatch` takes `upsert_material` patches with the nodes that use them, in one
21
+ * history step. A host that does lends `getMaterials`, and paint keeps native materials there.
22
+ */
23
+ readonly supportsMaterialUpserts?: boolean;
18
24
  setActiveScene(meta: ActiveSceneMeta): void;
19
25
  getActiveScene(): ActiveSceneMeta | null;
20
26
  clearActiveScene(): void;
@@ -27,6 +33,10 @@ export interface SceneOperations {
27
33
  loadJSON(json: string | SceneGraph): void;
28
34
  getNode(id: AnyNodeId): AnyNode | null;
29
35
  getNodes(): Record<AnyNodeId, AnyNode>;
36
+ getCollections(): Record<CollectionId, Collection>;
37
+ getMaterials?(): Readonly<Record<SceneMaterialId, SceneMaterial>>;
38
+ /** Replace the scene's collections (one undo step, like any edit). */
39
+ setCollections(collections: Record<CollectionId, Collection>): void;
30
40
  getRootNodeIds(): AnyNodeId[];
31
41
  getChildren(parentId: AnyNodeId): AnyNode[];
32
42
  getAncestry(id: AnyNodeId): AnyNode[];
@@ -44,6 +54,15 @@ export interface SceneOperations {
44
54
  deletedIds: AnyNodeId[];
45
55
  createdIds: AnyNodeId[];
46
56
  };
57
+ /**
58
+ * The active bridge's deletion preview, when it has one. Absent for bridges
59
+ * that cannot preview their own deletes; the apply_patch guard is then stricter.
60
+ */
61
+ readonly planDeletion?: (scene: NodeDeletionScene, ids: AnyNodeId[]) => NodeDeletionPlan;
62
+ deriveStructure(levelIds?: AnyNodeId[]): {
63
+ createdIds: AnyNodeId[];
64
+ deletedIds: AnyNodeId[];
65
+ };
47
66
  undo(steps?: number): number;
48
67
  redo(steps?: number): number;
49
68
  validateScene(): ValidationResult;
@@ -53,12 +72,13 @@ export interface SceneOperations {
53
72
  futureCount: number;
54
73
  };
55
74
  clearHistory(): void;
75
+ runAsSingleHistoryStep<T>(run: () => T): T;
56
76
  createProject(options: ProjectCreateOptions): Promise<ProjectStatus>;
57
77
  getProjectStatus(id: string): Promise<ProjectStatus | null>;
58
78
  saveScene(options: SceneSaveOptions): Promise<SceneMeta>;
59
79
  loadStoredScene(id: string): Promise<SceneWithGraph | null>;
60
80
  listScenes(options?: SceneListOptions): Promise<SceneMeta[]>;
61
- deleteStoredScene(id: string, options?: SceneMutateOptions): Promise<boolean>;
81
+ deleteStoredScene(id: string, options?: SceneMutateOptions): Promise<SceneDeleteResult>;
62
82
  renameStoredScene(id: string, newName: string, options?: SceneMutateOptions): Promise<SceneMeta>;
63
83
  appendSceneEvent(options: SceneEventAppendOptions): Promise<SceneEvent | null>;
64
84
  listSceneEvents(id: string, options?: SceneEventListOptions): Promise<SceneEvent[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"scene-operations.d.ts","sourceRoot":"","sources":["../../src/operations/scene-operations.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scene-operations.d.ts","sourceRoot":"","sources":["../../src/operations/scene-operations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAC3E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAA;AACpE,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EACT,WAAW,EACX,UAAU,EACV,YAAY,EACZ,aAAa,EACb,eAAe,EAChB,MAAM,yBAAyB,CAAA;AAChC,OAAO,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACnG,OAAO,KAAK,EACV,oBAAoB,EACpB,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,uBAAuB,EACvB,qBAAqB,EACrB,gBAAgB,EAChB,SAAS,EACT,kBAAkB,EAClB,gBAAgB,EAChB,UAAU,EACV,cAAc,EACf,MAAM,kBAAkB,CAAA;AAEzB,MAAM,MAAM,4BAA4B,GAAG;IACzC,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,KAAK,CAAC,EAAE,UAAU,CAAA;CACnB,CAAA;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAA;IACtC,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAA;IACpC,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAA;IAClC,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAA;IACrC,QAAQ,CAAC,YAAY,EAAE,UAAU,CAAC,SAAS,CAAC,GAAG,IAAI,CAAA;IACnD;;;OAGG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAA;IAE1C,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAA;IAC3C,cAAc,IAAI,eAAe,GAAG,IAAI,CAAA;IACxC,gBAAgB,IAAI,IAAI,CAAA;IACxB,WAAW,IAAI,IAAI,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,IAAI,CAAA;IAC3E,UAAU,IAAI,UAAU,GAAG;QAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAA;IACnE,gBAAgB,IAAI,UAAU,CAAA;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAA;IACzC,OAAO,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,GAAG,IAAI,CAAA;IACtC,QAAQ,IAAI,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;IACtC,cAAc,IAAI,MAAM,CAAC,YAAY,EAAE,UAAU,CAAC,CAAA;IAClD,YAAY,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC,CAAA;IACjE,sEAAsE;IACtE,cAAc,CAAC,WAAW,EAAE,MAAM,CAAC,YAAY,EAAE,UAAU,CAAC,GAAG,IAAI,CAAA;IACnE,cAAc,IAAI,SAAS,EAAE,CAAA;IAC7B,WAAW,CAAC,QAAQ,EAAE,SAAS,GAAG,OAAO,EAAE,CAAA;IAC3C,WAAW,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,EAAE,CAAA;IACrC,SAAS,CAAC,MAAM,EAAE;QAChB,IAAI,CAAC,EAAE,WAAW,CAAA;QAClB,QAAQ,CAAC,EAAE,SAAS,GAAG,IAAI,CAAA;QAC3B,OAAO,CAAC,EAAE,SAAS,CAAA;KACpB,GAAG,OAAO,EAAE,CAAA;IACb,cAAc,CAAC,EAAE,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI,CAAA;IAC/C,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IAC1D,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;IACvD,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,EAAE,CAAA;IACtD,UAAU,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG;QAC5B,UAAU,EAAE,MAAM,CAAA;QAClB,UAAU,EAAE,SAAS,EAAE,CAAA;QACvB,UAAU,EAAE,SAAS,EAAE,CAAA;KACxB,CAAA;IACD;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,gBAAgB,CAAA;IACxF,eAAe,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG;QAAE,UAAU,EAAE,SAAS,EAAE,CAAC;QAAC,UAAU,EAAE,SAAS,EAAE,CAAA;KAAE,CAAA;IAC7F,IAAI,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC5B,IAAI,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC5B,aAAa,IAAI,gBAAgB,CAAA;IACjC,UAAU,IAAI,MAAM,EAAE,CAAA;IACtB,UAAU,IAAI;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAA;IACxD,YAAY,IAAI,IAAI,CAAA;IACpB,sBAAsB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAA;IAE1C,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;IACpE,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAA;IAC3D,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IACxD,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAA;IAC3D,UAAU,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAA;IAC5D,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAA;IACvF,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IAChG,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAA;IAC9E,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAA;CACpF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,eAAe,CAE5F"}
@@ -4,13 +4,21 @@ export function createSceneOperations(options) {
4
4
  class SceneOperationsFacade {
5
5
  #bridge;
6
6
  #store;
7
+ planDeletion;
7
8
  constructor(options) {
8
9
  this.#bridge = options.bridge;
9
10
  this.#store = options.store;
11
+ const bridge = options.bridge;
12
+ if (typeof bridge?.planDeletion === 'function') {
13
+ this.planDeletion = (scene, ids) => bridge.planDeletion(scene, ids);
14
+ }
10
15
  }
11
16
  get hasBridge() {
12
17
  return this.#bridge !== undefined;
13
18
  }
19
+ get supportsMaterialUpserts() {
20
+ return this.#bridge?.supportsMaterialUpserts === true;
21
+ }
14
22
  get hasStore() {
15
23
  return this.#store !== undefined;
16
24
  }
@@ -61,7 +69,9 @@ class SceneOperationsFacade {
61
69
  };
62
70
  }
63
71
  loadJSON(json) {
64
- this.requireBridge().loadJSON(json);
72
+ const bridge = this.requireBridge();
73
+ bridge.loadJSON(json);
74
+ bridge.clearHistory();
65
75
  }
66
76
  getNode(id) {
67
77
  return this.requireBridge().getNode(id);
@@ -69,6 +79,15 @@ class SceneOperationsFacade {
69
79
  getNodes() {
70
80
  return this.requireBridge().getNodes();
71
81
  }
82
+ getCollections() {
83
+ return this.requireBridge().getCollections();
84
+ }
85
+ getMaterials() {
86
+ return this.requireBridge().getMaterials();
87
+ }
88
+ setCollections(collections) {
89
+ this.requireBridge().setCollections(collections);
90
+ }
72
91
  getRootNodeIds() {
73
92
  return this.requireBridge().getRootNodeIds();
74
93
  }
@@ -96,6 +115,14 @@ class SceneOperationsFacade {
96
115
  applyPatch(patches) {
97
116
  return this.requireBridge().applyPatch(patches);
98
117
  }
118
+ /**
119
+ * Derive construction from the intent the tools just wrote. The hosted
120
+ * bridge reconciles inside every mutation and exposes no hook, so the call
121
+ * is optional: there it is already done by the time a tool builds its result.
122
+ */
123
+ deriveStructure(levelIds) {
124
+ return this.requireBridge().deriveStructure?.(levelIds) ?? { createdIds: [], deletedIds: [] };
125
+ }
99
126
  undo(steps) {
100
127
  return this.requireBridge().undo(steps);
101
128
  }
@@ -114,6 +141,9 @@ class SceneOperationsFacade {
114
141
  clearHistory() {
115
142
  this.requireBridge().clearHistory();
116
143
  }
144
+ runAsSingleHistoryStep(run) {
145
+ return this.requireBridge().runAsSingleHistoryStep(run);
146
+ }
117
147
  async createProject(options) {
118
148
  const store = this.requireStore();
119
149
  if (!store.createProject) {
@@ -25,7 +25,7 @@ export function buildFromBriefPrompt(args) {
25
25
  if (args.constraints && args.constraints.trim().length > 0) {
26
26
  parts.push('', '## Constraints', args.constraints.trim());
27
27
  }
28
- parts.push('', '## Task', 'Produce tool calls that realise the brief within the stated constraints.', '1. Bind a scene — `create_project` (new), `list_scenes` then `load_scene` (existing), or `create_house_from_brief` (starter template).', '2. Build the design — `create_story_shell` for exterior shells, `create_room`/`add_door`/`add_window`/`create_stair_between_levels`/`furnish_room` for interior layout, `create_roof` for roofing, `apply_patch` for exact bulk graph work.', '3. Finish — call `validate_scene`, `verify_scene`, and `get_project_status`, then return the `editorUrl`.', 'Call `save_scene` with `saveMode: "checkpoint"` only when the design reaches a meaningful milestone.');
28
+ parts.push('', '## Task', 'Produce tool calls that realise the brief within the stated constraints.', '1. Bind a scene — `create_project` (new), `list_scenes` then `load_scene` (existing), or `create_house_from_brief` (starter template).', '2. Build the design — `create_story_shell` for exterior shells, `create_room`/`add_door`/`add_window`/`create_stair`/`furnish_room` for interior layout, `create_roof` for roofing, `apply_patch` for exact bulk graph work.', '3. Finish — call `validate_scene`, `verify_scene`, and `get_project_status`, then return the `editorUrl`.', 'Call `save_scene` with `saveMode: "checkpoint"` only when the design reaches a meaningful milestone.');
29
29
  return parts.join('\n');
30
30
  }
31
31
  export function registerFromBrief(server, _bridge) {
@@ -7,7 +7,7 @@ const PREAMBLE = [
7
7
  ' - Prefer updates over create+delete pairs when a field change will do.',
8
8
  ' - Do not re-create nodes that already exist.',
9
9
  ' - Do not touch nodes that are unrelated to the feedback.',
10
- ' - Prefer semantic tools such as create_room, add_door, add_window, furnish_room, and place_item when they match the request.',
10
+ ' - Prefer semantic tools such as create_room, add_door, add_window, furnish_room, and place_items when they match the request.',
11
11
  ' - Bundle related mutations into a single `apply_patch` call so they share one undo step.',
12
12
  ' - For multi-room changes, call verify_scene after the mutation and fix reported issues.',
13
13
  SCENE_DESIGN_GUIDANCE,
@@ -11,13 +11,13 @@ export const SCENE_DESIGN_GUIDANCE = [
11
11
  '',
12
12
  'Preferred phased tool workflow:',
13
13
  ' - Query first with list_levels, get_level_summary, get_walls, or get_zones when editing an existing scene.',
14
- ' - Create visible massing early: create_level as needed, then create_story_shell once per story.',
14
+ ' - Create visible massing early: add_level as needed, then create_story_shell once per story.',
15
15
  ' - For rooms, prefer create_room, then add_door/add_window, then furnish_room.',
16
- ' - For stairs between floors, prefer create_stair_between_levels so slab/ceiling openings stay rectangular and do not duplicate auto-generated holes.',
16
+ ' - For stairs between floors, use create_stair: the stair owns the floor openings it cuts, so moving or deleting it moves or closes them.',
17
17
  ' - For roofs, prefer create_roof and let it create/use a dedicated roof level above the top occupied story so solo/exploded level views can isolate the roof.',
18
18
  ' - verify_scene reports both levelCount and occupiedStoryCount. Use occupiedStoryCount when checking whether a one-story/two-story brief was satisfied.',
19
19
  ' - add_door/add_window use t = 0..1 along a wall: 0 is start, 0.5 is center, 1 is end.',
20
- ' - Use search_assets before place_item when placing a specific catalog item.',
20
+ ' - Use search_assets before place_items when placing catalog items.',
21
21
  ' - Use apply_patch for precise bulk edits that the semantic tools cannot express.',
22
22
  ' - After each major phase, call get_level_summary or pascal://scene/current/summary so progress is visible and errors are easier to localize.',
23
23
  ' - After multi-room or full-floor work, call validate_scene and verify_scene, then fix reported issues before finishing.',
@@ -1 +1 @@
1
- {"version":3,"file":"agent-guide.d.ts","sourceRoot":"","sources":["../../src/resources/agent-guide.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAEpD,eAAO,MAAM,WAAW,QA2DZ,CAAA;AAEZ,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI,CAuCpF"}
1
+ {"version":3,"file":"agent-guide.d.ts","sourceRoot":"","sources":["../../src/resources/agent-guide.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAEpD,eAAO,MAAM,WAAW,QAgFZ,CAAA;AAEZ,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI,CAuCpF"}