@pascal-app/mcp 1.0.3 → 1.0.5

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 (305) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +181 -56
  3. package/dist/bin/pascal-mcp.js +9 -3
  4. package/dist/bridge/agent-session.d.ts +18 -0
  5. package/dist/bridge/agent-session.d.ts.map +1 -0
  6. package/dist/bridge/agent-session.js +15 -0
  7. package/dist/bridge/index.d.ts +1 -1
  8. package/dist/bridge/index.d.ts.map +1 -1
  9. package/dist/bridge/scene-bridge.d.ts +42 -6
  10. package/dist/bridge/scene-bridge.d.ts.map +1 -1
  11. package/dist/bridge/scene-bridge.js +232 -45
  12. package/dist/index.d.ts +3 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +1 -0
  15. package/dist/lib/safe-fetch.d.ts +22 -4
  16. package/dist/lib/safe-fetch.d.ts.map +1 -1
  17. package/dist/lib/safe-fetch.js +233 -134
  18. package/dist/operations/scene-operations.d.ts +41 -3
  19. package/dist/operations/scene-operations.d.ts.map +1 -1
  20. package/dist/operations/scene-operations.js +42 -1
  21. package/dist/prompts/from-brief.d.ts +1 -1
  22. package/dist/prompts/from-brief.d.ts.map +1 -1
  23. package/dist/prompts/from-brief.js +3 -3
  24. package/dist/prompts/index.d.ts +1 -1
  25. package/dist/prompts/index.d.ts.map +1 -1
  26. package/dist/prompts/iterate-on-feedback.d.ts +1 -1
  27. package/dist/prompts/iterate-on-feedback.d.ts.map +1 -1
  28. package/dist/prompts/iterate-on-feedback.js +3 -3
  29. package/dist/prompts/renovation-from-photos.d.ts +1 -1
  30. package/dist/prompts/renovation-from-photos.d.ts.map +1 -1
  31. package/dist/prompts/renovation-from-photos.js +2 -2
  32. package/dist/prompts/scene-guidance.d.ts.map +1 -1
  33. package/dist/prompts/scene-guidance.js +13 -6
  34. package/dist/resources/agent-guide.d.ts +1 -1
  35. package/dist/resources/agent-guide.d.ts.map +1 -1
  36. package/dist/resources/agent-guide.js +62 -33
  37. package/dist/resources/catalog-items.d.ts +5 -6
  38. package/dist/resources/catalog-items.d.ts.map +1 -1
  39. package/dist/resources/catalog-items.js +7 -9
  40. package/dist/resources/constraints.d.ts +1 -1
  41. package/dist/resources/constraints.d.ts.map +1 -1
  42. package/dist/resources/constraints.js +1 -1
  43. package/dist/resources/design-schema.d.ts +9 -0
  44. package/dist/resources/design-schema.d.ts.map +1 -0
  45. package/dist/resources/design-schema.js +21 -0
  46. package/dist/resources/index.d.ts +4 -2
  47. package/dist/resources/index.d.ts.map +1 -1
  48. package/dist/resources/index.js +5 -2
  49. package/dist/resources/scene-current.d.ts +1 -1
  50. package/dist/resources/scene-current.d.ts.map +1 -1
  51. package/dist/resources/scene-summary.d.ts +1 -1
  52. package/dist/resources/scene-summary.d.ts.map +1 -1
  53. package/dist/server.d.ts +25 -1
  54. package/dist/server.d.ts.map +1 -1
  55. package/dist/server.js +71 -25
  56. package/dist/storage/disk-artifact-store.d.ts +9 -0
  57. package/dist/storage/disk-artifact-store.d.ts.map +1 -0
  58. package/dist/storage/disk-artifact-store.js +46 -0
  59. package/dist/storage/index.d.ts +1 -0
  60. package/dist/storage/index.d.ts.map +1 -1
  61. package/dist/storage/index.js +1 -0
  62. package/dist/storage/sqlite-scene-store.d.ts +3 -3
  63. package/dist/storage/sqlite-scene-store.d.ts.map +1 -1
  64. package/dist/storage/sqlite-scene-store.js +4 -3
  65. package/dist/storage/types.d.ts +36 -7
  66. package/dist/storage/types.d.ts.map +1 -1
  67. package/dist/storage/types.js +12 -0
  68. package/dist/templates/empty-studio.d.ts +3 -3
  69. package/dist/templates/empty-studio.d.ts.map +1 -1
  70. package/dist/templates/garden-house.d.ts +3 -3
  71. package/dist/templates/garden-house.d.ts.map +1 -1
  72. package/dist/templates/index.d.ts.map +1 -1
  73. package/dist/templates/two-bedroom.d.ts +3 -3
  74. package/dist/templates/two-bedroom.d.ts.map +1 -1
  75. package/dist/tools/add-column.d.ts +5 -0
  76. package/dist/tools/add-column.d.ts.map +1 -0
  77. package/dist/tools/add-column.js +59 -0
  78. package/dist/tools/add-object.d.ts +76 -0
  79. package/dist/tools/add-object.d.ts.map +1 -0
  80. package/dist/tools/add-object.js +187 -0
  81. package/dist/tools/annotations.d.ts.map +1 -1
  82. package/dist/tools/apply-patch.d.ts +9 -2
  83. package/dist/tools/apply-patch.d.ts.map +1 -1
  84. package/dist/tools/apply-patch.js +36 -5
  85. package/dist/tools/asset-catalog.d.ts +9 -5
  86. package/dist/tools/asset-catalog.d.ts.map +1 -1
  87. package/dist/tools/asset-catalog.js +33 -12
  88. package/dist/tools/author-scene.d.ts +10 -0
  89. package/dist/tools/author-scene.d.ts.map +1 -0
  90. package/dist/tools/author-scene.js +140 -0
  91. package/dist/tools/check-collisions.d.ts +7 -7
  92. package/dist/tools/check-collisions.d.ts.map +1 -1
  93. package/dist/tools/check-collisions.js +7 -8
  94. package/dist/tools/clear-scene.d.ts +9 -0
  95. package/dist/tools/clear-scene.d.ts.map +1 -0
  96. package/dist/tools/clear-scene.js +59 -0
  97. package/dist/tools/collections.d.ts +4 -0
  98. package/dist/tools/collections.d.ts.map +1 -0
  99. package/dist/tools/collections.js +53 -0
  100. package/dist/tools/construction-tools.d.ts +12 -88
  101. package/dist/tools/construction-tools.d.ts.map +1 -1
  102. package/dist/tools/construction-tools.js +122 -381
  103. package/dist/tools/create-unit.d.ts +5 -3
  104. package/dist/tools/create-unit.d.ts.map +1 -1
  105. package/dist/tools/create-unit.js +2 -2
  106. package/dist/tools/errors.d.ts +15 -4
  107. package/dist/tools/errors.d.ts.map +1 -1
  108. package/dist/tools/errors.js +14 -4
  109. package/dist/tools/export-glb.d.ts +1 -1
  110. package/dist/tools/export-glb.d.ts.map +1 -1
  111. package/dist/tools/export-glb.js +2 -2
  112. package/dist/tools/export-json.d.ts +1 -1
  113. package/dist/tools/export-json.d.ts.map +1 -1
  114. package/dist/tools/export-json.js +2 -2
  115. package/dist/tools/geometry-script-worker.d.ts +2 -0
  116. package/dist/tools/geometry-script-worker.d.ts.map +1 -0
  117. package/dist/tools/geometry-script-worker.js +21 -0
  118. package/dist/tools/get-scene.d.ts +2 -1
  119. package/dist/tools/get-scene.d.ts.map +1 -1
  120. package/dist/tools/get-scene.js +5 -3
  121. package/dist/tools/honest-patch-guard.d.ts +2 -0
  122. package/dist/tools/honest-patch-guard.d.ts.map +1 -0
  123. package/dist/tools/honest-patch-guard.js +32 -0
  124. package/dist/tools/hosted-services.d.ts +4 -0
  125. package/dist/tools/hosted-services.d.ts.map +1 -0
  126. package/dist/tools/hosted-services.js +40 -0
  127. package/dist/tools/index.d.ts +12 -2
  128. package/dist/tools/index.d.ts.map +1 -1
  129. package/dist/tools/index.js +18 -32
  130. package/dist/tools/level-height-guard.d.ts +2 -0
  131. package/dist/tools/level-height-guard.d.ts.map +1 -0
  132. package/dist/tools/level-height-guard.js +11 -0
  133. package/dist/tools/list-units.d.ts +2 -2
  134. package/dist/tools/list-units.d.ts.map +1 -1
  135. package/dist/tools/list-units.js +2 -2
  136. package/dist/tools/live-sync.d.ts +25 -6
  137. package/dist/tools/live-sync.d.ts.map +1 -1
  138. package/dist/tools/live-sync.js +48 -27
  139. package/dist/tools/local-geometry-scripts.d.ts +8 -0
  140. package/dist/tools/local-geometry-scripts.d.ts.map +1 -0
  141. package/dist/tools/local-geometry-scripts.js +67 -0
  142. package/dist/tools/measure.d.ts +7 -1
  143. package/dist/tools/measure.d.ts.map +1 -1
  144. package/dist/tools/measure.js +38 -70
  145. package/dist/tools/measurement.d.ts +1 -36
  146. package/dist/tools/measurement.d.ts.map +1 -1
  147. package/dist/tools/measurement.js +2 -94
  148. package/dist/tools/node-world-point.d.ts +37 -0
  149. package/dist/tools/node-world-point.d.ts.map +1 -0
  150. package/dist/tools/node-world-point.js +457 -0
  151. package/dist/tools/normalize-schema-dialect.d.ts +1 -1
  152. package/dist/tools/normalize-schema-dialect.d.ts.map +1 -1
  153. package/dist/tools/normalize-schema-dialect.js +22 -3
  154. package/dist/tools/patch-guards.d.ts +60 -0
  155. package/dist/tools/patch-guards.d.ts.map +1 -0
  156. package/dist/tools/patch-guards.js +256 -0
  157. package/dist/tools/photo-to-scene/index.d.ts +1 -1
  158. package/dist/tools/photo-to-scene/index.d.ts.map +1 -1
  159. package/dist/tools/photo-to-scene/photo-to-scene.d.ts +3 -3
  160. package/dist/tools/photo-to-scene/photo-to-scene.d.ts.map +1 -1
  161. package/dist/tools/photo-to-scene/photo-to-scene.js +10 -9
  162. package/dist/tools/project-files.d.ts +4 -0
  163. package/dist/tools/project-files.d.ts.map +1 -0
  164. package/dist/tools/project-files.js +30 -0
  165. package/dist/tools/redo.d.ts +4 -2
  166. package/dist/tools/redo.d.ts.map +1 -1
  167. package/dist/tools/redo.js +4 -4
  168. package/dist/tools/retired-tools.d.ts +10 -0
  169. package/dist/tools/retired-tools.d.ts.map +1 -0
  170. package/dist/tools/retired-tools.js +22 -0
  171. package/dist/tools/roof-level-guard.d.ts +2 -0
  172. package/dist/tools/roof-level-guard.d.ts.map +1 -0
  173. package/dist/tools/roof-level-guard.js +22 -0
  174. package/dist/tools/room-tools.d.ts +36 -98
  175. package/dist/tools/room-tools.d.ts.map +1 -1
  176. package/dist/tools/room-tools.js +130 -565
  177. package/dist/tools/scene-lifecycle/create-project.d.ts +1 -1
  178. package/dist/tools/scene-lifecycle/create-project.d.ts.map +1 -1
  179. package/dist/tools/scene-lifecycle/create-project.js +31 -6
  180. package/dist/tools/scene-lifecycle/delete-scene.d.ts +3 -1
  181. package/dist/tools/scene-lifecycle/delete-scene.d.ts.map +1 -1
  182. package/dist/tools/scene-lifecycle/delete-scene.js +10 -9
  183. package/dist/tools/scene-lifecycle/get-project-status.d.ts +1 -1
  184. package/dist/tools/scene-lifecycle/get-project-status.d.ts.map +1 -1
  185. package/dist/tools/scene-lifecycle/get-project-status.js +6 -6
  186. package/dist/tools/scene-lifecycle/index.d.ts +1 -1
  187. package/dist/tools/scene-lifecycle/index.d.ts.map +1 -1
  188. package/dist/tools/scene-lifecycle/list-scenes.d.ts +1 -1
  189. package/dist/tools/scene-lifecycle/list-scenes.d.ts.map +1 -1
  190. package/dist/tools/scene-lifecycle/list-scenes.js +4 -4
  191. package/dist/tools/scene-lifecycle/load-scene.d.ts +2 -2
  192. package/dist/tools/scene-lifecycle/load-scene.d.ts.map +1 -1
  193. package/dist/tools/scene-lifecycle/load-scene.js +5 -5
  194. package/dist/tools/scene-lifecycle/metadata.d.ts +1 -1
  195. package/dist/tools/scene-lifecycle/rename-scene.d.ts +1 -1
  196. package/dist/tools/scene-lifecycle/rename-scene.d.ts.map +1 -1
  197. package/dist/tools/scene-lifecycle/rename-scene.js +6 -6
  198. package/dist/tools/scene-lifecycle/save-scene.d.ts +6 -3
  199. package/dist/tools/scene-lifecycle/save-scene.d.ts.map +1 -1
  200. package/dist/tools/scene-lifecycle/save-scene.js +124 -16
  201. package/dist/tools/scene-lifecycle/test-utils.d.ts +3 -3
  202. package/dist/tools/scene-lifecycle/test-utils.d.ts.map +1 -1
  203. package/dist/tools/scene-lifecycle/test-utils.js +2 -1
  204. package/dist/tools/set-unit-members.d.ts +4 -2
  205. package/dist/tools/set-unit-members.d.ts.map +1 -1
  206. package/dist/tools/set-unit-members.js +6 -6
  207. package/dist/tools/set-zone.d.ts +4 -2
  208. package/dist/tools/set-zone.d.ts.map +1 -1
  209. package/dist/tools/set-zone.js +30 -18
  210. package/dist/tools/shared-tools.d.ts +40 -0
  211. package/dist/tools/shared-tools.d.ts.map +1 -0
  212. package/dist/tools/shared-tools.js +574 -0
  213. package/dist/tools/structure-tools.d.ts +57 -0
  214. package/dist/tools/structure-tools.d.ts.map +1 -0
  215. package/dist/tools/structure-tools.js +57 -0
  216. package/dist/tools/templates/create-from-template.d.ts +2 -2
  217. package/dist/tools/templates/create-from-template.d.ts.map +1 -1
  218. package/dist/tools/templates/create-from-template.js +6 -6
  219. package/dist/tools/templates/create-house-from-brief.d.ts +1 -1
  220. package/dist/tools/templates/create-house-from-brief.d.ts.map +1 -1
  221. package/dist/tools/templates/create-house-from-brief.js +6 -6
  222. package/dist/tools/templates/index.d.ts +1 -1
  223. package/dist/tools/templates/index.d.ts.map +1 -1
  224. package/dist/tools/templates/list-templates.d.ts +1 -1
  225. package/dist/tools/templates/list-templates.d.ts.map +1 -1
  226. package/dist/tools/templates/list-templates.js +2 -2
  227. package/dist/tools/undo.d.ts +4 -2
  228. package/dist/tools/undo.d.ts.map +1 -1
  229. package/dist/tools/undo.js +4 -4
  230. package/dist/tools/unit-members.d.ts.map +1 -1
  231. package/dist/tools/unit-members.js +6 -6
  232. package/dist/tools/variants/generate-variants.d.ts +6 -6
  233. package/dist/tools/variants/generate-variants.d.ts.map +1 -1
  234. package/dist/tools/variants/generate-variants.js +6 -6
  235. package/dist/tools/variants/index.d.ts +1 -1
  236. package/dist/tools/variants/index.d.ts.map +1 -1
  237. package/dist/tools/variants/mutations.js +1 -1
  238. package/dist/tools/view-scene.d.ts +44 -0
  239. package/dist/tools/view-scene.d.ts.map +1 -0
  240. package/dist/tools/view-scene.js +75 -0
  241. package/dist/tools/vision/analyze-floorplan-image.d.ts +1 -1
  242. package/dist/tools/vision/analyze-floorplan-image.d.ts.map +1 -1
  243. package/dist/tools/vision/analyze-floorplan-image.js +7 -7
  244. package/dist/tools/vision/analyze-room-photo.d.ts +1 -1
  245. package/dist/tools/vision/analyze-room-photo.d.ts.map +1 -1
  246. package/dist/tools/vision/analyze-room-photo.js +7 -7
  247. package/dist/tools/vision/index.d.ts +1 -1
  248. package/dist/tools/vision/index.d.ts.map +1 -1
  249. package/dist/transports/http.d.ts +8 -2
  250. package/dist/transports/http.d.ts.map +1 -1
  251. package/dist/transports/http.js +9 -3
  252. package/dist/transports/stdio.d.ts +15 -9
  253. package/dist/transports/stdio.d.ts.map +1 -1
  254. package/dist/transports/stdio.js +18 -11
  255. package/dist/version.d.ts +5 -0
  256. package/dist/version.d.ts.map +1 -1
  257. package/dist/version.js +17 -3
  258. package/examples/coordinate-conventions-demo.md +12 -13
  259. package/examples/embed-in-agent.ts +6 -7
  260. package/examples/generate-apartment.md +13 -15
  261. package/examples/photo-to-scene.md +3 -4
  262. package/examples/renovate-from-photos.md +6 -9
  263. package/package.json +19 -9
  264. package/dist/tools/create-level.d.ts +0 -21
  265. package/dist/tools/create-level.d.ts.map +0 -1
  266. package/dist/tools/create-level.js +0 -63
  267. package/dist/tools/create-wall.d.ts +0 -22
  268. package/dist/tools/create-wall.d.ts.map +0 -1
  269. package/dist/tools/create-wall.js +0 -57
  270. package/dist/tools/cut-opening.d.ts +0 -25
  271. package/dist/tools/cut-opening.d.ts.map +0 -1
  272. package/dist/tools/cut-opening.js +0 -61
  273. package/dist/tools/delete-node.d.ts +0 -19
  274. package/dist/tools/delete-node.d.ts.map +0 -1
  275. package/dist/tools/delete-node.js +0 -40
  276. package/dist/tools/describe-node.d.ts +0 -17
  277. package/dist/tools/describe-node.d.ts.map +0 -1
  278. package/dist/tools/describe-node.js +0 -97
  279. package/dist/tools/door-clearance.d.ts +0 -135
  280. package/dist/tools/door-clearance.d.ts.map +0 -1
  281. package/dist/tools/door-clearance.js +0 -334
  282. package/dist/tools/duplicate-level.d.ts +0 -19
  283. package/dist/tools/duplicate-level.d.ts.map +0 -1
  284. package/dist/tools/duplicate-level.js +0 -60
  285. package/dist/tools/find-nodes.d.ts +0 -32
  286. package/dist/tools/find-nodes.d.ts.map +0 -1
  287. package/dist/tools/find-nodes.js +0 -107
  288. package/dist/tools/geometry.d.ts +0 -24
  289. package/dist/tools/geometry.d.ts.map +0 -1
  290. package/dist/tools/geometry.js +0 -94
  291. package/dist/tools/get-node.d.ts +0 -11
  292. package/dist/tools/get-node.d.ts.map +0 -1
  293. package/dist/tools/get-node.js +0 -29
  294. package/dist/tools/layout-clearance.d.ts +0 -105
  295. package/dist/tools/layout-clearance.d.ts.map +0 -1
  296. package/dist/tools/layout-clearance.js +0 -190
  297. package/dist/tools/place-item.d.ts +0 -22
  298. package/dist/tools/place-item.d.ts.map +0 -1
  299. package/dist/tools/place-item.js +0 -89
  300. package/dist/tools/scene-query.d.ts +0 -62
  301. package/dist/tools/scene-query.d.ts.map +0 -1
  302. package/dist/tools/scene-query.js +0 -670
  303. package/dist/tools/validate-scene.d.ts +0 -19
  304. package/dist/tools/validate-scene.d.ts.map +0 -1
  305. package/dist/tools/validate-scene.js +0 -28
