@pascal-app/mcp 1.0.3 → 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 (193) 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 +39 -6
  4. package/dist/bridge/scene-bridge.d.ts.map +1 -1
  5. package/dist/bridge/scene-bridge.js +225 -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/variants/mutations.js +1 -1
  150. package/dist/tools/view-scene.d.ts +38 -0
  151. package/dist/tools/view-scene.d.ts.map +1 -0
  152. package/dist/tools/view-scene.js +63 -0
  153. package/dist/version.d.ts +5 -0
  154. package/dist/version.d.ts.map +1 -1
  155. package/dist/version.js +17 -3
  156. package/examples/embed-in-agent.ts +1 -1
  157. package/examples/generate-apartment.md +9 -11
  158. package/examples/photo-to-scene.md +1 -1
  159. package/examples/renovate-from-photos.md +3 -4
  160. package/package.json +10 -4
  161. package/dist/tools/create-level.d.ts +0 -21
  162. package/dist/tools/create-level.d.ts.map +0 -1
  163. package/dist/tools/create-level.js +0 -63
  164. package/dist/tools/create-wall.d.ts +0 -22
  165. package/dist/tools/create-wall.d.ts.map +0 -1
  166. package/dist/tools/create-wall.js +0 -57
  167. package/dist/tools/cut-opening.d.ts +0 -25
  168. package/dist/tools/cut-opening.d.ts.map +0 -1
  169. package/dist/tools/cut-opening.js +0 -61
  170. package/dist/tools/delete-node.d.ts +0 -19
  171. package/dist/tools/delete-node.d.ts.map +0 -1
  172. package/dist/tools/delete-node.js +0 -40
  173. package/dist/tools/door-clearance.d.ts +0 -135
  174. package/dist/tools/door-clearance.d.ts.map +0 -1
  175. package/dist/tools/door-clearance.js +0 -334
  176. package/dist/tools/duplicate-level.d.ts +0 -19
  177. package/dist/tools/duplicate-level.d.ts.map +0 -1
  178. package/dist/tools/duplicate-level.js +0 -60
  179. package/dist/tools/geometry.d.ts +0 -24
  180. package/dist/tools/geometry.d.ts.map +0 -1
  181. package/dist/tools/geometry.js +0 -94
  182. package/dist/tools/get-node.d.ts +0 -11
  183. package/dist/tools/get-node.d.ts.map +0 -1
  184. package/dist/tools/get-node.js +0 -29
  185. package/dist/tools/layout-clearance.d.ts +0 -105
  186. package/dist/tools/layout-clearance.d.ts.map +0 -1
  187. package/dist/tools/layout-clearance.js +0 -190
  188. package/dist/tools/place-item.d.ts +0 -22
  189. package/dist/tools/place-item.d.ts.map +0 -1
  190. package/dist/tools/place-item.js +0 -89
  191. package/dist/tools/scene-query.d.ts +0 -62
  192. package/dist/tools/scene-query.d.ts.map +0 -1
  193. package/dist/tools/scene-query.js +0 -670
@@ -1,7 +1,7 @@
1
1
  // Side-effect import MUST come first: installs RAF polyfill before core loads.
2
2
  import './node-shims.js';
3
- import { HIDDEN_SITE_NOTE } from '@pascal-app/core';
4
- 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';
5
5
  // Per PLAN §0.6: `useScene` is the DEFAULT export from `@pascal-app/core/store`.
6
6
  import useScene from '@pascal-app/core/store';
7
7
  /**
@@ -12,6 +12,8 @@ import useScene from '@pascal-app/core/store';
12
12
  * `flushDirty()` for observability.
13
13
  */
14
14
  export class SceneBridge {
15
+ /** `applyPatch` takes `upsert_material` patches, in the patch's one history step. */
16
+ supportsMaterialUpserts = true;
15
17
  activeScene = null;
16
18
  /**
17
19
  * Scene identity currently bound to this bridge. MCP tools use this to know
@@ -25,6 +27,7 @@ export class SceneBridge {
25
27
  ownerId: meta.ownerId,
26
28
  thumbnailUrl: meta.thumbnailUrl,
27
29
  version: meta.version,
30
+ ...(meta.graphHash === undefined ? {} : { graphHash: meta.graphHash }),
28
31
  };
29
32
  }
30
33
  getActiveScene() {
@@ -119,6 +122,17 @@ export class SceneBridge {
119
122
  getNodes() {
120
123
  return useScene.getState().nodes;
121
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
+ }
122
136
  /** Root node IDs. */
123
137
  getRootNodeIds() {
124
138
  return useScene.getState().rootNodeIds;
@@ -237,7 +251,12 @@ export class SceneBridge {
237
251
  * Returns the generated id.
238
252
  */
239
253
  createNode(node, parentId) {
240
- 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
+ });
241
260
  return node.id;
242
261
  }
243
262
  /** Update node fields (shallow merge through the core store). */
@@ -245,7 +264,12 @@ export class SceneBridge {
245
264
  if (!useScene.getState().nodes[id]) {
246
265
  throw new Error(`node not found: ${id}`);
247
266
  }
248
- 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
+ });
249
273
  }
