@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,8 @@
1
- import { resolveStairTotalRise } from '@pascal-app/core';
2
- import { CeilingNode, getActiveRoofHeight, LevelNode, RoofNode, RoofSegmentNode, SlabNode, StairNode, StairSegmentNode, WallNode, } from '@pascal-app/core/schema';
1
+ import { createZone, generateId } from '@pascal-app/core';
2
+ import { unknownMaterialPresetRefusal } from '@pascal-app/core/agent-operations';
3
+ import { getActiveRoofHeight, LevelNode, RoofNode, RoofSegmentNode } from '@pascal-app/core/schema';
3
4
  import { z } from 'zod';
4
- import { ADDITIVE_TOOL_ANNOTATIONS, DESTRUCTIVE_TOOL_ANNOTATIONS } from './annotations.js';
5
+ import { ADDITIVE_TOOL_ANNOTATIONS } from './annotations.js';
5
6
  import { liveSyncOutput, persistencePayload, publishLiveSceneSnapshot } from './live-sync.js';
6
7
  import { measurement } from './measurement.js';
7
8
  import { NodeIdSchema, Vec2Schema, Vec3Schema } from './schemas.js';
@@ -15,7 +16,6 @@ const ROOF_TYPES = [
15
16
  'flat',
16
17
  'conical',
17
18
  ];
18
- const RAILING_MODES = ['none', 'left', 'right', 'both'];
19
19
  export const createStoryShellInput = {
20
20
  levelId: NodeIdSchema,
21
21
  footprint: z.array(Vec2Schema).min(3),
@@ -27,9 +27,17 @@ export const createStoryShellInput = {
27
27
  positive: true,
28
28
  description: 'Wall thickness.',
29
29
  }).default(0.16),
