@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.
- package/CHANGELOG.md +76 -0
- package/README.md +181 -56
- package/dist/bin/pascal-mcp.js +9 -3
- package/dist/bridge/agent-session.d.ts +18 -0
- package/dist/bridge/agent-session.d.ts.map +1 -0
- package/dist/bridge/agent-session.js +15 -0
- package/dist/bridge/index.d.ts +1 -1
- package/dist/bridge/index.d.ts.map +1 -1
- package/dist/bridge/scene-bridge.d.ts +42 -6
- package/dist/bridge/scene-bridge.d.ts.map +1 -1
- package/dist/bridge/scene-bridge.js +232 -45
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/lib/safe-fetch.d.ts +22 -4
- package/dist/lib/safe-fetch.d.ts.map +1 -1
- package/dist/lib/safe-fetch.js +233 -134
- package/dist/operations/scene-operations.d.ts +41 -3
- package/dist/operations/scene-operations.d.ts.map +1 -1
- package/dist/operations/scene-operations.js +42 -1
- package/dist/prompts/from-brief.d.ts +1 -1
- package/dist/prompts/from-brief.d.ts.map +1 -1
- package/dist/prompts/from-brief.js +3 -3
- package/dist/prompts/index.d.ts +1 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/iterate-on-feedback.d.ts +1 -1
- package/dist/prompts/iterate-on-feedback.d.ts.map +1 -1
- package/dist/prompts/iterate-on-feedback.js +3 -3
- package/dist/prompts/renovation-from-photos.d.ts +1 -1
- package/dist/prompts/renovation-from-photos.d.ts.map +1 -1
- package/dist/prompts/renovation-from-photos.js +2 -2
- package/dist/prompts/scene-guidance.d.ts.map +1 -1
- package/dist/prompts/scene-guidance.js +13 -6
- package/dist/resources/agent-guide.d.ts +1 -1
- package/dist/resources/agent-guide.d.ts.map +1 -1
- package/dist/resources/agent-guide.js +62 -33
- package/dist/resources/catalog-items.d.ts +5 -6
- package/dist/resources/catalog-items.d.ts.map +1 -1
- package/dist/resources/catalog-items.js +7 -9
- package/dist/resources/constraints.d.ts +1 -1
- package/dist/resources/constraints.d.ts.map +1 -1
- package/dist/resources/constraints.js +1 -1
- package/dist/resources/design-schema.d.ts +9 -0
- package/dist/resources/design-schema.d.ts.map +1 -0
- package/dist/resources/design-schema.js +21 -0
- package/dist/resources/index.d.ts +4 -2
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/index.js +5 -2
- package/dist/resources/scene-current.d.ts +1 -1
- package/dist/resources/scene-current.d.ts.map +1 -1
- package/dist/resources/scene-summary.d.ts +1 -1
- package/dist/resources/scene-summary.d.ts.map +1 -1
- package/dist/server.d.ts +25 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +71 -25
- package/dist/storage/disk-artifact-store.d.ts +9 -0
- package/dist/storage/disk-artifact-store.d.ts.map +1 -0
- package/dist/storage/disk-artifact-store.js +46 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +1 -0
- package/dist/storage/sqlite-scene-store.d.ts +3 -3
- package/dist/storage/sqlite-scene-store.d.ts.map +1 -1
- package/dist/storage/sqlite-scene-store.js +4 -3
- package/dist/storage/types.d.ts +36 -7
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js +12 -0
- package/dist/templates/empty-studio.d.ts +3 -3
- package/dist/templates/empty-studio.d.ts.map +1 -1
- package/dist/templates/garden-house.d.ts +3 -3
- package/dist/templates/garden-house.d.ts.map +1 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/two-bedroom.d.ts +3 -3
- package/dist/templates/two-bedroom.d.ts.map +1 -1
- package/dist/tools/add-column.d.ts +5 -0
- package/dist/tools/add-column.d.ts.map +1 -0
- package/dist/tools/add-column.js +59 -0
- package/dist/tools/add-object.d.ts +76 -0
- package/dist/tools/add-object.d.ts.map +1 -0
- package/dist/tools/add-object.js +187 -0
- package/dist/tools/annotations.d.ts.map +1 -1
- package/dist/tools/apply-patch.d.ts +9 -2
- package/dist/tools/apply-patch.d.ts.map +1 -1
- package/dist/tools/apply-patch.js +36 -5
- package/dist/tools/asset-catalog.d.ts +9 -5
- package/dist/tools/asset-catalog.d.ts.map +1 -1
- package/dist/tools/asset-catalog.js +33 -12
- package/dist/tools/author-scene.d.ts +10 -0
- package/dist/tools/author-scene.d.ts.map +1 -0
- package/dist/tools/author-scene.js +140 -0
- package/dist/tools/check-collisions.d.ts +7 -7
- package/dist/tools/check-collisions.d.ts.map +1 -1
- package/dist/tools/check-collisions.js +7 -8
- package/dist/tools/clear-scene.d.ts +9 -0
- package/dist/tools/clear-scene.d.ts.map +1 -0
- package/dist/tools/clear-scene.js +59 -0
- package/dist/tools/collections.d.ts +4 -0
- package/dist/tools/collections.d.ts.map +1 -0
- package/dist/tools/collections.js +53 -0
- package/dist/tools/construction-tools.d.ts +12 -88
- package/dist/tools/construction-tools.d.ts.map +1 -1
- package/dist/tools/construction-tools.js +122 -381
- package/dist/tools/create-unit.d.ts +5 -3
- package/dist/tools/create-unit.d.ts.map +1 -1
- package/dist/tools/create-unit.js +2 -2
- package/dist/tools/errors.d.ts +15 -4
- package/dist/tools/errors.d.ts.map +1 -1
- package/dist/tools/errors.js +14 -4
- package/dist/tools/export-glb.d.ts +1 -1
- package/dist/tools/export-glb.d.ts.map +1 -1
- package/dist/tools/export-glb.js +2 -2
- package/dist/tools/export-json.d.ts +1 -1
- package/dist/tools/export-json.d.ts.map +1 -1
- package/dist/tools/export-json.js +2 -2
- package/dist/tools/geometry-script-worker.d.ts +2 -0
- package/dist/tools/geometry-script-worker.d.ts.map +1 -0
- package/dist/tools/geometry-script-worker.js +21 -0
- package/dist/tools/get-scene.d.ts +2 -1
- package/dist/tools/get-scene.d.ts.map +1 -1
- package/dist/tools/get-scene.js +5 -3
- package/dist/tools/honest-patch-guard.d.ts +2 -0
- package/dist/tools/honest-patch-guard.d.ts.map +1 -0
- package/dist/tools/honest-patch-guard.js +32 -0
- package/dist/tools/hosted-services.d.ts +4 -0
- package/dist/tools/hosted-services.d.ts.map +1 -0
- package/dist/tools/hosted-services.js +40 -0
- package/dist/tools/index.d.ts +12 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +18 -32
- package/dist/tools/level-height-guard.d.ts +2 -0
- package/dist/tools/level-height-guard.d.ts.map +1 -0
- package/dist/tools/level-height-guard.js +11 -0
- package/dist/tools/list-units.d.ts +2 -2
- package/dist/tools/list-units.d.ts.map +1 -1
- package/dist/tools/list-units.js +2 -2
- package/dist/tools/live-sync.d.ts +25 -6
- package/dist/tools/live-sync.d.ts.map +1 -1
- package/dist/tools/live-sync.js +48 -27
- package/dist/tools/local-geometry-scripts.d.ts +8 -0
- package/dist/tools/local-geometry-scripts.d.ts.map +1 -0
- package/dist/tools/local-geometry-scripts.js +67 -0
- package/dist/tools/measure.d.ts +7 -1
- package/dist/tools/measure.d.ts.map +1 -1
- package/dist/tools/measure.js +38 -70
- package/dist/tools/measurement.d.ts +1 -36
- package/dist/tools/measurement.d.ts.map +1 -1
- package/dist/tools/measurement.js +2 -94
- package/dist/tools/node-world-point.d.ts +37 -0
- package/dist/tools/node-world-point.d.ts.map +1 -0
- package/dist/tools/node-world-point.js +457 -0
- package/dist/tools/normalize-schema-dialect.d.ts +1 -1
- package/dist/tools/normalize-schema-dialect.d.ts.map +1 -1
- package/dist/tools/normalize-schema-dialect.js +22 -3
- package/dist/tools/patch-guards.d.ts +60 -0
- package/dist/tools/patch-guards.d.ts.map +1 -0
- package/dist/tools/patch-guards.js +256 -0
- package/dist/tools/photo-to-scene/index.d.ts +1 -1
- package/dist/tools/photo-to-scene/index.d.ts.map +1 -1
- package/dist/tools/photo-to-scene/photo-to-scene.d.ts +3 -3
- package/dist/tools/photo-to-scene/photo-to-scene.d.ts.map +1 -1
- package/dist/tools/photo-to-scene/photo-to-scene.js +10 -9
- package/dist/tools/project-files.d.ts +4 -0
- package/dist/tools/project-files.d.ts.map +1 -0
- package/dist/tools/project-files.js +30 -0
- package/dist/tools/redo.d.ts +4 -2
- package/dist/tools/redo.d.ts.map +1 -1
- package/dist/tools/redo.js +4 -4
- package/dist/tools/retired-tools.d.ts +10 -0
- package/dist/tools/retired-tools.d.ts.map +1 -0
- package/dist/tools/retired-tools.js +22 -0
- package/dist/tools/roof-level-guard.d.ts +2 -0
- package/dist/tools/roof-level-guard.d.ts.map +1 -0
- package/dist/tools/roof-level-guard.js +22 -0
- package/dist/tools/room-tools.d.ts +36 -98
- package/dist/tools/room-tools.d.ts.map +1 -1
- package/dist/tools/room-tools.js +130 -565
- package/dist/tools/scene-lifecycle/create-project.d.ts +1 -1
- package/dist/tools/scene-lifecycle/create-project.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/create-project.js +31 -6
- package/dist/tools/scene-lifecycle/delete-scene.d.ts +3 -1
- package/dist/tools/scene-lifecycle/delete-scene.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/delete-scene.js +10 -9
- package/dist/tools/scene-lifecycle/get-project-status.d.ts +1 -1
- package/dist/tools/scene-lifecycle/get-project-status.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/get-project-status.js +6 -6
- package/dist/tools/scene-lifecycle/index.d.ts +1 -1
- package/dist/tools/scene-lifecycle/index.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/list-scenes.d.ts +1 -1
- package/dist/tools/scene-lifecycle/list-scenes.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/list-scenes.js +4 -4
- package/dist/tools/scene-lifecycle/load-scene.d.ts +2 -2
- package/dist/tools/scene-lifecycle/load-scene.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/load-scene.js +5 -5
- package/dist/tools/scene-lifecycle/metadata.d.ts +1 -1
- package/dist/tools/scene-lifecycle/rename-scene.d.ts +1 -1
- package/dist/tools/scene-lifecycle/rename-scene.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/rename-scene.js +6 -6
- package/dist/tools/scene-lifecycle/save-scene.d.ts +6 -3
- package/dist/tools/scene-lifecycle/save-scene.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/save-scene.js +124 -16
- package/dist/tools/scene-lifecycle/test-utils.d.ts +3 -3
- package/dist/tools/scene-lifecycle/test-utils.d.ts.map +1 -1
- package/dist/tools/scene-lifecycle/test-utils.js +2 -1
- package/dist/tools/set-unit-members.d.ts +4 -2
- package/dist/tools/set-unit-members.d.ts.map +1 -1
- package/dist/tools/set-unit-members.js +6 -6
- package/dist/tools/set-zone.d.ts +4 -2
- package/dist/tools/set-zone.d.ts.map +1 -1
- package/dist/tools/set-zone.js +30 -18
- package/dist/tools/shared-tools.d.ts +40 -0
- package/dist/tools/shared-tools.d.ts.map +1 -0
- package/dist/tools/shared-tools.js +574 -0
- package/dist/tools/structure-tools.d.ts +57 -0
- package/dist/tools/structure-tools.d.ts.map +1 -0
- package/dist/tools/structure-tools.js +57 -0
- package/dist/tools/templates/create-from-template.d.ts +2 -2
- package/dist/tools/templates/create-from-template.d.ts.map +1 -1
- package/dist/tools/templates/create-from-template.js +6 -6
- package/dist/tools/templates/create-house-from-brief.d.ts +1 -1
- package/dist/tools/templates/create-house-from-brief.d.ts.map +1 -1
- package/dist/tools/templates/create-house-from-brief.js +6 -6
- package/dist/tools/templates/index.d.ts +1 -1
- package/dist/tools/templates/index.d.ts.map +1 -1
- package/dist/tools/templates/list-templates.d.ts +1 -1
- package/dist/tools/templates/list-templates.d.ts.map +1 -1
- package/dist/tools/templates/list-templates.js +2 -2
- package/dist/tools/undo.d.ts +4 -2
- package/dist/tools/undo.d.ts.map +1 -1
- package/dist/tools/undo.js +4 -4
- package/dist/tools/unit-members.d.ts.map +1 -1
- package/dist/tools/unit-members.js +6 -6
- package/dist/tools/variants/generate-variants.d.ts +6 -6
- package/dist/tools/variants/generate-variants.d.ts.map +1 -1
- package/dist/tools/variants/generate-variants.js +6 -6
- package/dist/tools/variants/index.d.ts +1 -1
- package/dist/tools/variants/index.d.ts.map +1 -1
- package/dist/tools/variants/mutations.js +1 -1
- package/dist/tools/view-scene.d.ts +44 -0
- package/dist/tools/view-scene.d.ts.map +1 -0
- package/dist/tools/view-scene.js +75 -0
- package/dist/tools/vision/analyze-floorplan-image.d.ts +1 -1
- package/dist/tools/vision/analyze-floorplan-image.d.ts.map +1 -1
- package/dist/tools/vision/analyze-floorplan-image.js +7 -7
- package/dist/tools/vision/analyze-room-photo.d.ts +1 -1
- package/dist/tools/vision/analyze-room-photo.d.ts.map +1 -1
- package/dist/tools/vision/analyze-room-photo.js +7 -7
- package/dist/tools/vision/index.d.ts +1 -1
- package/dist/tools/vision/index.d.ts.map +1 -1
- package/dist/transports/http.d.ts +8 -2
- package/dist/transports/http.d.ts.map +1 -1
- package/dist/transports/http.js +9 -3
- package/dist/transports/stdio.d.ts +15 -9
- package/dist/transports/stdio.d.ts.map +1 -1
- package/dist/transports/stdio.js +18 -11
- package/dist/version.d.ts +5 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +17 -3
- package/examples/coordinate-conventions-demo.md +12 -13
- package/examples/embed-in-agent.ts +6 -7
- package/examples/generate-apartment.md +13 -15
- package/examples/photo-to-scene.md +3 -4
- package/examples/renovate-from-photos.md +6 -9
- package/package.json +19 -9
- package/dist/tools/create-level.d.ts +0 -21
- package/dist/tools/create-level.d.ts.map +0 -1
- package/dist/tools/create-level.js +0 -63
- package/dist/tools/create-wall.d.ts +0 -22
- package/dist/tools/create-wall.d.ts.map +0 -1
- package/dist/tools/create-wall.js +0 -57
- package/dist/tools/cut-opening.d.ts +0 -25
- package/dist/tools/cut-opening.d.ts.map +0 -1
- package/dist/tools/cut-opening.js +0 -61
- package/dist/tools/delete-node.d.ts +0 -19
- package/dist/tools/delete-node.d.ts.map +0 -1
- package/dist/tools/delete-node.js +0 -40
- package/dist/tools/describe-node.d.ts +0 -17
- package/dist/tools/describe-node.d.ts.map +0 -1
- package/dist/tools/describe-node.js +0 -97
- package/dist/tools/door-clearance.d.ts +0 -135
- package/dist/tools/door-clearance.d.ts.map +0 -1
- package/dist/tools/door-clearance.js +0 -334
- package/dist/tools/duplicate-level.d.ts +0 -19
- package/dist/tools/duplicate-level.d.ts.map +0 -1
- package/dist/tools/duplicate-level.js +0 -60
- package/dist/tools/find-nodes.d.ts +0 -32
- package/dist/tools/find-nodes.d.ts.map +0 -1
- package/dist/tools/find-nodes.js +0 -107
- package/dist/tools/geometry.d.ts +0 -24
- package/dist/tools/geometry.d.ts.map +0 -1
- package/dist/tools/geometry.js +0 -94
- package/dist/tools/get-node.d.ts +0 -11
- package/dist/tools/get-node.d.ts.map +0 -1
- package/dist/tools/get-node.js +0 -29
- package/dist/tools/layout-clearance.d.ts +0 -105
- package/dist/tools/layout-clearance.d.ts.map +0 -1
- package/dist/tools/layout-clearance.js +0 -190
- package/dist/tools/place-item.d.ts +0 -22
- package/dist/tools/place-item.d.ts.map +0 -1
- package/dist/tools/place-item.js +0 -89
- package/dist/tools/scene-query.d.ts +0 -62
- package/dist/tools/scene-query.d.ts.map +0 -1
- package/dist/tools/scene-query.js +0 -670
- package/dist/tools/validate-scene.d.ts +0 -19
- package/dist/tools/validate-scene.d.ts.map +0 -1
- package/dist/tools/validate-scene.js +0 -28
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { computeSegmentTransforms, getFenceCenterlineFrameAt, getLevelElevations, getWallCurveFrameAt, measurementCentroid, nodeRegistry, } from '@pascal-app/core';
|
|
2
|
+
import { composeFrames, frame, IDENTITY_FRAME, nodeLevelFrame, transformPoint, } from '@pascal-app/core/procedural-items';
|
|
3
|
+
import { AnyNode, getRoofSegmentSurfaceY, nodeKindOf, } from '@pascal-app/core/schema';
|
|
4
|
+
function isVec3(value) {
|
|
5
|
+
return (Array.isArray(value) &&
|
|
6
|
+
value.length >= 3 &&
|
|
7
|
+
value.slice(0, 3).every((v) => typeof v === 'number' && Number.isFinite(v)));
|
|
8
|
+
}
|
|
9
|
+
function boundsCentre(points) {
|
|
10
|
+
if (points.length === 0)
|
|
11
|
+
return null;
|
|
12
|
+
const min = [Infinity, Infinity, Infinity];
|
|
13
|
+
const max = [-Infinity, -Infinity, -Infinity];
|
|
14
|
+
for (const p of points) {
|
|
15
|
+
for (let i = 0; i < 3; i++) {
|
|
16
|
+
min[i] = Math.min(min[i], p[i]);
|
|
17
|
+
max[i] = Math.max(max[i], p[i]);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return [(min[0] + max[0]) / 2, (min[1] + max[1]) / 2, (min[2] + max[2]) / 2];
|
|
21
|
+
}
|
|
22
|
+
/** Area centroid of a plan polygon at height `y`; the vertex average when degenerate. */
|
|
23
|
+
function polygonCentre(polygon, y) {
|
|
24
|
+
if (!Array.isArray(polygon) || polygon.length === 0)
|
|
25
|
+
return null;
|
|
26
|
+
const points = polygon.map(([x, z]) => [x, y, z]);
|
|
27
|
+
const centroid = measurementCentroid(points);
|
|
28
|
+
if (centroid)
|
|
29
|
+
return centroid;
|
|
30
|
+
let cx = 0;
|
|
31
|
+
let cz = 0;
|
|
32
|
+
for (const [x, , z] of points) {
|
|
33
|
+
cx += x;
|
|
34
|
+
cz += z;
|
|
35
|
+
}
|
|
36
|
+
return [cx / points.length, y, cz / points.length];
|
|
37
|
+
}
|
|
38
|
+
function geometryContext(node, nodes) {
|
|
39
|
+
const lookup = (id) => nodes[id];
|
|
40
|
+
const childIds = (n) => 'children' in n && Array.isArray(n.children) ? n.children : [];
|
|
41
|
+
const parent = node.parentId ? (lookup(node.parentId) ?? null) : null;
|
|
42
|
+
return {
|
|
43
|
+
resolve: ((id) => lookup(id)),
|
|
44
|
+
parent,
|
|
45
|
+
children: childIds(node)
|
|
46
|
+
.map(lookup)
|
|
47
|
+
.filter((child) => child !== undefined),
|
|
48
|
+
siblings: parent
|
|
49
|
+
? childIds(parent)
|
|
50
|
+
.map(lookup)
|
|
51
|
+
.filter((s) => s !== undefined && s.type === node.type)
|
|
52
|
+
: [],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** The point at parameter `t` (default 0.5) along a feature, by arc length. */
|
|
56
|
+
function featurePoint(feature, reference) {
|
|
57
|
+
const t = typeof reference.parameters?.t === 'number' ? reference.parameters.t : 0.5;
|
|
58
|
+
const geometry = feature.geometry;
|
|
59
|
+
if (geometry.kind === 'point')
|
|
60
|
+
return geometry.point;
|
|
61
|
+
const points = geometry.kind === 'segment' ? [geometry.start, geometry.end] : geometry.points;
|
|
62
|
+
const closed = geometry.kind === 'polygon' || (geometry.kind === 'path' && geometry.closed === true);
|
|
63
|
+
if (points.length === 0)
|
|
64
|
+
return null;
|
|
65
|
+
const count = closed ? points.length : points.length - 1;
|
|
66
|
+
const lengths = Array.from({ length: Math.max(0, count) }, (_, i) => {
|
|
67
|
+
const a = points[i];
|
|
68
|
+
const b = points[(i + 1) % points.length];
|
|
69
|
+
return Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
|
|
70
|
+
});
|
|
71
|
+
const total = lengths.reduce((sum, length) => sum + length, 0);
|
|
72
|
+
if (total <= 1e-9)
|
|
73
|
+
return points[0];
|
|
74
|
+
let remaining = Math.max(0, Math.min(1, t)) * total;
|
|
75
|
+
for (let i = 0; i < count; i++) {
|
|
76
|
+
const length = lengths[i];
|
|
77
|
+
if (remaining <= length || i === count - 1) {
|
|
78
|
+
const a = points[i];
|
|
79
|
+
const b = points[(i + 1) % points.length];
|
|
80
|
+
const u = length <= 1e-9 ? 0 : remaining / length;
|
|
81
|
+
return [a[0] + (b[0] - a[0]) * u, a[1] + (b[1] - a[1]) * u, a[2] + (b[2] - a[2]) * u];
|
|
82
|
+
}
|
|
83
|
+
remaining -= length;
|
|
84
|
+
}
|
|
85
|
+
return points[points.length - 1];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* A measurement or dimension anchor as the viewer resolves it: a tuple is a
|
|
89
|
+
* free point; a feature anchor follows its referenced node through that kind's
|
|
90
|
+
* registered measurement contribution, and falls back to its stored point when
|
|
91
|
+
* the node or the contribution is missing (`fallback: true`).
|
|
92
|
+
*/
|
|
93
|
+
function resolveAnchor(anchor, nodes) {
|
|
94
|
+
if (isVec3(anchor))
|
|
95
|
+
return { point: anchor, fallback: false };
|
|
96
|
+
const feature = anchor;
|
|
97
|
+
if (!(feature?.reference && isVec3(feature.fallback)))
|
|
98
|
+
return null;
|
|
99
|
+
const reference = feature.reference;
|
|
100
|
+
const referenced = nodes[reference.nodeId];
|
|
101
|
+
const contribution = referenced ? nodeRegistry.get(referenced.type)?.measurement : undefined;
|
|
102
|
+
if (referenced && contribution) {
|
|
103
|
+
const context = geometryContext(referenced, nodes);
|
|
104
|
+
const resolved = contribution.resolve?.(referenced, context, reference) ??
|
|
105
|
+
contribution.features(referenced, context).find((c) => c.id === reference.featureId);
|
|
106
|
+
const point = resolved ? featurePoint(resolved, reference) : null;
|
|
107
|
+
if (point)
|
|
108
|
+
return { point, fallback: false };
|
|
109
|
+
}
|
|
110
|
+
return { point: feature.fallback, fallback: true };
|
|
111
|
+
}
|
|
112
|
+
/** The anchors a measurement or construction dimension draws from. */
|
|
113
|
+
function anchorSet(node) {
|
|
114
|
+
if (node.type === 'construction-dimension')
|
|
115
|
+
return { anchors: node.anchors, shape: 'points' };
|
|
116
|
+
if (node.type !== 'measurement')
|
|
117
|
+
return null;
|
|
118
|
+
const m = node.measurement;
|
|
119
|
+
if (m.kind === 'distance' || m.kind === 'angle')
|
|
120
|
+
return { anchors: m.points ?? [], shape: 'points' };
|
|
121
|
+
if (m.kind === 'volume' && isVec3(m.extrusion)) {
|
|
122
|
+
return { anchors: m.base ?? [], shape: 'volume', extrusion: m.extrusion };
|
|
123
|
+
}
|
|
124
|
+
return { anchors: m.base ?? [], shape: 'area' };
|
|
125
|
+
}
|
|
126
|
+
/** Centre of a measurement or dimension: its points' centre, a base's area centroid, a prism's centre. */
|
|
127
|
+
function anchorCentre(set, nodes) {
|
|
128
|
+
const points = set.anchors
|
|
129
|
+
.map((anchor) => resolveAnchor(anchor, nodes)?.point)
|
|
130
|
+
.filter((p) => p !== undefined);
|
|
131
|
+
if (set.shape === 'points')
|
|
132
|
+
return boundsCentre(points);
|
|
133
|
+
const base = measurementCentroid(points) ?? boundsCentre(points);
|
|
134
|
+
if (!base || set.shape === 'area' || !set.extrusion)
|
|
135
|
+
return base;
|
|
136
|
+
return [
|
|
137
|
+
base[0] + set.extrusion[0] / 2,
|
|
138
|
+
base[1] + set.extrusion[1] / 2,
|
|
139
|
+
base[2] + set.extrusion[2] / 2,
|
|
140
|
+
];
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Centre of the geometry a node stores in its own frame, before its transform:
|
|
144
|
+
* mesh vertices, a segment, a path, a polygon or measurement anchors.
|
|
145
|
+
*/
|
|
146
|
+
function ownGeometryCentre(node, nodes) {
|
|
147
|
+
const n = node;
|
|
148
|
+
if (node.type === 'block') {
|
|
149
|
+
return boundsCentre(node.topology.vertices.map((v) => v.position));
|
|
150
|
+
}
|
|
151
|
+
if (node.type === 'imported-mesh') {
|
|
152
|
+
const points = [];
|
|
153
|
+
for (const primitive of node.primitives) {
|
|
154
|
+
for (let i = 0; i + 2 < primitive.positions.length; i += 3) {
|
|
155
|
+
points.push([
|
|
156
|
+
primitive.positions[i],
|
|
157
|
+
primitive.positions[i + 1],
|
|
158
|
+
primitive.positions[i + 2],
|
|
159
|
+
]);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return boundsCentre(points);
|
|
163
|
+
}
|
|
164
|
+
if (node.type === 'slab')
|
|
165
|
+
return polygonCentre(node.polygon, node.elevation ?? 0);
|
|
166
|
+
if (node.type === 'site')
|
|
167
|
+
return polygonCentre(node.polygon?.points, 0);
|
|
168
|
+
if (Array.isArray(n.polygon))
|
|
169
|
+
return polygonCentre(n.polygon, 0);
|
|
170
|
+
if (Array.isArray(n.start) && Array.isArray(n.end)) {
|
|
171
|
+
const [x1, z1] = n.start;
|
|
172
|
+
const [x2, z2] = n.end;
|
|
173
|
+
return [(x1 + x2) / 2, 0, (z1 + z2) / 2];
|
|
174
|
+
}
|
|
175
|
+
if (Array.isArray(n.path))
|
|
176
|
+
return boundsCentre(n.path.filter(isVec3));
|
|
177
|
+
const anchors = anchorSet(node);
|
|
178
|
+
return anchors ? anchorCentre(anchors, nodes) : null;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Stair segments render at the attachment chain, not at their stored
|
|
182
|
+
* `position`, so resolve them from the stair frame like the stair system does.
|
|
183
|
+
*/
|
|
184
|
+
function stairSegmentFrame(node, nodes) {
|
|
185
|
+
const stair = node.parentId ? nodes[node.parentId] : undefined;
|
|
186
|
+
if (stair?.type !== 'stair')
|
|
187
|
+
return null;
|
|
188
|
+
const segments = (stair.children ?? [])
|
|
189
|
+
.map((id) => nodes[id])
|
|
190
|
+
.filter((n) => n?.type === 'stair-segment');
|
|
191
|
+
const index = segments.findIndex((segment) => segment.id === node.id);
|
|
192
|
+
const transform = computeSegmentTransforms(segments)[index];
|
|
193
|
+
if (!transform)
|
|
194
|
+
return null;
|
|
195
|
+
return composeFrames(nodeLevelFrame(stair.id, nodes), frame(transform.position, [0, transform.rotation, 0]));
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Built-in kinds the viewer lifts onto the slab under them through the
|
|
199
|
+
* `floorPlaced` capability of their registered definition, and whose footprint
|
|
200
|
+
* core cannot derive without it (core derives item, shelf and procedural-item
|
|
201
|
+
* footprints from the node itself). Headless MCP does not load
|
|
202
|
+
* `@pascal-app/nodes`, so their lift is unresolved and flagged, not guessed. A
|
|
203
|
+
* test keeps this list in step with the definitions.
|
|
204
|
+
*/
|
|
205
|
+
export const HEADLESS_UNRESOLVED_FLOOR_LIFT_KINDS = [
|
|
206
|
+
'block',
|
|
207
|
+
'cabinet',
|
|
208
|
+
'cabinet-module',
|
|
209
|
+
'column',
|
|
210
|
+
'duct-terminal',
|
|
211
|
+
'hvac-equipment',
|
|
212
|
+
'spawn',
|
|
213
|
+
'stair',
|
|
214
|
+
];
|
|
215
|
+
const UNRESOLVED_FLOOR_LIFT = new Set(HEADLESS_UNRESOLVED_FLOOR_LIFT_KINDS);
|
|
216
|
+
const CORE_KINDS = new Set(AnyNode.options.map(nodeKindOf));
|
|
217
|
+
const FLOOR_LIFT_UNRESOLVED = 'floor-lift-unresolved-headless: the viewer lifts this onto the slab or ground under it through a node definition this runtime has not registered; the point is on the level plane';
|
|
218
|
+
/**
|
|
219
|
+
* The node on a level that carries `node`'s floor lift (itself or the host it
|
|
220
|
+
* sits on), when that lift is resolved by a definition this runtime lacks: a
|
|
221
|
+
* built-in floor-placed kind or a plugin kind, unregistered.
|
|
222
|
+
*/
|
|
223
|
+
function unresolvedFloorLiftHost(node, nodes) {
|
|
224
|
+
const seen = new Set();
|
|
225
|
+
let current = node;
|
|
226
|
+
while (current && !seen.has(current.id)) {
|
|
227
|
+
seen.add(current.id);
|
|
228
|
+
const parent = current.parentId ? nodes[current.parentId] : undefined;
|
|
229
|
+
if (parent?.type === 'level') {
|
|
230
|
+
if (nodeRegistry.get(current.type))
|
|
231
|
+
return undefined;
|
|
232
|
+
const unresolved = UNRESOLVED_FLOOR_LIFT.has(current.type) ||
|
|
233
|
+
(!CORE_KINDS.has(current.type) && 'position' in current);
|
|
234
|
+
return unresolved ? current : undefined;
|
|
235
|
+
}
|
|
236
|
+
current = parent;
|
|
237
|
+
}
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Why a node's point is not where its renderer draws it: kinds whose renderer
|
|
242
|
+
* derives the pose from data this resolver does not model, and floor lifts
|
|
243
|
+
* owned by definitions not registered here. The point is then the stored
|
|
244
|
+
* placement in its host's frame.
|
|
245
|
+
*/
|
|
246
|
+
function approximationReason(node, nodes) {
|
|
247
|
+
if (unresolvedFloorLiftHost(node, nodes))
|
|
248
|
+
return FLOOR_LIFT_UNRESOLVED;
|
|
249
|
+
const anchors = anchorSet(node);
|
|
250
|
+
if (anchors?.anchors.some((anchor) => resolveAnchor(anchor, nodes)?.fallback)) {
|
|
251
|
+
return 'anchor-fallback: an anchor follows a referenced node whose measurement features are not registered here (or the node is gone); its stored fallback point is used';
|
|
252
|
+
}
|
|
253
|
+
const parent = node.parentId ? nodes[node.parentId] : undefined;
|
|
254
|
+
switch (node.type) {
|
|
255
|
+
case 'downspout':
|
|
256
|
+
return 'rendered at its gutter outlet below the eave; the point is its gutter or host placement';
|
|
257
|
+
case 'gutter':
|
|
258
|
+
return 'rendered at the roof segment eave height, not its stored height';
|
|
259
|
+
case 'ridge-vent':
|
|
260
|
+
return 'rendered along its roof segment ridge; the point is its stored placement';
|
|
261
|
+
case 'fence':
|
|
262
|
+
return 'floor-lift-unresolved-headless: the fence renderer lifts it onto its host slab or the ground; the point is on the level plane';
|
|
263
|
+
case 'solar-panel':
|
|
264
|
+
case 'skylight':
|
|
265
|
+
return parent?.type === 'roof-segment'
|
|
266
|
+
? 'roof-surface: the viewer seats it on the finished outer roof surface (deck and shingles); the point is on the structural roof surface'
|
|
267
|
+
: undefined;
|
|
268
|
+
case 'door':
|
|
269
|
+
case 'window':
|
|
270
|
+
if (node.roofSegmentId || (node.type === 'window' && node.dormerId)) {
|
|
271
|
+
return 'hosted on a roof-segment or dormer wall face; the point ignores the face frame';
|
|
272
|
+
}
|
|
273
|
+
return undefined;
|
|
274
|
+
case 'lean-to-extension':
|
|
275
|
+
return parent?.type === 'wall'
|
|
276
|
+
? 'placed along its host wall centreline; the point uses a straight wall frame'
|
|
277
|
+
: undefined;
|
|
278
|
+
case 'item':
|
|
279
|
+
return node.asset.attachTo === 'wall-side'
|
|
280
|
+
? 'rendered on the wall face (± half the wall thickness); the point is on the wall centreline'
|
|
281
|
+
: undefined;
|
|
282
|
+
default:
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* The node's reference point in its level's frame (or in world space when it
|
|
288
|
+
* has no level ancestor), or null for a container with no geometry of its own.
|
|
289
|
+
*/
|
|
290
|
+
function levelLocalPoint(node, nodes) {
|
|
291
|
+
if (node.type === 'wall') {
|
|
292
|
+
const { point } = getWallCurveFrameAt(node, 0.5);
|
|
293
|
+
return [point.x, nodeLevelFrame(node.id, nodes).position[1], point.y];
|
|
294
|
+
}
|
|
295
|
+
if (node.type === 'fence') {
|
|
296
|
+
// Arc or spline midpoint on the level plane; its lift is flagged.
|
|
297
|
+
const { point } = getFenceCenterlineFrameAt(node, 0.5);
|
|
298
|
+
return [point.x, 0, point.y];
|
|
299
|
+
}
|
|
300
|
+
if (node.type === 'stair-segment') {
|
|
301
|
+
const segmentFrame = stairSegmentFrame(node, nodes);
|
|
302
|
+
if (segmentFrame)
|
|
303
|
+
return segmentFrame.position;
|
|
304
|
+
}
|
|
305
|
+
const parent = node.parentId ? nodes[node.parentId] : undefined;
|
|
306
|
+
if (node.type === 'downspout' && node.gutterId && nodes[node.gutterId]?.type === 'gutter') {
|
|
307
|
+
return levelLocalPoint(nodes[node.gutterId], nodes);
|
|
308
|
+
}
|
|
309
|
+
if (node.type === 'chimney' && parent?.type === 'roof-segment') {
|
|
310
|
+
// The renderer ignores the stored Y and stands the chimney on the segment base.
|
|
311
|
+
const [x, , z] = node.position;
|
|
312
|
+
return transformPoint(nodeLevelFrame(parent.id, nodes), [x, 0, z]);
|
|
313
|
+
}
|
|
314
|
+
if ((node.type === 'solar-panel' || node.type === 'skylight') &&
|
|
315
|
+
parent?.type === 'roof-segment') {
|
|
316
|
+
// Both renderers ignore the stored Y and sit on the roof surface; the
|
|
317
|
+
// finished surface (deck and shingles) is the viewer's, so this is flagged.
|
|
318
|
+
const [x, , z] = node.position;
|
|
319
|
+
return transformPoint(nodeLevelFrame(parent.id, nodes), [
|
|
320
|
+
x,
|
|
321
|
+
getRoofSegmentSurfaceY(parent, x, z),
|
|
322
|
+
z,
|
|
323
|
+
]);
|
|
324
|
+
}
|
|
325
|
+
const own = nodeLevelFrame(node.id, nodes);
|
|
326
|
+
const geometry = ownGeometryCentre(node, nodes);
|
|
327
|
+
if (geometry)
|
|
328
|
+
return transformPoint(own, geometry);
|
|
329
|
+
if (isVec3(node.position))
|
|
330
|
+
return own.position;
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
function nearestLevelId(node, nodes) {
|
|
334
|
+
const seen = new Set();
|
|
335
|
+
let current = node;
|
|
336
|
+
while (current && !seen.has(current.id)) {
|
|
337
|
+
if (current.type === 'level')
|
|
338
|
+
return current.id;
|
|
339
|
+
seen.add(current.id);
|
|
340
|
+
current = current.parentId ? nodes[current.parentId] : undefined;
|
|
341
|
+
}
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
function hostOf(id, nodes) {
|
|
345
|
+
const node = nodes[id];
|
|
346
|
+
if (node?.parentId)
|
|
347
|
+
return nodes[node.parentId];
|
|
348
|
+
// The default scene links site → building → level through `children` only.
|
|
349
|
+
return Object.values(nodes).find((candidate) => 'children' in candidate &&
|
|
350
|
+
Array.isArray(candidate.children) &&
|
|
351
|
+
candidate.children.some((child) => child === id || child?.id === id));
|
|
352
|
+
}
|
|
353
|
+
/** A level's frame in world space: its building's transform, then its stacked base Y. */
|
|
354
|
+
function levelWorldFrame(levelId, nodes) {
|
|
355
|
+
const baseY = getLevelElevations(nodes).get(levelId)?.baseY ?? 0;
|
|
356
|
+
const host = hostOf(levelId, nodes);
|
|
357
|
+
const hostFrame = host ? nodeLevelFrame(host.id, nodes) : IDENTITY_FRAME;
|
|
358
|
+
return composeFrames(hostFrame, frame([0, baseY, 0]));
|
|
359
|
+
}
|
|
360
|
+
function childIdsOf(id, nodes) {
|
|
361
|
+
const node = nodes[id];
|
|
362
|
+
const ids = new Set();
|
|
363
|
+
if (node && 'children' in node && Array.isArray(node.children)) {
|
|
364
|
+
for (const child of node.children) {
|
|
365
|
+
const childId = typeof child === 'string' ? child : child?.id;
|
|
366
|
+
if (typeof childId === 'string')
|
|
367
|
+
ids.add(childId);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (node?.type === 'unit')
|
|
371
|
+
for (const member of node.members)
|
|
372
|
+
ids.add(member);
|
|
373
|
+
for (const candidate of Object.values(nodes)) {
|
|
374
|
+
if (candidate.parentId === id)
|
|
375
|
+
ids.add(candidate.id);
|
|
376
|
+
}
|
|
377
|
+
return [...ids].filter((childId) => nodes[childId]);
|
|
378
|
+
}
|
|
379
|
+
export function resolveNodeWorldPoint(id, nodes) {
|
|
380
|
+
const linked = withParentIds(nodes);
|
|
381
|
+
const point = worldPoint(id, linked, 0);
|
|
382
|
+
if (!point)
|
|
383
|
+
return null;
|
|
384
|
+
const approximate = approximationReason(linked[id], linked);
|
|
385
|
+
return approximate ? { point, approximate } : { point };
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* `nodes` with every missing `parentId` filled from the parent's `children`
|
|
389
|
+
* array. The frame walk follows `parentId`, but scenes may link a subtree only
|
|
390
|
+
* through `children` (the default site → building → level assembly does), as
|
|
391
|
+
* SceneBridge's ancestry allows. Returns `nodes` itself when nothing is missing.
|
|
392
|
+
*/
|
|
393
|
+
function withParentIds(nodes) {
|
|
394
|
+
const parentOf = new Map();
|
|
395
|
+
for (const node of Object.values(nodes)) {
|
|
396
|
+
if (!('children' in node) || !Array.isArray(node.children))
|
|
397
|
+
continue;
|
|
398
|
+
for (const child of node.children) {
|
|
399
|
+
const childId = typeof child === 'string' ? child : child?.id;
|
|
400
|
+
if (typeof childId === 'string' && !parentOf.has(childId))
|
|
401
|
+
parentOf.set(childId, node.id);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
let linked = null;
|
|
405
|
+
for (const node of Object.values(nodes)) {
|
|
406
|
+
const parentId = parentOf.get(node.id);
|
|
407
|
+
if (node.parentId || !parentId)
|
|
408
|
+
continue;
|
|
409
|
+
linked ??= { ...nodes };
|
|
410
|
+
linked[node.id] = { ...node, parentId };
|
|
411
|
+
}
|
|
412
|
+
return linked ?? nodes;
|
|
413
|
+
}
|
|
414
|
+
function worldPoint(id, nodes, depth) {
|
|
415
|
+
const node = nodes[id];
|
|
416
|
+
if (!node || depth > 32)
|
|
417
|
+
return null;
|
|
418
|
+
if (node.type === 'level') {
|
|
419
|
+
return transformPoint(levelWorldFrame(node.id, nodes), levelPlanCentre(node.id, nodes));
|
|
420
|
+
}
|
|
421
|
+
const levelId = nearestLevelId(node, nodes);
|
|
422
|
+
const toWorld = (point) => levelId ? transformPoint(levelWorldFrame(levelId, nodes), point) : point;
|
|
423
|
+
const local = levelLocalPoint(node, nodes);
|
|
424
|
+
if (local)
|
|
425
|
+
return toWorld(local);
|
|
426
|
+
const points = childIdsOf(id, nodes)
|
|
427
|
+
.map((childId) => worldPoint(childId, nodes, depth + 1))
|
|
428
|
+
.filter((p) => p !== null);
|
|
429
|
+
if (points.length > 0)
|
|
430
|
+
return boundsCentre(points);
|
|
431
|
+
return toWorld(nodeLevelFrame(node.id, nodes).position);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* A level's plan centre on its base plane: the centre of its direct children's
|
|
435
|
+
* level-local points. Read from their stored geometry and own transform only,
|
|
436
|
+
* because resolving every wall's slab support makes a large level take seconds.
|
|
437
|
+
*/
|
|
438
|
+
function levelPlanCentre(levelId, nodes) {
|
|
439
|
+
const points = [];
|
|
440
|
+
for (const childId of childIdsOf(levelId, nodes)) {
|
|
441
|
+
const child = nodes[childId];
|
|
442
|
+
const transform = child;
|
|
443
|
+
const position = isVec3(transform.position) ? transform.position : [0, 0, 0];
|
|
444
|
+
const rotation = typeof transform.rotation === 'number'
|
|
445
|
+
? [0, transform.rotation, 0]
|
|
446
|
+
: isVec3(transform.rotation)
|
|
447
|
+
? transform.rotation
|
|
448
|
+
: [0, 0, 0];
|
|
449
|
+
const geometry = ownGeometryCentre(child, nodes);
|
|
450
|
+
if (geometry)
|
|
451
|
+
points.push(transformPoint(frame(position, rotation), geometry));
|
|
452
|
+
else if (isVec3(transform.position))
|
|
453
|
+
points.push(position);
|
|
454
|
+
}
|
|
455
|
+
const centre = boundsCentre(points);
|
|
456
|
+
return centre ? [centre[0], 0, centre[2]] : [0, 0, 0];
|
|
457
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { McpServer } from '@modelcontextprotocol/
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/server';
|
|
2
2
|
export declare function normalizeToolSchemaDialect(server: McpServer): void;
|
|
3
3
|
//# sourceMappingURL=normalize-schema-dialect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize-schema-dialect.d.ts","sourceRoot":"","sources":["../../src/tools/normalize-schema-dialect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"normalize-schema-dialect.d.ts","sourceRoot":"","sources":["../../src/tools/normalize-schema-dialect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAyC9E,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAWlE"}
|
|
@@ -1,5 +1,22 @@
|
|
|
1
|
-
import { ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
2
1
|
const DIALECT_2020_12 = 'https://json-schema.org/draft/2020-12/schema';
|
|
2
|
+
/**
|
|
3
|
+
* A tuple of one item schema, `items: [A, A]` (draft-07), as 2020-12 writes an array of that length:
|
|
4
|
+
* `items: A` with minItems and maxItems. Clients reading 2020-12 refuse the list form, and Claude
|
|
5
|
+
* Code left out 9 tools over it (2026-10-03). The tools still validate with their own tuples.
|
|
6
|
+
*/
|
|
7
|
+
function untuple(record, key) {
|
|
8
|
+
const list = record[key];
|
|
9
|
+
if (!(Array.isArray(list) && list.length > 0))
|
|
10
|
+
return;
|
|
11
|
+
const first = JSON.stringify(list[0]);
|
|
12
|
+
if (!list.every((entry) => JSON.stringify(entry) === first))
|
|
13
|
+
return;
|
|
14
|
+
delete record.prefixItems;
|
|
15
|
+
delete record.additionalItems;
|
|
16
|
+
record.items = list[0];
|
|
17
|
+
record.minItems ??= list.length;
|
|
18
|
+
record.maxItems ??= list.length;
|
|
19
|
+
}
|
|
3
20
|
function retargetDialect(value) {
|
|
4
21
|
if (Array.isArray(value)) {
|
|
5
22
|
for (const item of value)
|
|
@@ -10,6 +27,8 @@ function retargetDialect(value) {
|
|
|
10
27
|
const record = value;
|
|
11
28
|
if (typeof record.$schema === 'string')
|
|
12
29
|
record.$schema = DIALECT_2020_12;
|
|
30
|
+
untuple(record, 'items');
|
|
31
|
+
untuple(record, 'prefixItems');
|
|
13
32
|
for (const key of Object.keys(record))
|
|
14
33
|
retargetDialect(record[key]);
|
|
15
34
|
}
|
|
@@ -20,8 +39,8 @@ export function normalizeToolSchemaDialect(server) {
|
|
|
20
39
|
if (!original)
|
|
21
40
|
return;
|
|
22
41
|
server.server.removeRequestHandler('tools/list');
|
|
23
|
-
server.server.setRequestHandler(
|
|
24
|
-
const result = (await original(request,
|
|
42
|
+
server.server.setRequestHandler('tools/list', async (request, ctx) => {
|
|
43
|
+
const result = (await original(request, ctx));
|
|
25
44
|
retargetDialect(result.tools);
|
|
26
45
|
return result;
|
|
27
46
|
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type NodeDeletionPlan, type NodeDeletionScene } from '@pascal-app/core';
|
|
2
|
+
import { AnyNode, type AnyNodeId } from '@pascal-app/core/schema';
|
|
3
|
+
import type { Patch } from '../bridge/scene-bridge.js';
|
|
4
|
+
export type PatchRefusalCode = 'node_exists' | 'identity_change' | 'immutable_field' | 'invalid_parent' | 'invalid_update' | 'regenerated_default' | 'scripted_field' | 'unknown_field' | 'unknown_material' | 'field_required' | 'shadowed_field' | 'roof_level' | 'roof_level_holds_storey' | 'roof_level_on_top';
|
|
5
|
+
/**
|
|
6
|
+
* A check apply_patch runs over the whole patch before applying it, registered by the module that
|
|
7
|
+
* owns what it protects, so a feature brings its own check (the honest-update check is the
|
|
8
|
+
* first). It may rewrite an update's data (the cleaned data is what is written) and
|
|
9
|
+
* refuses by throwing: a PatchRefusedError comes back as `{ code, patchIndex, id, message }`, an
|
|
10
|
+
* ProtocolError as it is. What it changed in the patch it says in notes, which the answer carries.
|
|
11
|
+
* Lower `order` runs first.
|
|
12
|
+
*/
|
|
13
|
+
export type PatchGuard = {
|
|
14
|
+
name: string;
|
|
15
|
+
order: number;
|
|
16
|
+
run(patches: Patch[], nodes: Readonly<Record<string, AnyNode>>): string[] | void;
|
|
17
|
+
};
|
|
18
|
+
/** Runs a guard on every apply_patch from now on; registering a guard again replaces it. */
|
|
19
|
+
export declare function registerPatchGuard(guard: PatchGuard): void;
|
|
20
|
+
/** Runs every guard in order; the notes are what the guards changed in the patch. */
|
|
21
|
+
export declare function runPatchGuards(patches: Patch[], nodes: Readonly<Record<string, AnyNode>>): string[];
|
|
22
|
+
/**
|
|
23
|
+
* A patch op refused because it would break node identity, the hierarchy or
|
|
24
|
+
* the node's schema. apply_patch returns it as a tool error whose text is
|
|
25
|
+
* JSON `{ code, patchIndex, id, message }`.
|
|
26
|
+
*/
|
|
27
|
+
export declare class PatchRefusedError extends Error {
|
|
28
|
+
readonly code: PatchRefusalCode;
|
|
29
|
+
readonly patchIndex: number;
|
|
30
|
+
readonly nodeId: string;
|
|
31
|
+
constructor(code: PatchRefusalCode, patchIndex: number, nodeId: string, detail: string);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Dry-runs a patch against the scene as each op leaves it and refuses:
|
|
35
|
+
* - a create whose id is already present (`node_exists`): a silent replace
|
|
36
|
+
* orphans the old node's children;
|
|
37
|
+
* - an update that changes `id`, `type`, `object` or `children`
|
|
38
|
+
* (`immutable_field`); restating the current value passes;
|
|
39
|
+
* - an update whose merged node has schema issues the node did not have
|
|
40
|
+
* before (`invalid_update`); kinds without a schema in this runtime pass;
|
|
41
|
+
* - an update writing a material preset the catalog does not know
|
|
42
|
+
* (`invalid_update`; a create is rejected like other invalid creates): it
|
|
43
|
+
* would render as the default finish;
|
|
44
|
+
* - an update of what a node's script owns, its `source` or the size it
|
|
45
|
+
* built (`scripted_field`): only a rebuild keeps them and the geometry in step;
|
|
46
|
+
* - any op on a node an earlier delete in the patch removed, or on a default
|
|
47
|
+
* gutter or downspout that delete regenerates, and any update of the roof
|
|
48
|
+
* segment holding them (`regenerated_default`).
|
|
49
|
+
*
|
|
50
|
+
* Deletes run through the bridge's own deletion preview (`planDeletion`; the
|
|
51
|
+
* local bridge uses core's store planner), one call per run of consecutive
|
|
52
|
+
* deletes as the bridge batches them, so cascades and wall merges are seen as
|
|
53
|
+
* that bridge applies them. A bridge without a preview gets a stricter rule:
|
|
54
|
+
* a create may not reuse any id present at the start of the patch.
|
|
55
|
+
* An update of a roof segment unsettles the default gutters its refresh may
|
|
56
|
+
* regenerate, as a delete does.
|
|
57
|
+
* Lives in the tool layer so every bridge behind `apply_patch` inherits it.
|
|
58
|
+
*/
|
|
59
|
+
export declare function assertPatchKeepsIdentity(patches: readonly Patch[], nodes: Readonly<Record<string, AnyNode>>, rootNodeIds: readonly AnyNodeId[], planDeletion?: (scene: NodeDeletionScene, ids: AnyNodeId[]) => NodeDeletionPlan): void;
|
|
60
|
+
//# sourceMappingURL=patch-guards.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patch-guards.d.ts","sourceRoot":"","sources":["../../src/tools/patch-guards.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EAKvB,MAAM,kBAAkB,CAAA;AAKzB,OAAO,EAAE,OAAO,EAAE,KAAK,SAAS,EAAyB,MAAM,yBAAyB,CAAA;AACxF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AAEnD,MAAM,MAAM,gBAAgB,GACxB,aAAa,GACb,iBAAiB,GACjB,iBAAiB,GACjB,gBAAgB,GAChB,gBAAgB,GAChB,qBAAqB,GACrB,gBAAgB,GAEhB,eAAe,GACf,kBAAkB,GAClB,gBAAgB,GAChB,gBAAgB,GAEhB,YAAY,GACZ,yBAAyB,GACzB,mBAAmB,CAAA;AAEvB;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,EAAE,GAAG,IAAI,CAAA;CACjF,CAAA;AAID,4FAA4F;AAC5F,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,QAKnD;AAED,qFAAqF;AACrF,wBAAgB,cAAc,CAC5B,OAAO,EAAE,KAAK,EAAE,EAChB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACvC,MAAM,EAAE,CAKV;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAExC,QAAQ,CAAC,IAAI,EAAE,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM;IAHzB,YACW,IAAI,EAAE,gBAAgB,EACtB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,EAIf;CACF;AAkDD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,SAAS,KAAK,EAAE,EACzB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,WAAW,EAAE,SAAS,SAAS,EAAE,EACjC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,gBAAgB,GAC9E,IAAI,CA+KN"}
|