@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
@@ -0,0 +1,256 @@
1
+ import { nodeRegistry, planNodeDeletion, previewDefaultGutterRefresh, validateNodeRelations, } from '@pascal-app/core';
2
+ import { scriptedFieldRefusal, unknownMaterialPresetRefusal, } from '@pascal-app/core/agent-operations';
3
+ import { AnyNode, nodeKindOf, parseNode } from '@pascal-app/core/schema';
4
+ const guards = [];
5
+ /** Runs a guard on every apply_patch from now on; registering a guard again replaces it. */
6
+ export function registerPatchGuard(guard) {
7
+ const at = guards.findIndex((known) => known.name === guard.name);
8
+ if (at >= 0)
9
+ guards[at] = guard;
10
+ else
11
+ guards.push(guard);
12
+ guards.sort((a, b) => a.order - b.order);
13
+ }
14
+ /** Runs every guard in order; the notes are what the guards changed in the patch. */
15
+ export function runPatchGuards(patches, nodes) {
16
+ return guards.flatMap((guard) => {
17
+ const notes = guard.run(patches, nodes);
18
+ return Array.isArray(notes) ? notes : [];
19
+ });
20
+ }
21
+ /**
22
+ * A patch op refused because it would break node identity, the hierarchy or
23
+ * the node's schema. apply_patch returns it as a tool error whose text is
24
+ * JSON `{ code, patchIndex, id, message }`.
25
+ */
26
+ export class PatchRefusedError extends Error {
27
+ code;
28
+ patchIndex;
29
+ nodeId;
30
+ constructor(code, patchIndex, nodeId, detail) {
31
+ super(`${code}: patches[${patchIndex}] ${detail}`);
32
+ this.code = code;
33
+ this.patchIndex = patchIndex;
34
+ this.nodeId = nodeId;
35
+ this.name = 'PatchRefusedError';
36
+ }
37
+ }
38
+ /**
39
+ * Fields an update may restate but not change: `id` and `type` are the node's
40
+ * identity, `object` and `children` its place in the graph. `parentId` stays
41
+ * writable, checked below: the store reconciles both parents' `children`.
42
+ */
43
+ const IMMUTABLE_FIELDS = [
44
+ ['id', 'identity_change'],
45
+ ['type', 'identity_change'],
46
+ ['object', 'immutable_field'],
47
+ ['children', 'immutable_field'],
48
+ ];
49
+ const CORE_KINDS = new Set(AnyNode.options.map(nodeKindOf));
50
+ const sameValue = (a, b) => JSON.stringify(a) === JSON.stringify(b);
51
+ /**
52
+ * Schema issues of a node, keyed `path: message`, from its kind's schema: the
53
+ * registered definition's, or the core per-kind schema. `null` only for a kind
54
+ * with neither (a plugin kind this runtime has not registered), which is then
55
+ * not validated.
56
+ */
57
+ function schemaIssues(node) {
58
+ const type = node.type;
59
+ if (typeof type !== 'string')
60
+ return null;
61
+ const schema = nodeRegistry.get(type)?.schema;
62
+ if (!(schema || CORE_KINDS.has(type)))
63
+ return null;
64
+ const result = schema ? schema.safeParse(node) : parseNode(node);
65
+ const issues = result.success ? [] : (result.error?.issues ?? []);
66
+ return new Set(issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`));
67
+ }
68
+ function withChild(parent, childId) {
69
+ const children = parent.children;
70
+ if (!Array.isArray(children) || children.includes(childId))
71
+ return parent;
72
+ return { ...parent, children: [...children, childId] };
73
+ }
74
+ function withoutChild(parent, childId) {
75
+ const children = parent.children;
76
+ if (!Array.isArray(children))
77
+ return parent;
78
+ return { ...parent, children: children.filter((id) => id !== childId) };
79
+ }
80
+ /**
81
+ * Dry-runs a patch against the scene as each op leaves it and refuses:
82
+ * - a create whose id is already present (`node_exists`): a silent replace
83
+ * orphans the old node's children;
84
+ * - an update that changes `id`, `type`, `object` or `children`
85
+ * (`immutable_field`); restating the current value passes;
86
+ * - an update whose merged node has schema issues the node did not have
87
+ * before (`invalid_update`); kinds without a schema in this runtime pass;
88
+ * - an update writing a material preset the catalog does not know
89
+ * (`invalid_update`; a create is rejected like other invalid creates): it
90
+ * would render as the default finish;
91
+ * - an update of what a node's script owns, its `source` or the size it
92
+ * built (`scripted_field`): only a rebuild keeps them and the geometry in step;
93
+ * - any op on a node an earlier delete in the patch removed, or on a default
94
+ * gutter or downspout that delete regenerates, and any update of the roof
95
+ * segment holding them (`regenerated_default`).
96
+ *
97
+ * Deletes run through the bridge's own deletion preview (`planDeletion`; the
98
+ * local bridge uses core's store planner), one call per run of consecutive
99
+ * deletes as the bridge batches them, so cascades and wall merges are seen as
100
+ * that bridge applies them. A bridge without a preview gets a stricter rule:
101
+ * a create may not reuse any id present at the start of the patch.
102
+ * An update of a roof segment unsettles the default gutters its refresh may
103
+ * regenerate, as a delete does.
104
+ * Lives in the tool layer so every bridge behind `apply_patch` inherits it.
105
+ */
106
+ export function assertPatchKeepsIdentity(patches, nodes, rootNodeIds, planDeletion) {
107
+ const initialIds = new Set(Object.keys(nodes));
108
+ let scene = {
109
+ nodes: { ...nodes },
110
+ rootNodeIds: [...rootNodeIds],
111
+ collections: {},
112
+ };
113
+ const at = (id) => scene.nodes[id];
114
+ const put = (node) => {
115
+ scene.nodes[node.id] = node;
116
+ };
117
+ // Default gutters and downspouts a delete run refreshes: the store may keep,
118
+ // move or replace them with freshly minted ids, so later ops in the same
119
+ // patch cannot address them reliably.
120
+ const regenerated = new Map();
121
+ // Roof segments whose `children` that refresh rewrites: an update restating
122
+ // their pre-delete children would bring back obsolete ids.
123
+ const regeneratedHosts = new Map();
124
+ const refuseRegenerated = (index, id) => {
125
+ const deleteIndex = regenerated.get(id);
126
+ if (deleteIndex === undefined)
127
+ return;
128
+ throw new PatchRefusedError('regenerated_default', index, id, `"${id}" is a default gutter or downspout that the edit at patches[${deleteIndex}] regenerates. Address it in a separate apply_patch call.`);
129
+ };
130
+ // Updating a roof or roof segment makes the store refresh that roof's
131
+ // default gutters, with the same effect on ids as a delete's refresh.
132
+ const unsettleRoofRefresh = (node, index) => {
133
+ const roofId = node.type === 'roof' ? node.id : node.type === 'roof-segment' ? node.parentId : null;
134
+ if (!roofId)
135
+ return;
136
+ const preview = previewDefaultGutterRefresh(scene.nodes, [roofId]);
137
+ for (const id of preview.unsettledIds)
138
+ regenerated.set(id, index);
139
+ for (const id of preview.regeneratedHostIds)
140
+ regeneratedHosts.set(id, index);
141
+ };
142
+ let pendingDeletes = [];
143
+ const flushDeletes = (index) => {
144
+ const plan = planDeletion
145
+ ? planDeletion(scene, pendingDeletes)
146
+ : planNodeDeletion(scene, pendingDeletes, { mintDefaults: false });
147
+ for (const id of plan.unsettledIds)
148
+ regenerated.set(id, index);
149
+ for (const id of plan.regeneratedHostIds)
150
+ regeneratedHosts.set(id, index);
151
+ scene = { nodes: plan.nodes, rootNodeIds: plan.rootNodeIds, collections: plan.collections };
152
+ pendingDeletes = [];
153
+ };
154
+ patches.forEach((patch, index) => {
155
+ if (patch.op === 'create') {
156
+ // Parse like the store does, so defaults such as an empty `children`
157
+ // list exist for later ops in the patch; the bridge reports schema errors.
158
+ const parsed = parseNode(patch.node);
159
+ const node = (parsed.success ? parsed.data : patch.node);
160
+ if (typeof node?.id !== 'string')
161
+ return;
162
+ const preset = unknownMaterialPresetRefusal(node);
163
+ if (preset)
164
+ throw new Error(`invalid patch: patches[${index}] create "${node.id}": ${preset}`);
165
+ refuseRegenerated(index, node.id);
166
+ const effectiveParentId = patch.parentId ?? node.parentId;
167
+ if (effectiveParentId)
168
+ refuseRegenerated(index, effectiveParentId);
169
+ if (at(node.id) || (!planDeletion && initialIds.has(node.id))) {
170
+ throw new PatchRefusedError('node_exists', index, node.id, planDeletion || at(node.id)
171
+ ? `create id "${node.id}" already exists. Use op "update" to change it, or delete it earlier in the same patch to replace it.`
172
+ : `create id "${node.id}" existed when the patch started, and this bridge cannot preview its deletes. Delete it in one apply_patch call and create it in the next.`);
173
+ }
174
+ const parentId = patch.parentId ?? node.parentId ?? null;
175
+ if (patch.parentId !== undefined && !at(patch.parentId)) {
176
+ throw new Error(`invalid patch: patches[${index}] create parentId "${patch.parentId}" not found`);
177
+ }
178
+ put({ ...node, parentId });
179
+ const parent = parentId ? at(parentId) : undefined;
180
+ if (parent)
181
+ put(withChild(parent, node.id));
182
+ return;
183
+ }
184
+ if (patch.op === 'update') {
185
+ refuseRegenerated(index, patch.id);
186
+ const hostDeleteIndex = regeneratedHosts.get(patch.id);
187
+ if (hostDeleteIndex !== undefined) {
188
+ throw new PatchRefusedError('regenerated_default', index, patch.id, `the edit at patches[${hostDeleteIndex}] regenerates the default gutters and downspouts under "${patch.id}", so its children are not known yet. Update it in a separate apply_patch call.`);
189
+ }
190
+ if (typeof patch.data?.parentId === 'string')
191
+ refuseRegenerated(index, patch.data.parentId);
192
+ const current = at(patch.id);
193
+ if (!current)
194
+ throw new Error(`invalid patch: patches[${index}] update id "${patch.id}" not found`);
195
+ if (!patch.data || typeof patch.data !== 'object')
196
+ return;
197
+ const data = patch.data;
198
+ for (const [field, code] of IMMUTABLE_FIELDS) {
199
+ const before = current[field];
200
+ if (field in data && !sameValue(data[field], before)) {
201
+ throw new PatchRefusedError(code, index, patch.id, `update cannot change "${field}" of "${patch.id}" (${JSON.stringify(before)} → ${JSON.stringify(data[field])}). Create a new node, delete the old one, or reparent children through their own parentId.`);
202
+ }
203
+ }
204
+ const scripted = scriptedFieldRefusal(current, data);
205
+ if (scripted)
206
+ throw new PatchRefusedError('scripted_field', index, patch.id, scripted);
207
+ const preset = unknownMaterialPresetRefusal(data, current);
208
+ if (preset)
209
+ throw new PatchRefusedError('invalid_update', index, patch.id, preset);
210
+ const merged = { ...current, ...data };
211
+ const issuesAfter = schemaIssues(merged);
212
+ if (issuesAfter && issuesAfter.size > 0) {
213
+ const issuesBefore = schemaIssues(current) ?? new Set();
214
+ const added = [...issuesAfter].filter((issue) => !issuesBefore.has(issue));
215
+ if (added.length > 0) {
216
+ throw new PatchRefusedError('invalid_update', index, patch.id, `update of ${current.type} "${patch.id}" fails its schema: ${added.slice(0, 5).join('; ')}`);
217
+ }
218
+ }
219
+ if ('parentId' in data && data.parentId !== current.parentId) {
220
+ const newParent = typeof data.parentId === 'string' ? at(data.parentId) : undefined;
221
+ if (!(newParent && Array.isArray(newParent.children))) {
222
+ throw new PatchRefusedError('invalid_parent', index, patch.id, `update cannot move "${patch.id}" under ${JSON.stringify(data.parentId)}: ${newParent ? `a ${newParent.type} holds no children` : 'no such node'}.`);
223
+ }
224
+ const before = { ...scene.nodes };
225
+ const oldParent = current.parentId ? at(current.parentId) : undefined;
226
+ if (oldParent)
227
+ put(withoutChild(oldParent, patch.id));
228
+ put(withChild(newParent, patch.id));
229
+ put(merged);
230
+ unsettleRoofRefresh(merged, index);
231
+ try {
232
+ validateNodeRelations(before, scene.nodes, [patch.id]);
233
+ }
234
+ catch (err) {
235
+ throw new PatchRefusedError('invalid_parent', index, patch.id, `${newParent.type} "${newParent.id}" does not accept "${patch.id}": ${err instanceof Error ? err.message : String(err)}`);
236
+ }
237
+ return;
238
+ }
239
+ put(merged);
240
+ unsettleRoofRefresh(merged, index);
241
+ return;
242
+ }
243
+ if (patch.op === 'delete') {
244
+ refuseRegenerated(index, patch.id);
245
+ if (!at(patch.id) || pendingDeletes.includes(patch.id)) {
246
+ throw new Error(`invalid patch: patches[${index}] delete id "${patch.id}" not found`);
247
+ }
248
+ pendingDeletes.push(patch.id);
249
+ // The bridge applies a run of consecutive deletes as one deleteNodes
250
+ // call, and wall merges and kind cascades depend on the whole id list,
251
+ // so plan the run together once it ends.
252
+ if (patches[index + 1]?.op !== 'delete')
253
+ flushDeletes(index);
254
+ }
255
+ });
256
+ }
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import type { SceneOperations } from '../../operations/index.js';
3
3
  /**
4
4
  * Register the `photo_to_scene` orchestrator tool. Chains the vision
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/photo-to-scene/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAGvD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAEzF;AAED,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/photo-to-scene/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAGvD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAEzF;AAED,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,kBAAkB,CAAA"}
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { z } from 'zod';
3
3
  import type { SceneOperations } from '../../operations/index.js';
4
4
  /**
@@ -10,8 +10,8 @@ export declare const photoToSceneInput: {
10
10
  scaleHint: z.ZodOptional<z.ZodString>;
11
11
  name: z.ZodDefault<z.ZodString>;
12
12
  save: z.ZodDefault<z.ZodBoolean>;
13
- defaultWallThickness: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
14
- defaultWallHeight: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
13
+ defaultWallThickness: z.ZodDefault<z.ZodPipe<z.ZodUnion<[z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
14
+ defaultWallHeight: z.ZodDefault<z.ZodPipe<z.ZodUnion<[z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
15
15
  };
16
16
  export declare const photoToSceneOutput: {
17
17
  sceneId: z.ZodOptional<z.ZodString>;
@@ -1 +1 @@
1
- {"version":3,"file":"photo-to-scene.d.ts","sourceRoot":"","sources":["../../../src/tools/photo-to-scene/photo-to-scene.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAYxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAKvD;;;GAGG;AACH,eAAO,MAAM,iBAAiB;;;;;;;CAa7B,CAAA;AAED,eAAO,MAAM,kBAAkB;;;;;;;;CAQ9B,CAAA;AAmTD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CA8ErF"}
1
+ {"version":3,"file":"photo-to-scene.d.ts","sourceRoot":"","sources":["../../../src/tools/photo-to-scene/photo-to-scene.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAM7D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAKvD;;;GAGG;AACH,eAAO,MAAM,iBAAiB;IAC5B,KAAK;IACL,SAAS;IACT,IAAI;IACJ,IAAI;IACJ,oBAAoB;IAIpB,iBAAiB;CAIlB,CAAA;AAED,eAAO,MAAM,kBAAkB;IAC7B,OAAO;IACP,GAAG;IACH,KAAK;IACL,KAAK;IACL,UAAU;IACV,KAAK;IACL,KAAK;CACN,CAAA;AAmTD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CA8ErF"}
@@ -1,5 +1,6 @@
1
- import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
- import { AnyNode, BuildingNode, LevelNode, SiteNode, WallNode, ZoneNode, } from '@pascal-app/core/schema';
1
+ import { ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/server';
2
+ import { newZone } from '@pascal-app/core';
3
+ import { AnyNode, BuildingNode, LevelNode, SiteNode, WallNode } from '@pascal-app/core/schema';
3
4
  import { z } from 'zod';
4
5
  import { DESTRUCTIVE_OPEN_WORLD_TOOL_ANNOTATIONS } from '../annotations.js';
5
6
  import { appendLiveSceneEvent } from '../live-sync.js';
@@ -121,7 +122,7 @@ function extractText(content) {
121
122
  async function callVisionSampling(server, image, scaleHint) {
122
123
  const caps = server.server.getClientCapabilities();
123
124
  if (!caps?.sampling) {
124
- throw new McpError(ErrorCode.InvalidRequest, 'sampling_unavailable');
125
+ throw new ProtocolError(ProtocolErrorCode.InvalidRequest, 'sampling_unavailable');
125
126
  }
126
127
  const imageBlock = await resolveImageBlock(image);
127
128
  const instruction = scaleHint
@@ -140,7 +141,7 @@ async function callVisionSampling(server, image, scaleHint) {
140
141
  });
141
142
  const text = extractText(response.content);
142
143
  if (!text) {
143
- throw new McpError(ErrorCode.InternalError, 'sampling_response_unparseable', {
144
+ throw new ProtocolError(ProtocolErrorCode.InternalError, 'sampling_response_unparseable', {
144
145
  reason: 'no text content returned by host',
145
146
  });
146
147
  }
@@ -149,14 +150,14 @@ async function callVisionSampling(server, image, scaleHint) {
149
150
  parsed = JSON.parse(text);
150
151
  }
151
152
  catch (err) {
152
- throw new McpError(ErrorCode.InternalError, 'sampling_response_unparseable', {
153
+ throw new ProtocolError(ProtocolErrorCode.InternalError, 'sampling_response_unparseable', {
153
154
  raw: text,
154
155
  reason: err instanceof Error ? err.message : String(err),
155
156
  });
156
157
  }
157
158
  const validation = VisionResponseSchema.safeParse(parsed);
158
159
  if (!validation.success) {
159
- throw new McpError(ErrorCode.InternalError, 'sampling_response_invalid', {
160
+ throw new ProtocolError(ProtocolErrorCode.InternalError, 'sampling_response_invalid', {
160
161
  raw: text,
161
162
  errors: validation.error.issues,
162
163
  });
@@ -236,7 +237,7 @@ function buildSceneGraphFromVision(vision, defaultWallThickness, defaultWallHeig
236
237
  for (let i = 0; i < vision.rooms.length; i++) {
237
238
  const r = vision.rooms[i];
238
239
  try {
239
- const zone = ZoneNode.parse({
240
+ const zone = newZone({
240
241
  name: r.name,
241
242
  polygon: r.polygon,
242
243
  });
@@ -279,8 +280,8 @@ export function registerPhotoToScene(server, bridge) {
279
280
  server.registerTool('photo_to_scene', {
280
281
  title: 'Photo to Pascal scene',
281
282
  description: 'Orchestrator: analyse a floor-plan photo via MCP sampling, translate the structured vision result into a Pascal SceneGraph (site → building → level with walls and zones), optionally save it, and swap the bridge to the new scene. Requires host support for sampling.',
282
- inputSchema: photoToSceneInput,
283
- outputSchema: photoToSceneOutput,
283
+ inputSchema: z.object(photoToSceneInput),
284
+ outputSchema: z.object(photoToSceneOutput),
284
285
  annotations: DESTRUCTIVE_OPEN_WORLD_TOOL_ANNOTATIONS,
285
286
  }, async ({ image, scaleHint, name, save, defaultWallThickness, defaultWallHeight }) => {
286
287
  // 1. Vision.
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
+ import { type ProjectFileToolExecutor } from '@pascal-app/core/agent-operations';
3
+ export declare function registerProjectFileTools(server: McpServer, execute: ProjectFileToolExecutor): void;
4
+ //# sourceMappingURL=project-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-files.d.ts","sourceRoot":"","sources":["../../src/tools/project-files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,mCAAmC,CAAA;AAQ1C,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,uBAAuB,QAmC3F"}
@@ -0,0 +1,30 @@
1
+ import { executeProjectFileTool, } from '@pascal-app/core/agent-operations';
2
+ import { isAgentRefusal, PROJECT_FILE_TOOL_CONTRACTS, refusalPayload, } from '@pascal-app/core/agent-tools';
3
+ import { z } from 'zod';
4
+ export function registerProjectFileTools(server, execute) {
5
+ for (const contract of PROJECT_FILE_TOOL_CONTRACTS) {
6
+ server.registerTool(contract.name, {
7
+ title: contract.title,
8
+ description: contract.description,
9
+ inputSchema: z.object(contract.input).strict(),
10
+ annotations: contract.annotations,
11
+ }, async (input, ctx) => {
12
+ try {
13
+ const result = await executeProjectFileTool(contract.name, input, execute, ctx.mcpReq.signal);
14
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
15
+ }
16
+ catch (error) {
17
+ const result = isAgentRefusal(error)
18
+ ? refusalPayload(error)
19
+ : {
20
+ code: 'file_error',
21
+ error: 'Could not complete the file request. List project files before retrying an upload.',
22
+ };
23
+ return {
24
+ isError: true,
25
+ content: [{ type: 'text', text: JSON.stringify(result) }],
26
+ };
27
+ }
28
+ });
29
+ }
30
+ }
@@ -1,14 +1,16 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { z } from 'zod';
3
3
  import type { SceneOperations } from '../operations/index.js';
4
4
  export declare const redoInput: {
5
5
  steps: z.ZodOptional<z.ZodNumber>;
6
6
  };
7
7
  export declare const redoOutput: {
8
+ project: z.ZodOptional<z.ZodNullable<z.ZodString>>;
9
+ unsaved: z.ZodOptional<z.ZodString>;
8
10
  persistence: z.ZodOptional<z.ZodObject<{
9
11
  status: z.ZodEnum<{
10
- unbound: "unbound";
11
12
  events_unsupported: "events_unsupported";
13
+ unbound: "unbound";
12
14
  }>;
13
15
  warning: z.ZodString;
14
16
  }, z.core.$strip>>;
@@ -1 +1 @@
1
- {"version":3,"file":"redo.d.ts","sourceRoot":"","sources":["../../src/tools/redo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAIpD,eAAO,MAAM,SAAS;;CAErB,CAAA;AAED,eAAO,MAAM,UAAU;;;;;;;;;CAGtB,CAAA;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAsB7E"}
1
+ {"version":3,"file":"redo.d.ts","sourceRoot":"","sources":["../../src/tools/redo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AASpD,eAAO,MAAM,SAAS;IACpB,KAAK;CACN,CAAA;AAED,eAAO,MAAM,UAAU;;;;;;;;;;;CAGtB,CAAA;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAsB7E"}
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { DESTRUCTIVE_TOOL_ANNOTATIONS } from './annotations.js';
3
- import { liveSyncOutput, persistencePayload, publishLiveSceneSnapshot } from './live-sync.js';
3
+ import { currentLiveSync, liveSyncOutput, persistencePayload, publishLiveSceneSnapshot, } from './live-sync.js';
4
4
  export const redoInput = {
5
5
  steps: z.number().int().positive().optional(),
6
6
  };
@@ -12,12 +12,12 @@ export function registerRedo(server, bridge) {
12
12
  server.registerTool('redo', {
13
13
  title: 'Redo',
14
14
  description: 'Redo the next N previously-undone steps (default 1). Returns the number of steps actually redone.',
15
- inputSchema: redoInput,
16
- outputSchema: redoOutput,
15
+ inputSchema: z.object(redoInput),
16
+ outputSchema: z.object(redoOutput),
17
17
  annotations: DESTRUCTIVE_TOOL_ANNOTATIONS,
18
18
  }, async ({ steps }) => {
19
19
  const redone = bridge.redo(steps ?? 1);
20
- const persistence = redone > 0 ? await publishLiveSceneSnapshot(bridge, 'redo') : 'published';
20
+ const persistence = redone > 0 ? await publishLiveSceneSnapshot(bridge, 'redo') : currentLiveSync(bridge);
21
21
  const payload = { redone, ...persistencePayload(persistence) };
22
22
  return {
23
23
  content: [{ type: 'text', text: JSON.stringify(payload) }],
@@ -0,0 +1,10 @@
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
+ import { type RetiredTool } from '@pascal-app/core/agent-tools';
3
+ /**
4
+ * A retired tool (`RETIRED_MCP_TOOLS`, or a host's own map) is listed nowhere and still answered: a
5
+ * call to its name gets `tool_retired` with the tool that replaced it and how to use that one,
6
+ * where the SDK would answer an unknown name with a bare error. Install it once the tools are
7
+ * registered.
8
+ */
9
+ export declare function answerRetiredTools(server: McpServer, retired?: Readonly<Record<string, RetiredTool>>): void;
10
+ //# sourceMappingURL=retired-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retired-tools.d.ts","sourceRoot":"","sources":["../../src/tools/retired-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EAEL,KAAK,WAAW,EAEjB,MAAM,8BAA8B,CAAA;AAKrC;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,OAAO,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAqB,GACjE,IAAI,CAYN"}
@@ -0,0 +1,22 @@
1
+ import { RETIRED_MCP_TOOLS, retiredToolRefusal, } from '@pascal-app/core/agent-tools';
2
+ import { toolError } from './errors.js';
3
+ /**
4
+ * A retired tool (`RETIRED_MCP_TOOLS`, or a host's own map) is listed nowhere and still answered: a
5
+ * call to its name gets `tool_retired` with the tool that replaced it and how to use that one,
6
+ * where the SDK would answer an unknown name with a bare error. Install it once the tools are
7
+ * registered.
8
+ */
9
+ export function answerRetiredTools(server, retired = RETIRED_MCP_TOOLS) {
10
+ const registry = server.server;
11
+ const original = registry._requestHandlers.get('tools/call');
12
+ if (!original)
13
+ return;
14
+ server.server.removeRequestHandler('tools/call');
15
+ server.server.setRequestHandler('tools/call', async (request, ctx) => {
16
+ const { name } = request.params;
17
+ if (!Object.hasOwn(retired, name))
18
+ return (await original(request, ctx));
19
+ const { error, ...answer } = retiredToolRefusal(name, retired[name]);
20
+ return toolError(error, answer);
21
+ });
22
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=roof-level-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roof-level-guard.d.ts","sourceRoot":"","sources":["../../src/tools/roof-level-guard.ts"],"names":[],"mappings":""}
@@ -0,0 +1,22 @@
1
+ import { seatWrittenRoofs } from '@pascal-app/core/agent-operations';
2
+ import { isAgentRefusal } from '@pascal-app/core/agent-tools';
3
+ import { PatchRefusedError, registerPatchGuard } from './patch-guards.js';
4
+ /**
5
+ * The roof-level rule: a roof the patch puts on its walls' storey is seated on the level above by
6
+ * ops appended to the patch, said in notes, so every refusal's patchIndex stays on the model's own
7
+ * op; storey content on a roof level, a walled level declared the roof level and a roof level
8
+ * moved under a storey are refused. After the level heights, before the identity check, which
9
+ * checks the appended ops too.
10
+ */
11
+ function roofLevels(patches, scene) {
12
+ try {
13
+ return seatWrittenRoofs(patches, scene);
14
+ }
15
+ catch (error) {
16
+ if (!isAgentRefusal(error))
17
+ throw error;
18
+ const { index, id } = error.details;
19
+ throw new PatchRefusedError(error.code, index, id, error.message);
20
+ }
21
+ }
22
+ registerPatchGuard({ name: 'roof-levels', order: 40, run: roofLevels });