30
- createSlab: z.boolean().default(true),
31
- createCeiling: z.boolean().default(true),
32
- slabElevation: measurement('length', 'm', { description: 'Slab elevation.' }).default(0.1),
30
+ createSlab: z
31
+ .boolean()
32
+ .default(true)
33
+ .describe('Floor intent for the rooms inside the shell. False records hasFloor: false.'),
34
+ createCeiling: z
35
+ .boolean()
36
+ .default(true)
37
+ .describe('Ceiling intent for the rooms inside the shell. False records hasCeiling: false.'),
38
+ slabElevation: measurement('length', 'm', {
39
+ description: "Walking surface of the derived plate, stored as the room's floor elevation.",
40
+ }).default(0.1),
33
41
  ceilingHeight: measurement('length', 'm', {
34
42
  positive: true,
35
43
  description: 'Ceiling height.',
@@ -42,7 +50,11 @@ export const createStoryShellInput = {
42
50
  export const createStoryShellOutput = {
43
51
  levelId: z.string(),
44
52
  wallIds: z.array(z.string()),
53
+ /** Zones the reconciler derived for the enclosed faces. */
54
+ zoneIds: z.array(z.string()),
55
+ /** Derived floor plate. `null` when floors were declined or nothing reconciled. */
45
56
  slabId: z.string().nullable(),
57
+ /** Derived ceiling. `null` when ceilings were declined or nothing reconciled. */
46
58
  ceilingId: z.string().nullable(),
47
59
  createdIds: z.array(z.string()),
48
60
  ...liveSyncOutput,
@@ -80,48 +92,6 @@ export const createRoofOutput = {
80
92
  roofSegmentId: z.string(),
81
93
  ...liveSyncOutput,
82
94
  };
83
- export const createStairBetweenLevelsInput = {
84
- fromLevelId: NodeIdSchema,
85
- toLevelId: NodeIdSchema,
86
- position: Vec3Schema,
87
- rotation: measurement('angle', 'rad', { description: 'Y-axis rotation.' }).default(0),
88
- width: measurement('length', 'm', { positive: true, description: 'Stair width.' }).default(1),
89
- runLength: measurement('length', 'm', {
90
- positive: true,
91
- description: 'Horizontal run length.',
92
- }).default(3),
93
- totalRise: measurement('length', 'm', {
94
- positive: true,
95
- description: 'Total vertical rise.',
96
- }).optional(),
97
- stepCount: z.number().int().positive().default(14),
98
- railingMode: z.enum(RAILING_MODES).default('both'),
99
- destinationSlabId: NodeIdSchema.optional(),
100
- sourceCeilingId: NodeIdSchema.optional(),
101
- createDestinationSlabOpening: z.boolean().default(true),
102
- createSourceCeilingOpening: z.boolean().default(true),
103
- openingWidth: measurement('length', 'm', {
104
- positive: true,
105
- description: 'Floor opening width.',
106
- }).optional(),
107
- openingLength: measurement('length', 'm', {
108
- positive: true,
109
- description: 'Floor opening length.',
110
- }).optional(),
111
- openingOffset: measurement('length', 'm', { min: 0, description: 'Opening offset.' }).default(0),
112
- openingCenter: Vec2Schema.optional(),
113
- openingRotation: measurement('angle', 'rad', { description: 'Opening rotation.' }).optional(),
114
- materialPreset: z.string().optional(),
115
- name: z.string().optional(),
116
- };
117
- export const createStairBetweenLevelsOutput = {
118
- stairId: z.string(),
119
- stairSegmentId: z.string(),
120
- destinationSlabId: z.string().nullable(),
121
- sourceCeilingId: z.string().nullable(),
122
- openingPolygon: z.array(Vec2Schema),
123
- ...liveSyncOutput,
124
- };
125
95
  function textResult(payload) {
126
96
  return {
127
97
  content: [{ type: 'text', text: JSON.stringify(payload) }],
@@ -159,114 +129,124 @@ function nextLevelIndex(bridge, buildingId, referenceLevel) {
159
129
  const candidate = referenceIndex + 1;
160
130
  return existing.includes(candidate) ? Math.max(candidate, ...existing) + 1 : candidate;
161
131
  }
162
- function nodesOnLevel(bridge, levelId) {
163
- return Object.values(bridge.getNodes()).filter((node) => node.id !== levelId && bridge.resolveLevelId(node.id) === levelId);
164
- }
165
- function firstNodeOnLevel(bridge, levelId, type) {
166
- return nodesOnLevel(bridge, levelId).find((node) => node.type === type) ?? null;
132
+ /** Room zones that appeared on the level while this tool call ran. */
133
+ function newRoomZoneIds(bridge, levelId, before) {
134
+ return Object.values(bridge.getNodes())
135
+ .filter((node) => node.type === 'zone' &&
136
+ node.parentId === levelId &&
137
+ node.spaceRole === 'room' &&
138
+ !before.has(node.id))
139
+ .map((node) => node.id)
140
+ .sort();
167
141
  }
168
- function rotatePoint(x, z, rotation) {
169
- const cos = Math.cos(rotation);
170
- const sin = Math.sin(rotation);
171
- return [x * cos + z * sin, -x * sin + z * cos];
172
- }
173
- function rectangularOpening(args) {
174
- const width = args.width + args.offset * 2;
175
- const length = args.length + args.offset * 2;
176
- const center = args.center ?? [
177
- args.position[0],
178
- args.position[2] + args.length / 2,
179
- ];
180
- const rotation = args.openingRotation ?? args.rotation;
181
- const halfW = width / 2;
182
- const halfL = length / 2;
183
- const local = [
184
- [-halfW, -halfL],
185
- [halfW, -halfL],
186
- [halfW, halfL],
187
- [-halfW, halfL],
188
- ];
189
- return local.map(([x, z]) => {
190
- const [rx, rz] = rotatePoint(x, z, rotation);
191
- return [center[0] + rx, center[1] + rz];
192
- });
193
- }
194
- function withHole(surface, hole) {
195
- return {
196
- holes: [...(surface.holes ?? []), hole],
197
- holeMetadata: [...(surface.holeMetadata ?? []), { source: 'manual' }],
198
- };
142
+ function derivedShellSurfaces(bridge, levelId, zoneIds) {
143
+ const children = Object.values(bridge.getNodes()).filter((node) => node.parentId === levelId);
144
+ const plate = children
145
+ .filter((node) => node.type === 'slab')
146
+ .sort((a, b) => Number(a.plateRole === 'base') - Number(b.plateRole === 'base'))
147
+ .find((node) => node.type === 'slab' &&
148
+ node.boundary === 'auto' &&
149
+ (node.zoneIds ?? []).some((id) => zoneIds.includes(id)));
150
+ const ceiling = children.find((node) => node.type === 'ceiling' && node.boundary === 'auto' && zoneIds.includes(node.zoneId ?? ''));
151
+ return { slabId: plate?.id ?? null, ceilingId: ceiling?.id ?? null };
199
152
  }
200
153
  export function registerConstructionTools(server, bridge) {
201
154
  server.registerTool('create_story_shell', {
202
155
  title: 'Create story shell',
203
- description: 'Create one level-owned building shell from a footprint: perimeter walls plus optional slab and ceiling. Use once per story; do not make first-floor walls span multiple stories.',
156
+ description: 'Create one level-owned building shell from a footprint: the perimeter walls. The floor plate and the ceiling are DERIVED from the rooms the walls enclose — never author a slab or a ceiling here, and never pass boundary/autoFromWalls; createSlab / createCeiling / slabElevation are recorded as room intent instead. Use once per story; do not make first-floor walls span multiple stories.',
204
157
  inputSchema: createStoryShellInput,
205
158
  outputSchema: createStoryShellOutput,
206
159
  annotations: ADDITIVE_TOOL_ANNOTATIONS,
207
160
  }, async ({ levelId, footprint, wallHeight, wallThickness, createSlab, createCeiling, slabElevation, ceilingHeight, namePrefix, wallMaterialPreset, slabMaterialPreset, ceilingMaterialPreset, }) => {
161
+ const preset = unknownMaterialPresetRefusal({
162
+ wallMaterialPreset,
163
+ slabMaterialPreset,
164
+ ceilingMaterialPreset,
165
+ });
166
+ if (preset)
167
+ throw new Error(preset);
208
168
  const level = assertNode(bridge, levelId, 'level');
209
169
  if (isRoofLevel(level)) {
210
170
  throw new Error(`Cannot create a story shell on roof support level ${levelId}; create or choose an occupied story level instead`);
211
171
  }
212
172
  const points = footprint;
213
- const wallIds = [];
214
- const patches = [];
215
- for (let i = 0; i < points.length; i++) {
216
- const wall = WallNode.parse({
217
- name: namePrefix ? `${namePrefix} Wall ${i + 1}` : undefined,
218
- start: points[i],
219
- end: points[(i + 1) % points.length],
173
+ const before = new Set(Object.keys(bridge.getNodes()));
174
+ const plan = createZone(bridge.getNodes(), {
175
+ levelId,
176
+ polygon: points,
177
+ name: namePrefix ?? 'Room',
178
+ enclose: true,
179
+ mintId: generateId,
180
+ wall: {
220
181
  thickness: wallThickness,
221
182
  ...(wallHeight !== undefined ? { height: wallHeight } : {}),
222
- frontSide: 'exterior',
223
- backSide: 'interior',
224
- ...(wallMaterialPreset ? { materialPreset: wallMaterialPreset } : {}),
225
- metadata: { role: 'exterior', storyShell: true },
226
- });
227
- wallIds.push(wall.id);
228
- patches.push({ op: 'create', node: wall, parentId: levelId });
229
- }
230
- let slabId = null;
231
- if (createSlab) {
232
- const slab = SlabNode.parse({
233
- name: namePrefix ? `${namePrefix} Slab` : undefined,
234
- polygon: points,
235
- elevation: slabElevation,
236
- // Grounded solid: underside on the level plane, so the created
237
- // story slab occupies [0, slabElevation] like the legacy
238
- // extrude-from-zero model.
239
- thickness: Math.max(slabElevation, 0),
240
- recessed: slabElevation < 0,
241
- ...(slabMaterialPreset ? { materialPreset: slabMaterialPreset } : {}),
242
- metadata: { role: 'story-slab' },
243
- });
244
- slabId = slab.id;
245
- patches.push({ op: 'create', node: slab, parentId: levelId });
246
- }
247
- let ceilingId = null;
248
- if (createCeiling) {
249
- // Height-less unless the caller pinned one: a new story ceiling
250
- // follows the level top automatically.
251
- const explicitCeilingHeight = ceilingHeight ?? wallHeight;
252
- const ceiling = CeilingNode.parse({
253
- name: namePrefix ? `${namePrefix} Ceiling` : undefined,
254
- polygon: points,
255
- ...(explicitCeilingHeight !== undefined ? { height: explicitCeilingHeight } : {}),
256
- ...(ceilingMaterialPreset ? { materialPreset: ceilingMaterialPreset } : {}),
257
- metadata: { role: 'story-ceiling' },
258
- });
259
- ceilingId = ceiling.id;
260
- patches.push({ op: 'create', node: ceiling, parentId: levelId });
261
- }
262
- const result = bridge.applyPatch(patches);
183
+ },
184
+ intent: {
185
+ floor: { elevation: slabElevation },
186
+ hasFloor: createSlab,
187
+ hasCeiling: createCeiling,
188
+ },
189
+ });
190
+ const wallIds = plan.changes.flatMap((change) => change.op === 'create' && change.node.type === 'wall' ? [change.node.id] : []);
191
+ bridge.runAsSingleHistoryStep(() => {
192
+ bridge.applyPatch(plan.changes.map((change) => change.op === 'create'
193
+ ? {
194
+ ...change,
195
+ parentId: change.node.parentId,
196
+ node: change.node.type === 'wall'
197
+ ? {
198
+ ...change.node,
199
+ ...(namePrefix
200
+ ? { name: `${namePrefix} Wall ${wallIds.indexOf(change.node.id) + 1}` }
201
+ : {}),
202
+ ...(wallMaterialPreset ? { materialPreset: wallMaterialPreset } : {}),
203
+ metadata: { role: 'exterior', storyShell: true },
204
+ }
205
+ : change.node,
206
+ }
207
+ : change));
208
+ bridge.deriveStructure([levelId]);
209
+ });
210
+ const zoneIds = newRoomZoneIds(bridge, levelId, before);
211
+ // Finishes and an explicit ceiling height stay editable on derived
212
+ // construction, so they are applied to whatever the reconciler built.
213
+ const derived = derivedShellSurfaces(bridge, levelId, zoneIds);
214
+ const explicitCeilingHeight = ceilingHeight ?? wallHeight;
215
+ const finishes = [
216
+ ...(derived.slabId && slabMaterialPreset
217
+ ? [
218
+ {
219
+ op: 'update',
220
+ id: derived.slabId,
221
+ data: {
222
+ ...(slabMaterialPreset ? { materialPreset: slabMaterialPreset } : {}),
223
+ },
224
+ },
225
+ ]
226
+ : []),
227
+ ...(derived.ceilingId && (ceilingMaterialPreset || explicitCeilingHeight !== undefined)
228
+ ? [
229
+ {
230
+ op: 'update',
231
+ id: derived.ceilingId,
232
+ data: {
233
+ ...(ceilingMaterialPreset ? { materialPreset: ceilingMaterialPreset } : {}),
234
+ ...(explicitCeilingHeight !== undefined ? { height: explicitCeilingHeight } : {}),
235
+ },
236
+ },
237
+ ]
238
+ : []),
239
+ ];
240
+ if (finishes.length > 0)
241
+ bridge.applyPatch(finishes);
263
242
  const persistence = await publishLiveSceneSnapshot(bridge, 'create_story_shell');
264
243
  return textResult({
265
244
  levelId,
266
245
  wallIds,
267
- slabId,
268
- ceilingId,
269
- createdIds: result.createdIds,
246
+ zoneIds,
247
+ slabId: derived.slabId,
248
+ ceilingId: derived.ceilingId,
249
+ createdIds: Object.keys(bridge.getNodes()).filter((id) => !before.has(id)),
270
250
  ...persistencePayload(persistence),
271
251
  });
272
252
  });
@@ -277,6 +257,9 @@ export function registerConstructionTools(server, bridge) {
277
257
  outputSchema: createRoofOutput,
278
258
  annotations: ADDITIVE_TOOL_ANNOTATIONS,
279
259
  }, async ({ levelId, roofLevelId, useDedicatedRoofLevel, roofLevelLabel, roofLevelElevation, roofLevelHeight, center, width, depth, roofType, pitch, wallHeight, wallThickness, overhang, materialPreset, name, }) => {
260
+ const preset = unknownMaterialPresetRefusal({ materialPreset });
261
+ if (preset)
262
+ throw new Error(preset);
280
263
  const effectiveWidth = roofType === 'conical' ? Math.max(width, depth) : width;
281
264
  const effectiveDepth = roofType === 'conical' ? effectiveWidth : depth;
282
265
  // Peak height is derived from pitch + footprint + type; we still
@@ -350,97 +333,4 @@ export function registerConstructionTools(server, bridge) {
350
333
  ...persistencePayload(persistence),
351
334
  });
352
335
  });
353
- server.registerTool('create_stair_between_levels', {
354
- title: 'Create stair between levels',
355
- description: 'Create a straight stair and a single rectangular manual opening in the destination slab/source ceiling. This disables stair auto-opening mode to avoid duplicate or irregular holes.',
356
- inputSchema: createStairBetweenLevelsInput,
357
- outputSchema: createStairBetweenLevelsOutput,
358
- annotations: DESTRUCTIVE_TOOL_ANNOTATIONS,
359
- }, async ({ fromLevelId, toLevelId, position, rotation, width, runLength, totalRise, stepCount, railingMode, destinationSlabId, sourceCeilingId, createDestinationSlabOpening, createSourceCeilingOpening, openingWidth, openingLength, openingOffset, openingCenter, openingRotation, materialPreset, name, }) => {
360
- const fromLevel = assertNode(bridge, fromLevelId, 'level');
361
- const toLevel = assertNode(bridge, toLevelId, 'level');
362
- if (isRoofLevel(fromLevel) || isRoofLevel(toLevel)) {
363
- throw new Error('Roof support levels are not occupied stories; create a separate occupied attic/story level if a stair-accessible attic is required');
364
- }
365
- const stairDraft = StairNode.parse({
366
- name: name ?? 'Stair',
367
- position: position,
368
- rotation,
369
- stairType: 'straight',
370
- fromLevelId,
371
- toLevelId,
372
- slabOpeningMode: 'none',
373
- openingOffset,
374
- width,
375
- ...(totalRise !== undefined ? { totalRise } : {}),
376
- stepCount,
377
- railingMode,
378
- children: [],
379
- ...(materialPreset ? { materialPreset } : {}),
380
- metadata: {
381
- openingManaged: 'manual-rectangular',
382
- },
383
- });
384
- const riseNodes = {
385
- ...bridge.getNodes(),
386
- [fromLevel.id]: {
387
- ...fromLevel,
388
- children: [...fromLevel.children, stairDraft.id],
389
- },
390
- [stairDraft.id]: stairDraft,
391
- };
392
- const resolvedTotalRise = resolveStairTotalRise(stairDraft, riseNodes);
393
- const segment = StairSegmentNode.parse({
394
- segmentType: 'stair',
395
- width,
396
- length: runLength,
397
- height: resolvedTotalRise,
398
- stepCount,
399
- ...(materialPreset ? { materialPreset } : {}),
400
- });
401
- const stair = { ...stairDraft, children: [segment.id] };
402
- const openingPolygon = rectangularOpening({
403
- position: position,
404
- rotation,
405
- width: openingWidth ?? width,
406
- length: openingLength ?? runLength,
407
- offset: openingOffset,
408
- center: openingCenter,
409
- openingRotation,
410
- });
411
- const patches = [
412
- { op: 'create', node: stair, parentId: fromLevelId },
413
- { op: 'create', node: segment, parentId: stair.id },
414
- ];
415
- const destinationSlab = destinationSlabId !== undefined
416
- ? assertNode(bridge, destinationSlabId, 'slab')
417
- : firstNodeOnLevel(bridge, toLevelId, 'slab');
418
- if (createDestinationSlabOpening && destinationSlab?.type === 'slab') {
419
- patches.push({
420
- op: 'update',
421
- id: destinationSlab.id,
422
- data: withHole(destinationSlab, openingPolygon),
423
- });
424
- }
425
- const sourceCeiling = sourceCeilingId !== undefined
426
- ? assertNode(bridge, sourceCeilingId, 'ceiling')
427
- : firstNodeOnLevel(bridge, fromLevelId, 'ceiling');
428
- if (createSourceCeilingOpening && sourceCeiling?.type === 'ceiling') {
429
- patches.push({
430
- op: 'update',
431
- id: sourceCeiling.id,
432
- data: withHole(sourceCeiling, openingPolygon),
433
- });
434
- }
435
- bridge.applyPatch(patches);
436
- const persistence = await publishLiveSceneSnapshot(bridge, 'create_stair_between_levels');
437
- return textResult({
438
- stairId: stair.id,
439
- stairSegmentId: segment.id,
440
- destinationSlabId: destinationSlab?.id ?? null,
441
- sourceCeilingId: sourceCeiling?.id ?? null,
442
- openingPolygon,
443
- ...persistencePayload(persistence),
444
- });
445
- });
446
336
  }
@@ -14,6 +14,8 @@ export declare const createUnitInput: {
14
14
  memberZoneIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
15
15
  };
16
16
  export declare const createUnitOutput: {
17
+ project: z.ZodOptional<z.ZodNullable<z.ZodString>>;
18
+ unsaved: z.ZodOptional<z.ZodString>;
17
19
  persistence: z.ZodOptional<z.ZodObject<{
18
20
  status: z.ZodEnum<{
19
21
  unbound: "unbound";
@@ -1 +1 @@
1
- {"version":3,"file":"create-unit.d.ts","sourceRoot":"","sources":["../../src/tools/create-unit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAMpD,eAAO,MAAM,eAAe;;;;;;;;;;;CAM3B,CAAA;AAED,eAAO,MAAM,gBAAgB;;;;;;;;;CAG5B,CAAA;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAuBnF"}
1
+ {"version":3,"file":"create-unit.d.ts","sourceRoot":"","sources":["../../src/tools/create-unit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAMpD,eAAO,MAAM,eAAe;;;;;;;;;;;CAM3B,CAAA;AAED,eAAO,MAAM,gBAAgB;;;;;;;;;;;CAG5B,CAAA;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAuBnF"}
@@ -1 +1 @@
1
- {"version":3,"file":"describe-node.d.ts","sourceRoot":"","sources":["../../src/tools/describe-node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAMpD,eAAO,MAAM,iBAAiB;;CAE7B,CAAA;AAED,eAAO,MAAM,kBAAkB;;;;;;;;CAQ9B,CAAA;AA0CD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAiDrF"}
1
+ {"version":3,"file":"describe-node.d.ts","sourceRoot":"","sources":["../../src/tools/describe-node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAIxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAKpD,eAAO,MAAM,iBAAiB;;CAE7B,CAAA;AAED,eAAO,MAAM,kBAAkB;;;;;;;;CAQ9B,CAAA;AA4CD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAiDrF"}
@@ -1,8 +1,8 @@
1
- import { resolveCeilingHeight } from '@pascal-app/core';
1
+ import { floorFootprintName, getLevelDisplayName, resolveCeilingHeight } from '@pascal-app/core';
2
+ import { wallResolvedHeight } from '@pascal-app/core/agent-operations';
2
3
  import { z } from 'zod';
3
4
  import { READ_ONLY_TOOL_ANNOTATIONS } from './annotations.js';
4
5
  import { ErrorCode, throwMcpError } from './errors.js';
5
- import { resolveReportedWallHeight } from './scene-query.js';
6
6
  import { NodeIdSchema } from './schemas.js';
7
7
  export const describeNodeInput = {
8
8
  id: NodeIdSchema,
@@ -26,11 +26,11 @@ function describe(node, bridge) {
26
26
  const [x1, z1] = node.start;
27
27
  const [x2, z2] = node.end;
28
28
  const t = node.thickness ?? 0.1;
29
- const h = resolveReportedWallHeight(bridge, node);
29
+ const h = wallResolvedHeight(bridge.getNodes(), node);
30
30
  return `Wall from (${x1},${z1}) to (${x2},${z2}), thickness ${t.toFixed(2)}m, height ${h.toFixed(2)}m`;
31
31
  }
32
32
  case 'level':
33
- return `Level ${node.level}`;
33
+ return getLevelDisplayName(node);
34
34
  case 'building': {
35
35
  const [x, y, z] = node.position;
36
36
  return `Building at (${x},${y},${z})`;
@@ -40,7 +40,9 @@ function describe(node, bridge) {
40
40
  case 'zone':
41
41
  return `Zone "${node.name}" with ${node.polygon.length} vertices`;
42
42
  case 'slab':
43
- return `Slab with ${node.polygon.length} vertices`;
43
+ return node.plateRole === 'base'
44
+ ? `Floor footprint "${floorFootprintName(bridge.getNodes(), node)}" with ${node.polygon.length} vertices`
45
+ : `Slab with ${node.polygon.length} vertices`;
44
46
  case 'ceiling':
45
47
  return `Ceiling with ${node.polygon.length} vertices, height ${resolveCeilingHeight(node, bridge.getNodes()).toFixed(2)}m`;
46
48
  case 'door':
@@ -76,7 +78,7 @@ export function registerDescribeNode(server, bridge) {
76
78
  const properties = n.type === 'wall'
77
79
  ? {
78
80
  ...n,
79
- resolvedHeight: resolveReportedWallHeight(bridge, n),
81
+ resolvedHeight: wallResolvedHeight(bridge.getNodes(), n),
80
82
  heightIsExplicit: n.height !== undefined,
81
83
  }
82
84
  : n;
@@ -16,5 +16,16 @@ export declare function toolError(message: string, data?: Record<string, unknown
16
16
  }[];
17
17
  isError: true;
18
18
  };
19
+ /**
20
+ * A core operation's refusal (`AgentRefusal`) as the same `{ error, code, ...details }` answer the
21
+ * hosted chat returns; anything else is a real failure and is rethrown.
22
+ */
23
+ export declare function refusalResult(error: unknown): {
24
+ content: {
25
+ type: "text";
26
+ text: string;
27
+ }[];
28
+ isError: true;
29
+ };
19
30
  export { ErrorCode, McpError };
20
31
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/tools/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAA;AAExE;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,KAAK,CAErF;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B;IACD,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACzC,OAAO,EAAE,IAAI,CAAA;CACd,CAUA;AAED,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAA"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/tools/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAA;AAGxE;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,KAAK,CAErF;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B;IACD,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACzC,OAAO,EAAE,IAAI,CAAA;CACd,CAUA;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO;aAlBjC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE;aAChC,IAAI;EAoBd;AAED,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAA"}
@@ -1,4 +1,5 @@
1
1
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
+ import { isAgentRefusal } from '@pascal-app/core/agent-tools';
2
3
  /**
3
4
  * Throw a structured MCP error. The SDK translates `McpError` into a
4
5
  * JSON-RPC error response automatically.
@@ -22,4 +23,13 @@ export function toolError(message, data) {
22
23
  isError: true,
23
24
  };
24
25
  }
26
+ /**
27
+ * A core operation's refusal (`AgentRefusal`) as the same `{ error, code, ...details }` answer the
28
+ * hosted chat returns; anything else is a real failure and is rethrown.
29
+ */
30
+ export function refusalResult(error) {
31
+ if (isAgentRefusal(error))
32
+ return toolError(error.message, { code: error.code, ...error.details });
33
+ throw error;
34
+ }
25
35
  export { ErrorCode, McpError };
@@ -2,28 +2,12 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { z } from 'zod';
3
3
  import type { SceneOperations } from '../operations/index.js';
4
4
  export declare const findNodesInput: {
5
- type: z.ZodOptional<z.ZodEnum<{
6
- site: "site";
7
- building: "building";
8
- level: "level";
9
- wall: "wall";
10
- fence: "fence";
11
- slab: "slab";
12
- door: "door";
13
- item: "item";
14
- zone: "zone";
15
- stair: "stair";
16
- ceiling: "ceiling";
17
- roof: "roof";
18
- "roof-segment": "roof-segment";
19
- "stair-segment": "stair-segment";
20
- scan: "scan";
21
- guide: "guide";
22
- window: "window";
23
- }>>;
5
+ type: z.ZodOptional<z.ZodString>;
24
6
  parentId: z.ZodOptional<z.ZodString>;
25
7
  levelId: z.ZodOptional<z.ZodString>;
26
8
  zoneId: z.ZodOptional<z.ZodString>;
9
+ sourceId: z.ZodOptional<z.ZodString>;
10
+ sourceIdPrefix: z.ZodOptional<z.ZodString>;
27
11
  };
28
12
  export declare const findNodesOutput: {
29
13
  nodes: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -1 +1 @@
1
- {"version":3,"file":"find-nodes.d.ts","sourceRoot":"","sources":["../../src/tools/find-nodes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAwBpD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;CAK1B,CAAA;AAED,eAAO,MAAM,eAAe;;CAE3B,CAAA;AAkCD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAwDlF"}
1
+ {"version":3,"file":"find-nodes.d.ts","sourceRoot":"","sources":["../../src/tools/find-nodes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAKxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAOpD,eAAO,MAAM,cAAc;;;;;;;CAyB1B,CAAA;AAED,eAAO,MAAM,eAAe;;CAE3B,CAAA;AAmHD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAuFlF"}