@@ -6,7 +6,8 @@ import { parseArgs } from 'node:util';
6
6
  import { SceneBridge } from '../bridge/scene-bridge.js';
7
7
  import { version } from '../index.js';
8
8
  import { createPascalMcpServer } from '../server.js';
9
- import { createSceneStore } from '../storage/index.js';
9
+ import { createSceneStore, resolveDefaultDatabasePath } from '../storage/index.js';
10
+ import { localGeometryScripts } from '../tools/local-geometry-scripts.js';
10
11
  import { connectHttp } from '../transports/http.js';
11
12
  import { connectStdio } from '../transports/stdio.js';
12
13
  const HELP = `pascal-mcp — MCP server for the Pascal editor
@@ -54,7 +55,12 @@ async function main() {
54
55
  bridge.loadJSON(readFileSync(values.scene, 'utf8'));
55
56
  else
56
57
  bridge.loadDefault();
57
- return createPascalMcpServer({ bridge, store });
58
+ bridge.clearHistory();
59
+ return createPascalMcpServer({
60
+ bridge,
61
+ store,
62
+ geometryScripts: localGeometryScripts(resolveDefaultDatabasePath()),
63
+ });
58
64
  };
59
65
  if (values.http) {
60
66
  const portNum = Number.parseInt(values.port ?? '3917', 10);
@@ -88,7 +94,7 @@ async function main() {
88
94
  }
89
95
  else {
90
96
  // --stdio is the default when no transport flag is passed.
91
- await connectStdio(createServer());
97
+ connectStdio(createServer);
92
98
  console.error('[pascal-mcp] stdio server running');
93
99
  }
94
100
  }
@@ -0,0 +1,18 @@
1
+ import { type SessionKeep, type SessionRecord } from '@pascal-app/core/agent-operations';
2
+ /**
3
+ * Where a host keeps what the agent's session should remember: the moments it named and what it
4
+ * made. Never scene data. A host with sessions that outlive the process keeps it beside them.
5
+ */
6
+ export type AgentSession = {
7
+ read(): SessionRecord;
8
+ keep(keep: SessionKeep): void;
9
+ };
10
+ /** The standalone server's: in memory, for as long as the bridge holds a scene. */
11
+ export declare class InMemoryAgentSession implements AgentSession {
12
+ #private;
13
+ read(): SessionRecord;
14
+ keep(keep: SessionKeep): void;
15
+ /** Binding another scene starts another session. */
16
+ replace(record?: SessionRecord): void;
17
+ }
18
+ //# sourceMappingURL=agent-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-session.d.ts","sourceRoot":"","sources":["../../src/bridge/agent-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,aAAa,EACnB,MAAM,mCAAmC,CAAA;AAE1C;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,IAAI,aAAa,CAAA;IACrB,IAAI,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAAA;CAC9B,CAAA;AAED,mFAAmF;AACnF,qBAAa,oBAAqB,YAAW,YAAY;;IAGvD,IAAI,IAAI,aAAa,CAEpB;IAED,IAAI,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAE5B;IAED,oDAAoD;IACpD,OAAO,CAAC,MAAM,GAAE,aAA6B,GAAG,IAAI,CAEnD;CACF"}
@@ -0,0 +1,15 @@
1
+ import { EMPTY_SESSION, keepInSession, } from '@pascal-app/core/agent-operations';
2
+ /** The standalone server's: in memory, for as long as the bridge holds a scene. */
3
+ export class InMemoryAgentSession {
4
+ #record = EMPTY_SESSION;
5
+ read() {
6
+ return this.#record;
7
+ }
8
+ keep(keep) {
9
+ this.#record = keepInSession(this.#record, keep);
10
+ }
11
+ /** Binding another scene starts another session. */
12
+ replace(record = EMPTY_SESSION) {
13
+ this.#record = record;
14
+ }
15
+ }
@@ -1,3 +1,3 @@
1
- export type { ActiveSceneMeta, CreatePatch, DeletePatch, Patch, UpdatePatch, ValidationError, ValidationResult, } from './scene-bridge.js';
1
+ export type { ActiveSceneMeta, CreatePatch, DeletePatch, MaterialUpsertPatch, Patch, UpdatePatch, ValidationError, ValidationResult, } from './scene-bridge.js';
2
2
  export { SceneBridge } from './scene-bridge.js';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bridge/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,WAAW,EACX,WAAW,EACX,KAAK,EACL,WAAW,EACX,eAAe,EACf,gBAAgB,GACjB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bridge/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,WAAW,EACX,WAAW,EACX,mBAAmB,EACnB,KAAK,EACL,WAAW,EACX,eAAe,EACf,gBAAgB,GACjB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA"}
@@ -1,9 +1,11 @@
1
1
  import './node-shims.js';
2
+ import { type NodeDeletionPlan, type NodeDeletionScene } from '@pascal-app/core';
2
3
  import type { SceneGraph } from '@pascal-app/core/clone-scene-graph';
3
- import type { AnyNode } from '@pascal-app/core/schema';
4
- import { type AnyNodeId, type AnyNodeType } from '@pascal-app/core/schema';
4
+ import type { AnyNode, Collection, CollectionId, SceneMaterialId } from '@pascal-app/core/schema';
5
+ import { type AnyNodeId, type AnyNodeType, SceneMaterial } from '@pascal-app/core/schema';
5
6
  import useScene from '@pascal-app/core/store';
6
7
  import type { SceneMeta } from '../storage/types.js';
8
+ import { InMemoryAgentSession } from './agent-session.js';
7
9
  export type ValidationError = {
8
10
  nodeId: string;
9
11
  path: string;
@@ -30,8 +32,13 @@ export type DeletePatch = {
30
32
  id: AnyNodeId;
31
33
  cascade?: boolean;
32
34
  };
33
- export type Patch = CreatePatch | UpdatePatch | DeletePatch;
34
- export type ActiveSceneMeta = Pick<SceneMeta, 'id' | 'name' | 'projectId' | 'ownerId' | 'thumbnailUrl' | 'version'>;
35
+ /** Adds or replaces a scene material, written before the nodes of the same patch. */
36
+ export type MaterialUpsertPatch = {
37
+ op: 'upsert_material';
38
+ material: SceneMaterial;
39
+ };
40
+ export type Patch = CreatePatch | UpdatePatch | DeletePatch | MaterialUpsertPatch;
41
+ export type ActiveSceneMeta = Pick<SceneMeta, 'id' | 'name' | 'projectId' | 'ownerId' | 'thumbnailUrl' | 'version' | 'graphHash'>;
35
42
  /** The `extra` bag `setScene` accepts — collections, materials, plugin state. */
36
43
  type SetSceneExtra = Parameters<ReturnType<typeof useScene.getState>['setScene']>[2];
37
44
  /**
@@ -42,7 +49,11 @@ type SetSceneExtra = Parameters<ReturnType<typeof useScene.getState>['setScene']
42
49
  * `flushDirty()` for observability.
43
50
  */
44
51
  export declare class SceneBridge {
52
+ /** `applyPatch` takes `upsert_material` patches, in the patch's one history step. */
53
+ readonly supportsMaterialUpserts = true;
45
54
  private activeScene;
55
+ /** What the agent's session has kept: its named moments, and what it made. */
56
+ readonly agentSession: InMemoryAgentSession;
46
57
  /**
47
58
  * Scene identity currently bound to this bridge. MCP tools use this to know
48
59
  * which editor scene should receive live events after mutations.
@@ -67,6 +78,9 @@ export declare class SceneBridge {
67
78
  getNode(id: AnyNodeId): AnyNode | null;
68
79
  /** All nodes (live reference into the store — do NOT mutate). */
69
80
  getNodes(): Record<AnyNodeId, AnyNode>;
81
+ getCollections(): Record<CollectionId, Collection>;
82
+ getMaterials(): Record<SceneMaterialId, SceneMaterial>;
83
+ setCollections(collections: Record<CollectionId, Collection>): void;
70
84
  /** Root node IDs. */
71
85
  getRootNodeIds(): AnyNodeId[];
72
86
  /**
@@ -118,14 +132,34 @@ export declare class SceneBridge {
118
132
  deleteNode(id: AnyNodeId, cascade?: boolean): string[];
119
133
  /**
120
134
  * Atomic multi-op patch. Validates EVERY patch first (dry run); only if all
121
- * pass does it apply in a single batch via `createNodes` / `updateNodes` /
122
- * `deleteNodes`. Throws on any validation failure without mutating state.
135
+ * pass does it apply through the store actions in one history step. Throws on
136
+ * validation failure without mutating state.
123
137
  */
124
138
  applyPatch(patches: Patch[]): {
125
139
  appliedOps: number;
126
140
  deletedIds: AnyNodeId[];
127
141
  createdIds: AnyNodeId[];
128
142
  };
143
+ /**
144
+ * Preview what deleting `ids` does to `scene` with this bridge's semantics
145
+ * (core's store planner), without committing or minting default gutters.
146
+ * The apply_patch guard runs its dry run through this.
147
+ */
148
+ planDeletion(scene: NodeDeletionScene, ids: AnyNodeId[]): NodeDeletionPlan;
149
+ /**
150
+ * Derive this scene's construction the way the browser's commit subscriber
151
+ * does: one pass of the shared kernel writes the room zones, the auto
152
+ * ceilings, the wall side classification and the floor plates.
153
+ *
154
+ * The headless bridge has no space-detection subscriber, so the semantic
155
+ * tools that state room intent call this to materialise the construction
156
+ * they report. Writes are history-paused, so undo returns to the state
157
+ * before the triggering tool call rather than stranding derived nodes.
158
+ */
159
+ deriveStructure(levelIds?: AnyNodeId[]): {
160
+ createdIds: AnyNodeId[];
161
+ deletedIds: AnyNodeId[];
162
+ };
129
163
  /** Undo. Returns the number of steps actually undone. */
130
164
  undo(steps?: number): number;
131
165
  /** Redo. Returns the number of steps actually redone. */
@@ -147,6 +181,8 @@ export declare class SceneBridge {
147
181
  };
148
182
  /** Clear the temporal undo/redo history. */
149
183
  clearHistory(): void;
184
+ runAsSingleHistoryStep<T>(run: () => T): T;
185
+ private reconcileDependentStructure;
150
186
  /**
151
187
  * Return the node whose `children` array (string or object form) contains
152
188
  * the given id, or null if none. Used as a fallback when `parentId` is
@@ -1 +1 @@
1
- {"version":3,"file":"scene-bridge.d.ts","sourceRoot":"","sources":["../../src/bridge/scene-bridge.ts"],"names":[],"mappings":"AACA,OAAO,cAAc,CAAA;AAErB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAA;AACpE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAA;AACtD,OAAO,EACL,KAAK,SAAS,EAEd,KAAK,WAAW,EAEjB,MAAM,yBAAyB,CAAA;AAEhC,OAAO,QAAQ,MAAM,wBAAwB,CAAA;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD,MAAM,MAAM,eAAe,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAC/E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,eAAe,EAAE,CAAA;IACzB,qEAAqE;IACrE,QAAQ,EAAE,eAAe,EAAE,CAAA;CAC5B,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,CAAA;AAC/E,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAA;AACjF,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,SAAS,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAC5E,MAAM,MAAM,KAAK,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAA;AAC3D,MAAM,MAAM,eAAe,GAAG,IAAI,CAChC,SAAS,EACT,IAAI,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,cAAc,GAAG,SAAS,CACrE,CAAA;AAED,iFAAiF;AACjF,KAAK,aAAa,GAAG,UAAU,CAAC,UAAU,CAAC,OAAO,QAAQ,CAAC,QAAQ,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAEpF;;;;;;GAMG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,WAAW,CAA+B;IAElD;;;OAGG;IACH,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI;IAW3C,cAAc,IAAI,eAAe,GAAG,IAAI;IAIxC,gBAAgB,IAAI,IAAI;IAIxB,6EAA6E;IAC7E,WAAW,IAAI,IAAI;IAInB,iDAAiD;IACjD,QAAQ,CACN,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EACjC,WAAW,EAAE,SAAS,EAAE,EACxB,KAAK,CAAC,EAAE,aAAa,GACpB,IAAI;IAIP,gFAAgF;IAChF,UAAU,IAAI,UAAU,GAAG;QAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE;IAgBnE;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAyDzC,oDAAoD;IACpD,OAAO,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,GAAG,IAAI;IAKtC,iEAAiE;IACjE,QAAQ,IAAI,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC;IAItC,qBAAqB;IACrB,cAAc,IAAI,SAAS,EAAE;IAI7B;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,QAAQ,EAAE,SAAS,GAAG,OAAO,EAAE;IAuC3C;;;;;OAKG;IACH,WAAW,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,EAAE;IAqBrC,qEAAqE;IACrE,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;IAiBb,4EAA4E;IAC5E,cAAc,CAAC,EAAE,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI;IAQ/C;;;;OAIG;IACH,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS;IAK1D,iEAAiE;IACjE,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI;IAOvD;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,UAAQ,GAAG,MAAM,EAAE;IAwBpD;;;;OAIG;IACH,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;IAkHD,yDAAyD;IACzD,IAAI,CAAC,KAAK,SAAI,GAAG,MAAM;IAOvB,yDAAyD;IACzD,IAAI,CAAC,KAAK,SAAI,GAAG,MAAM;IAOvB;;;OAGG;IACH,aAAa,IAAI,gBAAgB;IAqBjC;;;OAGG;IACH,UAAU,IAAI,MAAM,EAAE;IAStB,yCAAyC;IACzC,UAAU,IAAI;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE;IAQxD,4CAA4C;IAC5C,YAAY,IAAI,IAAI;IAMpB;;;;OAIG;IACH,OAAO,CAAC,yBAAyB;IAqBjC;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;CAkB5B"}
1
+ {"version":3,"file":"scene-bridge.d.ts","sourceRoot":"","sources":["../../src/bridge/scene-bridge.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,10 @@
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
+ import { InMemoryAgentSession } from './agent-session.js';
7
8
  /**
8
9
  * Headless bridge to the `@pascal-app/core` Zustand store.
9
10
  *
@@ -12,7 +13,11 @@ import useScene from '@pascal-app/core/store';
12
13
  * `flushDirty()` for observability.
13
14
  */
14
15
  export class SceneBridge {
16
+ /** `applyPatch` takes `upsert_material` patches, in the patch's one history step. */
17
+ supportsMaterialUpserts = true;
15
18
  activeScene = null;
19
+ /** What the agent's session has kept: its named moments, and what it made. */
20
+ agentSession = new InMemoryAgentSession();
16
21
  /**
17
22
  * Scene identity currently bound to this bridge. MCP tools use this to know
18
23
  * which editor scene should receive live events after mutations.
@@ -25,6 +30,7 @@ export class SceneBridge {
25
30
  ownerId: meta.ownerId,
26
31
  thumbnailUrl: meta.thumbnailUrl,
27
32
  version: meta.version,
33
+ ...(meta.graphHash === undefined ? {} : { graphHash: meta.graphHash }),
28
34
  };
29
35
  }
30
36
  getActiveScene() {
@@ -35,10 +41,12 @@ export class SceneBridge {
35
41
  }
36
42
  /** Load initial state; if empty, creates default Site → Building → Level. */
37
43
  loadDefault() {
44
+ this.agentSession.replace();
38
45
  useScene.getState().loadScene();
39
46
  }
40
47
  /** Replace entire scene (undoable via Zundo). */
41
48
  setScene(nodes, rootNodeIds, extra) {
49
+ this.agentSession.replace();
42
50
  useScene.getState().setScene(nodes, rootNodeIds, extra);
43
51
  }
44
52
  /** Full snapshot for export, including collections and the material palette. */
@@ -119,6 +127,17 @@ export class SceneBridge {
119
127
  getNodes() {
120
128
  return useScene.getState().nodes;
121
129
  }
130
+ getCollections() {
131
+ return useScene.getState().collections;
132
+ }
133
+ getMaterials() {
134
+ return useScene.getState().materials;
135
+ }
136
+ setCollections(collections) {
137
+ if (useScene.getState().readOnly)
138
+ return;
139
+ useScene.setState({ collections });
140
+ }
122
141
  /** Root node IDs. */
123
142
  getRootNodeIds() {
124
143
  return useScene.getState().rootNodeIds;
@@ -237,7 +256,12 @@ export class SceneBridge {
237
256
  * Returns the generated id.
238
257
  */
239
258
  createNode(node, parentId) {
240
- useScene.getState().createNode(node, parentId);
259
+ const before = useScene.getState().nodes;
260
+ assertDerivedNodeWrites(before, { create: [{ node: parentId ? { ...node, parentId } : node }] });
261
+ runAsSingleSceneHistoryStep(useScene, () => {
262
+ useScene.getState().createNode(node, parentId);
263
+ this.reconcileDependentStructure(before);
264
+ });
241
265
  return node.id;
242
266
  }
243
267
  /** Update node fields (shallow merge through the core store). */
@@ -245,7 +269,12 @@ export class SceneBridge {
245
269
  if (!useScene.getState().nodes[id]) {
246
270
  throw new Error(`node not found: ${id}`);
247
271
  }
248
- useScene.getState().updateNode(id, data);
272
+ const before = useScene.getState().nodes;
273
+ assertDerivedNodeWrites(before, { update: [{ id, data }] });
274
+ runAsSingleSceneHistoryStep(useScene, () => {
275
+ useScene.getState().updateNode(id, data);
276
+ this.reconcileDependentStructure(before);
277
+ });
249
278
  }
250
279
  /**
251
280
  * Delete a node. If the node has children and `cascade === false`, throws.
@@ -265,7 +294,18 @@ export class SceneBridge {
265
294
  throw new Error(`node has ${descendants.length - 1} descendant(s); pass cascade: true to delete recursively`);
266
295
  }
267
296
  const before = new Set(Object.keys(state.nodes));
268
- useScene.getState().deleteNode(id);
297
+ const wallLevels = [
298
+ ...new Set(descendants.flatMap((nodeId) => {
299
+ const deleted = state.nodes[nodeId];
300
+ return deleted?.type === 'wall' && deleted.parentId ? [deleted.parentId] : [];
301
+ })),
302
+ ];
303
+ runAsSingleSceneHistoryStep(useScene, () => {
304
+ useScene.getState().deleteNode(id);
305
+ if (wallLevels.length)
306
+ this.deriveStructure(wallLevels);
307
+ this.reconcileDependentStructure(state.nodes);
308
+ });
269
309
  const afterNodes = useScene.getState().nodes;
270
310
  const removed = [];
271
311
  for (const prevId of before) {
@@ -276,12 +316,14 @@ export class SceneBridge {
276
316
  }
277
317
  /**
278
318
  * 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.
319
+ * pass does it apply through the store actions in one history step. Throws on
320
+ * validation failure without mutating state.
281
321
  */
282
322
  applyPatch(patches) {
283
323
  const state = useScene.getState();
284
324
  const nodes = state.nodes;
325
+ if (state.readOnly)
326
+ throw new Error('scene_read_only');
285
327
  // Track synthesized state as we dry-run so later ops can reference
286
328
  // earlier-created ids and reflect earlier-deleted ids.
287
329
  const simAvailable = new Set(Object.keys(nodes));
@@ -290,11 +332,18 @@ export class SceneBridge {
290
332
  // Zod-normalised copy (which has a generated id if the caller omitted one)
291
333
  // instead of the unparsed input.
292
334
  const parsedCreateNodes = new Map();
335
+ const materialUpserts = new Map();
293
336
  for (let i = 0; i < patches.length; i++) {
294
337
  const p = patches[i];
295
338
  if (!p)
296
339
  throw new Error(`invalid patch: patches[${i}] is undefined`);
297
- if (p.op === 'create') {
340
+ if (p.op === 'upsert_material') {
341
+ const parsed = SceneMaterial.safeParse(p.material);
342
+ if (!parsed.success)
343
+ throw new Error(`invalid patch: patches[${i}] material failed schema: ${parsed.error.message}`);
344
+ materialUpserts.set(parsed.data.id, parsed.data);
345
+ }
346
+ else if (p.op === 'create') {
298
347
  const res = parseNode(p.node);
299
348
  if (!res.success) {
300
349
  throw new Error(`invalid patch: patches[${i}] create node failed schema: ${res.error.message}`);
@@ -302,7 +351,7 @@ export class SceneBridge {
302
351
  if (p.parentId !== undefined && !simAvailable.has(p.parentId)) {
303
352
  throw new Error(`invalid patch: patches[${i}] create parentId "${p.parentId}" not found`);
304
353
  }
305
- parsedCreateNodes.set(i, res.data);
354
+ parsedCreateNodes.set(i, p.parentId ? { ...res.data, parentId: p.parentId } : res.data);
306
355
  simAvailable.add(res.data.id);
307
356
  }
308
357
  else if (p.op === 'update') {
@@ -334,47 +383,75 @@ export class SceneBridge {
334
383
  throw new Error(`invalid patch: patches[${i}] unknown op`);
335
384
  }
336
385
  }
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;
386
+ // Derived construction is refused BEFORE anything is applied, so a patch
387
+ // that tries to author a floor plate or an auto ceiling leaves the scene
388
+ // untouched like any other invalid patch.
389
+ assertDerivedNodeWrites(nodes, {
390
+ create: [...parsedCreateNodes.values()].map((node) => ({ node })),
391
+ update: patches.flatMap((patch) => patch.op === 'update' ? [{ id: patch.id, data: patch.data }] : []),
392
+ });
393
+ const createOps = new Map();
394
+ const updateOps = new Map();
395
+ const deleteIds = new Set();
396
+ const nextNodes = { ...nodes };
397
+ for (const [index, patch] of patches.entries()) {
398
+ if (patch.op === 'create') {
399
+ const parsed = parsedCreateNodes.get(index);
400
+ const node = { ...parsed, parentId: patch.parentId ?? parsed.parentId };
401
+ createOps.set(node.id, { node, parentId: patch.parentId });
402
+ deleteIds.delete(node.id);
403
+ nextNodes[node.id] = node;
350
404
  }
351
- if (kind !== 'update' && updateOps.length > 0) {
352
- useScene.getState().updateNodes(updateOps);
353
- updateOps.length = 0;
405
+ else if (patch.op === 'update') {
406
+ nextNodes[patch.id] = { ...nextNodes[patch.id], ...patch.data };
407
+ const created = createOps.get(patch.id);
408
+ if (created) {
409
+ created.node = nextNodes[patch.id];
410
+ if (patch.data.parentId !== undefined)
411
+ created.parentId = patch.data.parentId;
412
+ }
413
+ else
414
+ updateOps.set(patch.id, { ...updateOps.get(patch.id), ...patch.data });
354
415
  }
355
- if (kind !== 'delete' && deleteIds.length > 0) {
356
- useScene.getState().deleteNodes(deleteIds);
357
- deleteIds.length = 0;
416
+ else if (patch.op === 'delete') {
417
+ deleteIds.add(patch.id);
418
+ createOps.delete(patch.id);
419
+ updateOps.delete(patch.id);
420
+ delete nextNodes[patch.id];
358
421
  }
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 });
422
+ }
423
+ // Plan the entire deletion selection before applying anything. Interleaved
424
+ // update operations must not turn an all-boundaries deletion into balconies.
425
+ const planned = structureChangeBatch(planWallDeletion(nodes, {
426
+ nodeIds: [...deleteIds],
427
+ nextNodes,
428
+ mintId: generateId,
429
+ }).changes);
430
+ for (const { id, data } of planned.update)
431
+ updateOps.set(id, { ...updateOps.get(id), ...data });
432
+ const wallLevels = [
433
+ ...new Set(planned.delete.flatMap((id) => {
434
+ const node = nodes[id];
435
+ return node?.type === 'wall' && node.parentId ? [node.parentId] : [];
436
+ })),
437
+ ];
438
+ runAsSingleSceneHistoryStep(useScene, () => {
439
+ pauseSpaceDetection();
440
+ try {
441
+ for (const material of materialUpserts.values())
442
+ useScene.getState().addSceneMaterial(material);
443
+ useScene.getState().createNodes([...createOps.values(), ...planned.create]);
444
+ useScene.getState().updateNodes([...updateOps].map(([id, data]) => ({ id, data })));
445
+ useScene.getState().deleteNodes(planned.delete);
371
446
  }
372
- else {
373
- flush('delete');
374
- deleteIds.push(p.id);
447
+ finally {
448
+ resumeSpaceDetection();
375
449
  }
376
- }
377
- flush('none');
450
+ if (wallLevels.length)
451
+ this.deriveStructure(wallLevels);
452
+ this.reconcileDependentStructure(nodes);
453
+ });
454
+ const createdIds = Object.keys(useScene.getState().nodes).filter((id) => !nodes[id]);
378
455
  // Compute actual deleted ids by diffing pre/post snapshots.
379
456
  const postNodes = useScene.getState().nodes;
380
457
  const deletedIds = [];
@@ -388,6 +465,40 @@ export class SceneBridge {
388
465
  createdIds,
389
466
  };
390
467
  }
468
+ /**
469
+ * Preview what deleting `ids` does to `scene` with this bridge's semantics
470
+ * (core's store planner), without committing or minting default gutters.
471
+ * The apply_patch guard runs its dry run through this.
472
+ */
473
+ planDeletion(scene, ids) {
474
+ return planNodeDeletion(scene, ids, { mintDefaults: false });
475
+ }
476
+ /**
477
+ * Derive this scene's construction the way the browser's commit subscriber
478
+ * does: one pass of the shared kernel writes the room zones, the auto
479
+ * ceilings, the wall side classification and the floor plates.
480
+ *
481
+ * The headless bridge has no space-detection subscriber, so the semantic
482
+ * tools that state room intent call this to materialise the construction
483
+ * they report. Writes are history-paused, so undo returns to the state
484
+ * before the triggering tool call rather than stranding derived nodes.
485
+ */
486
+ deriveStructure(levelIds) {
487
+ pauseSceneHistory(useScene);
488
+ try {
489
+ const patches = applyStructureReconciliation(useScene, {
490
+ levelIds,
491
+ mintId: (kind) => generateId(kind),
492
+ });
493
+ return {
494
+ createdIds: patches.flatMap((patch) => patch.op === 'create' ? [patch.node.id] : []),
495
+ deletedIds: patches.flatMap((patch) => (patch.op === 'delete' ? [patch.id] : [])),
496
+ };
497
+ }
498
+ finally {
499
+ resumeSceneHistory(useScene);
500
+ }
501
+ }
391
502
  /** Undo. Returns the number of steps actually undone. */
392
503
  undo(steps = 1) {
393
504
  const before = useScene.temporal.getState().pastStates.length;
@@ -451,6 +562,82 @@ export class SceneBridge {
451
562
  clearHistory() {
452
563
  useScene.temporal.getState().clear();
453
564
  }
565
+ runAsSingleHistoryStep(run) {
566
+ return runAsSingleSceneHistoryStep(useScene, run);
567
+ }
568
+ reconcileDependentStructure(before) {
569
+ const currentNodes = this.getNodes();
570
+ const authoredInputChanged = [
571
+ ...new Set([...Object.keys(before), ...Object.keys(currentNodes)]),
572
+ ].some((id) => {
573
+ if (before[id] === currentNodes[id])
574
+ return false;
575
+ const type = (currentNodes[id] ?? before[id])?.type;
576
+ return (type === 'stair' ||
577
+ type === 'stair-segment' ||
578
+ type === 'elevator' ||
579
+ type === 'level' ||
580
+ type === 'building' ||
581
+ type === 'slab' ||
582
+ type === 'ceiling');
583
+ });
584
+ if (authoredInputChanged) {
585
+ const ownerIds = changedStairOpeningOwners(before, currentNodes);
586
+ for (const node of [...Object.values(before), ...Object.values(currentNodes)]) {
587
+ if (node.type === 'elevator' &&
588
+ (before[node.id] !== currentNodes[node.id] ||
589
+ Object.values(currentNodes).some((level) => level.type === 'level' && before[level.id] !== level)))
590
+ ownerIds.add(node.id);
591
+ }
592
+ const owned = planOwnedFloorOpenings(currentNodes, { ownerIds });
593
+ if (owned.length) {
594
+ pauseSpaceDetection();
595
+ try {
596
+ useScene.getState().applyNodeChanges(structureChangeBatch(owned));
597
+ }
598
+ finally {
599
+ resumeSpaceDetection();
600
+ }
601
+ }
602
+ }
603
+ const current = this.getNodes();
604
+ const levels = new Set();
605
+ for (const id of new Set([...Object.keys(before), ...Object.keys(current)])) {
606
+ if (before[id] === current[id])
607
+ continue;
608
+ for (const nodes of [before, current]) {
609
+ const level = stairDeckLevelId(nodes, nodes[id]);
610
+ if (level)
611
+ levels.add(level);
612
+ const node = nodes[id];
613
+ if (node?.type === 'level') {
614
+ levels.add(node.id);
615
+ const lower = adjacentLevelId(nodes, node.id, -1);
616
+ if (lower)
617
+ levels.add(lower);
618
+ }
619
+ if (node?.type === 'stair')
620
+ for (const opening of Object.values(nodes))
621
+ if (opening.type === 'floor-opening' && opening.ownerId === node.id)
622
+ for (const target of floorOpeningTargets(nodes, opening))
623
+ levels.add(target.levelId);
624
+ if (node?.type === 'floor-opening')
625
+ for (const target of floorOpeningTargets(nodes, node))
626
+ levels.add(target.levelId);
627
+ if (node?.type === 'door' || node?.type === 'window') {
628
+ const wall = nodes[node.parentId];
629
+ if (wall?.type === 'wall' && wall.parentId)
630
+ levels.add(wall.parentId);
631
+ }
632
+ else if ((node?.type === 'zone' || node?.type === 'slab' || node?.type === 'wall') &&
633
+ node.parentId) {
634
+ levels.add(node.parentId);
635
+ }
636
+ }
637
+ }
638
+ if (levels.size)
639
+ this.deriveStructure([...levels]);
640
+ }
454
641
  // ---- internal helpers ----
455
642
  /**
456
643
  * 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,3 +1,21 @@
1
+ import type { LookupAddress } from 'node:dns';
2
+ import { type IncomingMessage } from 'node:http';
3
+ import { type LookupFunction } from 'node:net';
4
+ /** True for any address a server-side fetch must not reach. Unparseable input is blocked. */
5
+ export declare function isBlockedAddress(address: string): boolean;
6
+ export type Resolver = (hostname: string) => Promise<LookupAddress[]>;
7
+ /** A socket `lookup` that only ever answers with the given, already-checked addresses. */
8
+ export declare function pinnedLookup(addresses: LookupAddress[]): LookupFunction;
9
+ export type PinnedRequestInit = {
10
+ headers: Record<string, string>;
11
+ lookup: LookupFunction;
12
+ signal: AbortSignal;
13
+ };
14
+ export type Requester = (url: URL, init: PinnedRequestInit) => Promise<IncomingMessage>;
15
+ export type SafeFetchDeps = {
16
+ resolve: Resolver;
17
+ request: Requester;
18
+ };
1
19
  export type SafeFetchOptions = {
2
20
  maxBytes?: number;
3
21
  timeoutMs?: number;
@@ -11,9 +29,9 @@ export type SafeFetchResult = {
11
29
  hops: string[];
12
30
  };
13
31
  /**
14
- * SSRF-safe fetch that follows redirects manually, revalidating the host
15
- * allowlist + private-IP check on every hop. Throws `McpError` for blocked
16
- * URLs, non-2xx responses, oversize bodies, or timeouts.
32
+ * SSRF-safe fetch that follows redirects manually, re-resolving and rechecking
33
+ * the host on every hop and connecting only to the checked addresses. Throws
34
+ * `ProtocolError` for blocked URLs, non-2xx responses, oversize bodies, or timeouts.
17
35
  */
18
- export declare function safeFetch(urlStr: string, opts?: SafeFetchOptions): Promise<SafeFetchResult>;
36
+ export declare function safeFetch(urlStr: string, opts?: SafeFetchOptions, deps?: SafeFetchDeps): Promise<SafeFetchResult>;
19
37
  //# sourceMappingURL=safe-fetch.d.ts.map