250
274
  /**
251
275
  * Delete a node. If the node has children and `cascade === false`, throws.
@@ -265,7 +289,18 @@ export class SceneBridge {
265
289
  throw new Error(`node has ${descendants.length - 1} descendant(s); pass cascade: true to delete recursively`);
266
290
  }
267
291
  const before = new Set(Object.keys(state.nodes));
268
- 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
+ });
269
304
  const afterNodes = useScene.getState().nodes;
270
305
  const removed = [];
271
306
  for (const prevId of before) {
@@ -276,8 +311,8 @@ export class SceneBridge {
276
311
  }
277
312
  /**
278
313
  * Atomic multi-op patch. Validates EVERY patch first (dry run); only if all
279
- * pass does it apply in a single batch via `createNodes` / `updateNodes` /
280
- * `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.
281
316
  */
282
317
  applyPatch(patches) {
283
318
  const state = useScene.getState();
@@ -290,11 +325,18 @@ export class SceneBridge {
290
325
  // Zod-normalised copy (which has a generated id if the caller omitted one)
291
326
  // instead of the unparsed input.
292
327
  const parsedCreateNodes = new Map();
328
+ const materialUpserts = new Map();
293
329
  for (let i = 0; i < patches.length; i++) {
294
330
  const p = patches[i];
295
331
  if (!p)
296
332
  throw new Error(`invalid patch: patches[${i}] is undefined`);
297
- 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') {
298
340
  const res = parseNode(p.node);
299
341
  if (!res.success) {
300
342
  throw new Error(`invalid patch: patches[${i}] create node failed schema: ${res.error.message}`);
@@ -302,7 +344,7 @@ export class SceneBridge {
302
344
  if (p.parentId !== undefined && !simAvailable.has(p.parentId)) {
303
345
  throw new Error(`invalid patch: patches[${i}] create parentId "${p.parentId}" not found`);
304
346
  }
305
- parsedCreateNodes.set(i, res.data);
347
+ parsedCreateNodes.set(i, p.parentId ? { ...res.data, parentId: p.parentId } : res.data);
306
348
  simAvailable.add(res.data.id);
307
349
  }
308
350
  else if (p.op === 'update') {
@@ -334,47 +376,75 @@ export class SceneBridge {
334
376
  throw new Error(`invalid patch: patches[${i}] unknown op`);
335
377
  }
336
378
  }
337
- // Dry-run succeeded — apply in order, batching adjacent ops of the same
338
- // op type so Zundo groups them tightly.
339
- const createOps = [];
340
- const updateOps = [];
341
- const deleteIds = [];
342
- const createdIds = [];
343
- // Simple approach: queue by type, flush in original order by walking
344
- // patches and interleaving flushes when the op type changes, so ids
345
- // created/updated/deleted stay temporally consistent.
346
- const flush = (kind) => {
347
- if (kind !== 'create' && createOps.length > 0) {
348
- useScene.getState().createNodes(createOps);
349
- createOps.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;
350
397
  }
351
- if (kind !== 'update' && updateOps.length > 0) {
352
- useScene.getState().updateNodes(updateOps);
353
- updateOps.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 });
354
408
  }
355
- if (kind !== 'delete' && deleteIds.length > 0) {
356
- useScene.getState().deleteNodes(deleteIds);
357
- deleteIds.length = 0;
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];
358
414
  }
359
- };
360
- for (let i = 0; i < patches.length; i++) {
361
- const p = patches[i];
362
- if (p.op === 'create') {
363
- flush('create');
364
- const parsedNode = parsedCreateNodes.get(i);
365
- createOps.push({ node: parsedNode, parentId: p.parentId });
366
- createdIds.push(parsedNode.id);
367
- }
368
- else if (p.op === 'update') {
369
- flush('update');
370
- 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);
371
439
  }
372
- else {
373
- flush('delete');
374
- deleteIds.push(p.id);
440
+ finally {
441
+ resumeSpaceDetection();
375
442
  }
376
- }
377
- 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]);
378
448
  // Compute actual deleted ids by diffing pre/post snapshots.
379
449
  const postNodes = useScene.getState().nodes;
380
450
  const deletedIds = [];
@@ -388,6 +458,40 @@ export class SceneBridge {
388
458
  createdIds,
389
459
  };
390
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
+ }
391
495
  /** Undo. Returns the number of steps actually undone. */
392
496
  undo(steps = 1) {
393
497
  const before = useScene.temporal.getState().pastStates.length;
@@ -451,6 +555,82 @@ export class SceneBridge {
451
555
  clearHistory() {
452
556
  useScene.temporal.getState().clear();
453
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
+ }
454
634
  // ---- internal helpers ----
455
635
  /**
456
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":"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"}
@@ -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"}