@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
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { applySceneWrites, seatWrittenRoofs, } from '@pascal-app/core/agent-operations';
|
|
2
|
+
import { isAgentRefusal } from '@pascal-app/core/agent-tools';
|
|
3
|
+
import { migrateLegacyWallAssemblies } from '@pascal-app/core/scene-migrations';
|
|
1
4
|
import { AnyNode } from '@pascal-app/core/schema';
|
|
2
5
|
import { z } from 'zod';
|
|
3
6
|
import { SceneVersionConflictError } from '../../storage/types.js';
|
|
4
7
|
import { DESTRUCTIVE_TOOL_ANNOTATIONS } from '../annotations.js';
|
|
5
|
-
import {
|
|
8
|
+
import { ProtocolErrorCode, throwMcpError, toolError } from '../errors.js';
|
|
6
9
|
import { appendLiveSceneEvent } from '../live-sync.js';
|
|
7
10
|
import { currentLevelContext, sceneMetaPayload } from './metadata.js';
|
|
8
11
|
export const saveSceneInput = {
|
|
@@ -10,14 +13,18 @@ export const saveSceneInput = {
|
|
|
10
13
|
name: z.string().min(1).max(200),
|
|
11
14
|
projectId: z.string().optional(),
|
|
12
15
|
expectedVersion: z.number().int().positive().optional(),
|
|
16
|
+
replace: z
|
|
17
|
+
.boolean()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Write this session's scene over a project or scene it was not loaded from, replacing what that one holds."),
|
|
13
20
|
saveMode: z
|
|
14
21
|
.enum(['draft', 'checkpoint'])
|
|
15
22
|
.default('draft')
|
|
16
|
-
.describe('`draft` updates the browser-visible working model without polluting version history. `checkpoint` creates a meaningful saved version.'),
|
|
23
|
+
.describe('`draft` updates the browser-visible working model without polluting version history. `checkpoint` creates a meaningful saved version; it does not publish.'),
|
|
17
24
|
publish: z
|
|
18
25
|
.boolean()
|
|
19
26
|
.optional()
|
|
20
|
-
.describe(
|
|
27
|
+
.describe("Hosted Pascal: also publish this checkpoint, making it the version the project's viewers see. Only when the user asks to publish."),
|
|
21
28
|
thumbnail: z.string().url().optional(),
|
|
22
29
|
includeCurrentScene: z
|
|
23
30
|
.boolean()
|
|
@@ -26,7 +33,7 @@ export const saveSceneInput = {
|
|
|
26
33
|
graph: z
|
|
27
34
|
.record(z.string(), z.unknown())
|
|
28
35
|
.optional()
|
|
29
|
-
.describe(
|
|
36
|
+
.describe("Full SceneGraph { nodes, rootNodeIds, collections? } to save instead of the bridge state. Judged against the scene it is saved over (the session's scene when the save goes to it, else the stored scene `id` names) as apply_patch is: a roof it puts on the walls of the storey holding it goes to the level above them, said in notes, and storey content on a roof level is refused. A graph with no scene under it is saved as it is."),
|
|
30
37
|
};
|
|
31
38
|
export const saveSceneOutput = {
|
|
32
39
|
id: z.string(),
|
|
@@ -47,36 +54,117 @@ export const saveSceneOutput = {
|
|
|
47
54
|
graphHash: z.string().optional(),
|
|
48
55
|
levelIds: z.array(z.string()),
|
|
49
56
|
defaultLevelId: z.string().nullable(),
|
|
57
|
+
/** What the server changed in a provided graph before saving it, one line each. */
|
|
58
|
+
notes: z.array(z.string()).optional(),
|
|
50
59
|
};
|
|
60
|
+
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
61
|
+
/** A graph as writes over the scene in hand, so a rule on writes judges only what it changes. */
|
|
62
|
+
function writesOver(scene, nodes) {
|
|
63
|
+
const writes = [];
|
|
64
|
+
for (const node of Object.values(nodes)) {
|
|
65
|
+
const prior = scene[node.id];
|
|
66
|
+
if (!prior) {
|
|
67
|
+
writes.push({ op: 'create', node });
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const next = node;
|
|
71
|
+
const data = Object.fromEntries([...new Set([...Object.keys(prior), ...Object.keys(next)])]
|
|
72
|
+
.filter((key) => !same(prior[key], next[key]))
|
|
73
|
+
.map((key) => [key, next[key]]));
|
|
74
|
+
if (Object.keys(data).length)
|
|
75
|
+
writes.push({ op: 'update', id: node.id, data });
|
|
76
|
+
}
|
|
77
|
+
for (const id of Object.keys(scene))
|
|
78
|
+
if (!nodes[id])
|
|
79
|
+
writes.push({ op: 'delete', id });
|
|
80
|
+
return writes;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The scene a provided graph is saved over, as it stands: the session's when the save goes to the
|
|
84
|
+
* scene in hand, else the stored one. Null for a new scene: a graph with nothing under it is saved
|
|
85
|
+
* as it is, its roofs never judged against an unrelated scene (a legacy project saved from a fresh
|
|
86
|
+
* session would otherwise read as all new writes, and be migrated or refused).
|
|
87
|
+
*/
|
|
88
|
+
async function savedOver(bridge, target) {
|
|
89
|
+
const active = bridge.getActiveScene();
|
|
90
|
+
const inHand = active &&
|
|
91
|
+
(target.id !== undefined
|
|
92
|
+
? active.id === target.id
|
|
93
|
+
: target.projectId !== undefined && active.projectId === target.projectId);
|
|
94
|
+
if (inHand)
|
|
95
|
+
return bridge.getNodes();
|
|
96
|
+
if (target.id === undefined)
|
|
97
|
+
return null;
|
|
98
|
+
const stored = await bridge.loadStoredScene(target.id).catch(() => null);
|
|
99
|
+
return stored?.graph.nodes ?? null;
|
|
100
|
+
}
|
|
101
|
+
/** A saved scene that is more than the default site, building and level. */
|
|
102
|
+
const HOLDS_CONTENT = 3;
|
|
103
|
+
/**
|
|
104
|
+
* The session's scene goes over a project or scene only when it was loaded from it (or created
|
|
105
|
+
* for it). After a server reload a session starts over on a blank scene, and saving it by id wrote
|
|
106
|
+
* it over the project's draft (2026-10-03: 8 levels and 10 imported plans lost).
|
|
107
|
+
*/
|
|
108
|
+
async function requireSceneLoadedFrom(bridge, target) {
|
|
109
|
+
if (target.id === undefined && target.projectId === undefined)
|
|
110
|
+
return;
|
|
111
|
+
const active = bridge.getActiveScene();
|
|
112
|
+
if (target.id !== undefined ? active?.id === target.id : active?.projectId === target.projectId)
|
|
113
|
+
return;
|
|
114
|
+
const nodeCount = target.projectId !== undefined && bridge.canGetProjectStatus
|
|
115
|
+
? ((await bridge.getProjectStatus(target.projectId))?.nodeCount ?? 0)
|
|
116
|
+
: target.id !== undefined
|
|
117
|
+
? Object.keys((await bridge.loadStoredScene(target.id))?.graph.nodes ?? {}).length
|
|
118
|
+
: 0;
|
|
119
|
+
if (nodeCount <= HOLDS_CONTENT)
|
|
120
|
+
return;
|
|
121
|
+
throwMcpError(ProtocolErrorCode.InvalidRequest, `scene_not_loaded: this session's scene was not loaded from ${target.projectId ?? target.id}, which holds ${nodeCount} nodes — the server may have reloaded and this session started over. Call load_scene with it first, or pass replace: true to write this scene over it.`, { ...target, nodeCount });
|
|
122
|
+
}
|
|
51
123
|
export function registerSaveScene(server, bridge) {
|
|
52
124
|
server.registerTool('save_scene', {
|
|
53
125
|
title: 'Save scene',
|
|
54
|
-
description: 'Save the current scene (or a provided graph) to the SceneStore. Defaults to a browser-visible draft save so agents can iterate without creating many project versions. Use saveMode: "checkpoint" for meaningful version history.',
|
|
55
|
-
inputSchema: saveSceneInput,
|
|
56
|
-
outputSchema: saveSceneOutput,
|
|
126
|
+
description: 'Save the current scene (or a provided graph) to the SceneStore. Without id or projectId it saves to the project this session is bound to (create_project or load_scene). Defaults to a browser-visible draft save so agents can iterate without creating many project versions. Use saveMode: "checkpoint" for meaningful version history.',
|
|
127
|
+
inputSchema: z.object(saveSceneInput),
|
|
128
|
+
outputSchema: z.object(saveSceneOutput),
|
|
57
129
|
annotations: DESTRUCTIVE_TOOL_ANNOTATIONS,
|
|
58
|
-
}, async ({ id, name, projectId, expectedVersion, saveMode, publish, thumbnail, includeCurrentScene, graph, }) => {
|
|
130
|
+
}, async ({ id, name, projectId, expectedVersion, saveMode, publish, thumbnail, includeCurrentScene, graph, replace, }) => {
|
|
131
|
+
// Without a target, the session's scene goes where it came from (create_project, load_scene,
|
|
132
|
+
// an earlier save). An agent was refused "Call create_project first" on its first save, right
|
|
133
|
+
// after create_project bound the session to its project.
|
|
134
|
+
const bound = includeCurrentScene && id === undefined && projectId === undefined
|
|
135
|
+
? bridge.getActiveScene()
|
|
136
|
+
: null;
|
|
137
|
+
if (bound) {
|
|
138
|
+
id = bound.id;
|
|
139
|
+
projectId = bound.projectId ?? undefined;
|
|
140
|
+
}
|
|
59
141
|
let sceneGraph;
|
|
142
|
+
let notes = [];
|
|
143
|
+
if (includeCurrentScene && !replace)
|
|
144
|
+
await requireSceneLoadedFrom(bridge, { id, projectId });
|
|
60
145
|
if (includeCurrentScene) {
|
|
61
146
|
const validation = bridge.validateScene();
|
|
62
147
|
if (!validation.valid) {
|
|
63
|
-
throwMcpError(
|
|
148
|
+
throwMcpError(ProtocolErrorCode.InvalidRequest, 'scene_invalid', {
|
|
149
|
+
errors: validation.errors,
|
|
150
|
+
});
|
|
64
151
|
}
|
|
65
152
|
sceneGraph = bridge.exportSceneGraph();
|
|
66
153
|
}
|
|
67
154
|
else {
|
|
68
155
|
if (!graph) {
|
|
69
|
-
throwMcpError(
|
|
156
|
+
throwMcpError(ProtocolErrorCode.InvalidParams, 'graph_required: pass `graph` when includeCurrentScene is false');
|
|
70
157
|
}
|
|
71
158
|
// Security: revalidate every node with AnyNode schema (including the
|
|
72
159
|
// AssetUrl allowlist) BEFORE persisting. Without this, the save_scene
|
|
73
160
|
// graph arg is a bypass for the URL hardening in A7. See P4 report.
|
|
74
161
|
const rawNodes = graph.nodes;
|
|
75
162
|
if (!rawNodes || typeof rawNodes !== 'object') {
|
|
76
|
-
throwMcpError(
|
|
163
|
+
throwMcpError(ProtocolErrorCode.InvalidParams, 'graph.nodes must be an object');
|
|
77
164
|
}
|
|
165
|
+
const migration = migrateLegacyWallAssemblies(rawNodes);
|
|
78
166
|
const errors = [];
|
|
79
|
-
for (const [nodeId, node] of Object.entries(
|
|
167
|
+
for (const [nodeId, node] of Object.entries(migration.nodes)) {
|
|
80
168
|
const res = AnyNode.safeParse(node);
|
|
81
169
|
if (!res.success) {
|
|
82
170
|
for (const issue of res.error.issues) {
|
|
@@ -89,9 +177,28 @@ export function registerSaveScene(server, bridge) {
|
|
|
89
177
|
}
|
|
90
178
|
}
|
|
91
179
|
if (errors.length > 0) {
|
|
92
|
-
throwMcpError(
|
|
180
|
+
throwMcpError(ProtocolErrorCode.InvalidParams, 'graph_invalid', { errors });
|
|
181
|
+
}
|
|
182
|
+
// The roof-level rule, as apply_patch holds it: what the graph changes in the scene it
|
|
183
|
+
// replaces.
|
|
184
|
+
const scene = await savedOver(bridge, { id, projectId });
|
|
185
|
+
let nodes = migration.nodes;
|
|
186
|
+
if (scene) {
|
|
187
|
+
const writes = writesOver(scene, nodes);
|
|
188
|
+
const written = writes.length;
|
|
189
|
+
try {
|
|
190
|
+
notes = seatWrittenRoofs(writes, scene);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
if (!isAgentRefusal(error))
|
|
194
|
+
throw error;
|
|
195
|
+
// An index into the graph's writes means nothing to the caller; the ids do.
|
|
196
|
+
const { index: _index, ...details } = error.details;
|
|
197
|
+
return toolError(error.message, { code: error.code, ...details });
|
|
198
|
+
}
|
|
199
|
+
nodes = applySceneWrites(nodes, writes.slice(written));
|
|
93
200
|
}
|
|
94
|
-
sceneGraph = graph;
|
|
201
|
+
sceneGraph = { ...graph, nodes };
|
|
95
202
|
}
|
|
96
203
|
try {
|
|
97
204
|
const meta = await bridge.saveScene({
|
|
@@ -112,6 +219,7 @@ export function registerSaveScene(server, bridge) {
|
|
|
112
219
|
const payload = {
|
|
113
220
|
...sceneMetaPayload(meta, sceneGraph),
|
|
114
221
|
...currentLevelContext(bridge),
|
|
222
|
+
...(notes.length ? { notes } : {}),
|
|
115
223
|
};
|
|
116
224
|
return {
|
|
117
225
|
content: [{ type: 'text', text: JSON.stringify(payload) }],
|
|
@@ -120,13 +228,13 @@ export function registerSaveScene(server, bridge) {
|
|
|
120
228
|
}
|
|
121
229
|
catch (err) {
|
|
122
230
|
if (err instanceof SceneVersionConflictError) {
|
|
123
|
-
throwMcpError(
|
|
231
|
+
throwMcpError(ProtocolErrorCode.InvalidRequest, 'version_conflict', {
|
|
124
232
|
expectedVersion,
|
|
125
233
|
id,
|
|
126
234
|
});
|
|
127
235
|
}
|
|
128
236
|
const msg = err instanceof Error ? err.message : String(err);
|
|
129
|
-
throwMcpError(
|
|
237
|
+
throwMcpError(ProtocolErrorCode.InvalidRequest, msg);
|
|
130
238
|
}
|
|
131
239
|
});
|
|
132
240
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SceneBridge } from '../../bridge/scene-bridge.js';
|
|
2
2
|
import { type SceneOperations } from '../../operations/index.js';
|
|
3
|
-
import { type ProjectCreateOptions, type ProjectStatus, type SceneEvent, type SceneEventAppendOptions, type SceneEventListOptions, type SceneListOptions, type SceneMeta, type SceneMutateOptions, type SceneSaveOptions, type SceneStore, type SceneWithGraph } from '../../storage/types.js';
|
|
3
|
+
import { type ProjectCreateOptions, type ProjectStatus, type SceneDeleteResult, type SceneEvent, type SceneEventAppendOptions, type SceneEventListOptions, type SceneListOptions, type SceneMeta, type SceneMutateOptions, type SceneSaveOptions, type SceneStore, type SceneWithGraph } from '../../storage/types.js';
|
|
4
4
|
export type StoredTextContent = {
|
|
5
5
|
type: string;
|
|
6
6
|
text: string;
|
|
@@ -20,7 +20,7 @@ export declare function createTestSceneOperations(options?: {
|
|
|
20
20
|
* `expectedVersion`.
|
|
21
21
|
*/
|
|
22
22
|
export declare class InMemorySceneStore implements SceneStore {
|
|
23
|
-
readonly backend:
|
|
23
|
+
readonly backend: 'sqlite';
|
|
24
24
|
private readonly data;
|
|
25
25
|
private readonly projects;
|
|
26
26
|
private readonly events;
|
|
@@ -32,7 +32,7 @@ export declare class InMemorySceneStore implements SceneStore {
|
|
|
32
32
|
save(opts: SceneSaveOptions): Promise<SceneMeta>;
|
|
33
33
|
load(id: string): Promise<SceneWithGraph | null>;
|
|
34
34
|
list(opts?: SceneListOptions): Promise<SceneMeta[]>;
|
|
35
|
-
delete(id: string, opts?: SceneMutateOptions): Promise<
|
|
35
|
+
delete(id: string, opts?: SceneMutateOptions): Promise<SceneDeleteResult>;
|
|
36
36
|
rename(id: string, newName: string, opts?: SceneMutateOptions): Promise<SceneMeta>;
|
|
37
37
|
appendSceneEvent(opts: SceneEventAppendOptions): Promise<SceneEvent>;
|
|
38
38
|
listSceneEvents(sceneId: string, opts?: SceneEventListOptions): Promise<SceneEvent[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../../../src/tools/scene-lifecycle/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AACvD,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAC9E,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EAEf,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAA;AAG5B,MAAM,MAAM,iBAAiB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAE9D,wBAAgB,aAAa,CAAC,OAAO,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEnF;AAED,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE;IAClD,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,KAAK,CAAC,EAAE,kBAAkB,CAAA;CAC3B,GAAG;IACF,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,kBAAkB,CAAA;IACzB,UAAU,EAAE,eAAe,CAAA;CAC5B,CAKA;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,YAAW,UAAU;IACnD,QAAQ,CAAC,OAAO,EAAG,QAAQ,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAWtB;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,YAAY,CAAI;IAElB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../../../src/tools/scene-lifecycle/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AACvD,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAC9E,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,UAAU,EACf,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EAEf,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAA;AAG5B,MAAM,MAAM,iBAAiB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAE9D,wBAAgB,aAAa,CAAC,OAAO,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEnF;AAED,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE;IAClD,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,KAAK,CAAC,EAAE,kBAAkB,CAAA;CAC3B,GAAG;IACF,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,kBAAkB,CAAA;IACzB,UAAU,EAAE,eAAe,CAAA;CAC5B,CAKA;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,YAAW,UAAU;IACnD,QAAQ,CAAC,OAAO,EAAG,QAAQ,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAWtB;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,YAAY,CAAI;IAElB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC,CAatE;IAEK,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAGhE;IAEK,IAAI,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC,CA6DrD;IAEK,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAOrD;IAEK,IAAI,CAAC,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAWxD;IAEK,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAU9E;IAEK,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,SAAS,CAAC,CAiBvF;IAEK,gBAAgB,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC,UAAU,CAAC,CAWzE;IAEK,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAQ1F;IAED,OAAO,CAAC,YAAY;IAiBpB,OAAO,CAAC,MAAM;IAoBd,OAAO,CAAC,eAAe;CA0BxB"}
|
|
@@ -130,7 +130,8 @@ export class InMemorySceneStore {
|
|
|
130
130
|
if (opts?.expectedVersion !== undefined && rec.version !== opts.expectedVersion) {
|
|
131
131
|
throw new SceneVersionConflictError(`Expected version ${opts.expectedVersion}, have ${rec.version}`);
|
|
132
132
|
}
|
|
133
|
-
|
|
133
|
+
this.data.delete(id);
|
|
134
|
+
return { deleted: true, hidden: false };
|
|
134
135
|
}
|
|
135
136
|
async rename(id, newName, opts) {
|
|
136
137
|
const rec = this.data.get(id);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { McpServer } from '@modelcontextprotocol/
|
|
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 setUnitMembersInput: {
|
|
@@ -6,10 +6,12 @@ export declare const setUnitMembersInput: {
|
|
|
6
6
|
memberZoneIds: z.ZodArray<z.ZodString>;
|
|
7
7
|
};
|
|
8
8
|
export declare const setUnitMembersOutput: {
|
|
9
|
+
project: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
10
|
+
unsaved: z.ZodOptional<z.ZodString>;
|
|
9
11
|
persistence: z.ZodOptional<z.ZodObject<{
|
|
10
12
|
status: z.ZodEnum<{
|
|
11
|
-
unbound: "unbound";
|
|
12
13
|
events_unsupported: "events_unsupported";
|
|
14
|
+
unbound: "unbound";
|
|
13
15
|
}>;
|
|
14
16
|
warning: z.ZodString;
|
|
15
17
|
}, z.core.$strip>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"set-unit-members.d.ts","sourceRoot":"","sources":["../../src/tools/set-unit-members.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"set-unit-members.d.ts","sourceRoot":"","sources":["../../src/tools/set-unit-members.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAE7D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAOpD,eAAO,MAAM,mBAAmB;IAC9B,MAAM;IACN,aAAa;CACd,CAAA;AAED,eAAO,MAAM,oBAAoB;;;;;;;;;;;;CAIhC,CAAA;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAmCvF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { DESTRUCTIVE_TOOL_ANNOTATIONS } from './annotations.js';
|
|
3
|
-
import {
|
|
3
|
+
import { ProtocolErrorCode, throwMcpError } from './errors.js';
|
|
4
4
|
import { liveSyncOutput, persistencePayload, publishLiveSceneSnapshot } from './live-sync.js';
|
|
5
5
|
import { NodeIdSchema } from './schemas.js';
|
|
6
6
|
import { validateUnitMembers } from './unit-members.js';
|
|
@@ -17,19 +17,19 @@ export function registerSetUnitMembers(server, bridge) {
|
|
|
17
17
|
server.registerTool('set_unit_members', {
|
|
18
18
|
title: 'Set unit members',
|
|
19
19
|
description: 'Replace the zones referenced by a unit. Every zone must belong to a level in the same building.',
|
|
20
|
-
inputSchema: setUnitMembersInput,
|
|
21
|
-
outputSchema: setUnitMembersOutput,
|
|
20
|
+
inputSchema: z.object(setUnitMembersInput),
|
|
21
|
+
outputSchema: z.object(setUnitMembersOutput),
|
|
22
22
|
annotations: { ...DESTRUCTIVE_TOOL_ANNOTATIONS, idempotentHint: true },
|
|
23
23
|
}, async ({ unitId, memberZoneIds }) => {
|
|
24
24
|
const unit = bridge.getNode(unitId);
|
|
25
25
|
if (!unit) {
|
|
26
|
-
throwMcpError(
|
|
26
|
+
throwMcpError(ProtocolErrorCode.InvalidParams, `Unit not found: ${unitId}`);
|
|
27
27
|
}
|
|
28
28
|
if (unit.type !== 'unit') {
|
|
29
|
-
throwMcpError(
|
|
29
|
+
throwMcpError(ProtocolErrorCode.InvalidParams, `Node ${unitId} is a ${unit.type}, expected unit`);
|
|
30
30
|
}
|
|
31
31
|
if (!unit.parentId) {
|
|
32
|
-
throwMcpError(
|
|
32
|
+
throwMcpError(ProtocolErrorCode.InvalidParams, `Unit ${unitId} must belong to a building`);
|
|
33
33
|
}
|
|
34
34
|
const members = validateUnitMembers(bridge, unit.parentId, memberZoneIds);
|
|
35
35
|
bridge.updateNode(unit.id, { members });
|
package/dist/tools/set-zone.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { McpServer } from '@modelcontextprotocol/
|
|
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 setZoneInput: {
|
|
@@ -8,10 +8,12 @@ export declare const setZoneInput: {
|
|
|
8
8
|
properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
9
9
|
};
|
|
10
10
|
export declare const setZoneOutput: {
|
|
11
|
+
project: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
12
|
+
unsaved: z.ZodOptional<z.ZodString>;
|
|
11
13
|
persistence: z.ZodOptional<z.ZodObject<{
|
|
12
14
|
status: z.ZodEnum<{
|
|
13
|
-
unbound: "unbound";
|
|
14
15
|
events_unsupported: "events_unsupported";
|
|
16
|
+
unbound: "unbound";
|
|
15
17
|
}>;
|
|
16
18
|
warning: z.ZodString;
|
|
17
19
|
}, z.core.$strip>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"set-zone.d.ts","sourceRoot":"","sources":["../../src/tools/set-zone.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"set-zone.d.ts","sourceRoot":"","sources":["../../src/tools/set-zone.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAI7D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAMpD,eAAO,MAAM,YAAY;IACvB,OAAO;IACP,OAAO;IACP,KAAK;IACL,UAAU;CACX,CAAA;AAED,eAAO,MAAM,aAAa;;;;;;;;;;;CAGzB,CAAA;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAwDhF"}
|
package/dist/tools/set-zone.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isAllocatedRoomName, newZone, walledRoomAt } from '@pascal-app/core';
|
|
2
|
+
import { roofLevelRefusal } from '@pascal-app/core/agent-operations';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { ADDITIVE_TOOL_ANNOTATIONS } from './annotations.js';
|
|
4
|
-
import {
|
|
5
|
+
import { ProtocolErrorCode, refusalResult, throwMcpError } from './errors.js';
|
|
5
6
|
import { liveSyncOutput, persistencePayload, publishLiveSceneSnapshot } from './live-sync.js';
|
|
6
7
|
import { NodeIdSchema, Vec2Schema } from './schemas.js';
|
|
7
8
|
export const setZoneInput = {
|
|
@@ -17,30 +18,41 @@ export const setZoneOutput = {
|
|
|
17
18
|
export function registerSetZone(server, bridge) {
|
|
18
19
|
server.registerTool('set_zone', {
|
|
19
20
|
title: 'Set zone',
|
|
20
|
-
description: 'Create a polygonal zone on the given level. label is stored as the zone name and properties are merged into metadata.',
|
|
21
|
-
inputSchema: setZoneInput,
|
|
22
|
-
outputSchema: setZoneOutput,
|
|
21
|
+
description: 'Create a polygonal zone on the given level. label is stored as the zone name and properties are merged into metadata. A polygon along walls that already enclose a room is that room, never a second zone: a room still named "Room N", or already named label, takes the label and properties; a room a person named otherwise is refused (rename it with update_node only when the person asked).',
|
|
22
|
+
inputSchema: z.object(setZoneInput),
|
|
23
|
+
outputSchema: z.object(setZoneOutput),
|
|
23
24
|
annotations: ADDITIVE_TOOL_ANNOTATIONS,
|
|
24
25
|
}, async ({ levelId, polygon, label, properties }) => {
|
|
25
26
|
const parent = bridge.getNode(levelId);
|
|
26
27
|
if (!parent) {
|
|
27
|
-
throwMcpError(
|
|
28
|
+
throwMcpError(ProtocolErrorCode.InvalidParams, `Level not found: ${levelId}`);
|
|
28
29
|
}
|
|
29
30
|
if (parent.type !== 'level') {
|
|
30
|
-
throwMcpError(
|
|
31
|
+
throwMcpError(ProtocolErrorCode.InvalidParams, `Node ${levelId} is a ${parent.type}, expected level`);
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
const onRoofLevel = roofLevelRefusal(bridge.getNodes(), levelId, 'a zone');
|
|
34
|
+
if (onRoofLevel)
|
|
35
|
+
return refusalResult(onRoofLevel);
|
|
36
|
+
const walled = walledRoomAt(bridge.getNodes(), levelId, polygon);
|
|
37
|
+
if (walled && !isAllocatedRoomName(walled.name) && walled.name !== label) {
|
|
38
|
+
throwMcpError(ProtocolErrorCode.InvalidParams, `The walls here already enclose the room "${walled.name}" (${walled.id}), which a person named; a zone "${label}" would stack a second room on it. Rename it with update_node only if the person asked; otherwise leave it.`);
|
|
39
|
+
}
|
|
40
|
+
let id;
|
|
41
|
+
if (walled) {
|
|
42
|
+
id = walled.id;
|
|
43
|
+
bridge.updateNode(id, {
|
|
44
|
+
name: label,
|
|
45
|
+
metadata: { ...walled.metadata, ...properties },
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
const zone = newZone({
|
|
50
|
+
name: label,
|
|
51
|
+
polygon: polygon,
|
|
52
|
+
metadata: properties ?? {},
|
|
53
|
+
});
|
|
54
|
+
id = bridge.createNode(zone, levelId);
|
|
37
55
|
}
|
|
38
|
-
const zone = ZoneNode.parse({
|
|
39
|
-
name: label,
|
|
40
|
-
polygon: polygon,
|
|
41
|
-
metadata: properties ?? {},
|
|
42
|
-
});
|
|
43
|
-
const id = bridge.createNode(zone, levelId);
|
|
44
56
|
const persistence = await publishLiveSceneSnapshot(bridge, 'set_zone');
|
|
45
57
|
const payload = { zoneId: id, ...persistencePayload(persistence) };
|
|
46
58
|
return {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/server';
|
|
2
|
+
import { type AddObjectInput, type AgentHostRuntime, type AgentOperation, type AssetViewLoader, type SceneChanges } from '@pascal-app/core/agent-operations';
|
|
3
|
+
import { type AgentToolAnnotations, type AgentToolContract, type DesignBrief } from '@pascal-app/core/agent-tools';
|
|
4
|
+
import { type CompiledGeometryScript } from '@pascal-app/core/schema';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import type { Patch } from '../bridge/scene-bridge.js';
|
|
7
|
+
import type { SceneOperations } from '../operations/index.js';
|
|
8
|
+
import { type AssetCatalog } from './asset-catalog.js';
|
|
9
|
+
/**
|
|
10
|
+
* A tool the server runs as a core-style operation: parse with the contract, plan with the
|
|
11
|
+
* operation, apply its changes through the bridge in one history step, answer with what the scene
|
|
12
|
+
* then holds. `annotations` are the host's to choose from its own review of the operation; a
|
|
13
|
+
* plugin contract's `annotations` are only its hints.
|
|
14
|
+
*/
|
|
15
|
+
export type OperationTool = {
|
|
16
|
+
contract: AgentToolContract;
|
|
17
|
+
operation: AgentOperation;
|
|
18
|
+
annotations: AgentToolAnnotations;
|
|
19
|
+
outputSchema?: Record<string, z.ZodType>;
|
|
20
|
+
envelope?: (bridge: SceneOperations) => Record<string, unknown>;
|
|
21
|
+
/** Reads the host's item library: only these calls wait for it (the hosted one is a query). */
|
|
22
|
+
catalog?: true;
|
|
23
|
+
};
|
|
24
|
+
export type PlanReadingHosts = {
|
|
25
|
+
/** Reads catalog pictures and surfaces before view_assets and paint. */
|
|
26
|
+
assetViews?: AssetViewLoader;
|
|
27
|
+
/** Compiles one add_object module for a batch before the synchronous operations run. */
|
|
28
|
+
buildObject?: (input: Omit<AddObjectInput, 'compiled'>) => Promise<CompiledGeometryScript>;
|
|
29
|
+
};
|
|
30
|
+
export declare function toPatches(changes: SceneChanges): Patch[];
|
|
31
|
+
/** How an outcome reaches this bridge: its patches, its derived construction, its session record. */
|
|
32
|
+
export declare function agentRuntime(bridge: SceneOperations): AgentHostRuntime;
|
|
33
|
+
export declare function briefInForce(bridge: SceneOperations): DesignBrief | null | undefined;
|
|
34
|
+
export declare function registerSharedTools(server: McpServer, bridge: SceneOperations, catalog?: AssetCatalog, hosts?: PlanReadingHosts): void;
|
|
35
|
+
/**
|
|
36
|
+
* Registers one operation tool, as the shared tools are: a host adds a plugin's `'operation'`
|
|
37
|
+
* tools with it, from the plugin's `./agent-tools` contract and `./agent-operations` operation.
|
|
38
|
+
*/
|
|
39
|
+
export declare function registerOperationTool(server: McpServer, bridge: SceneOperations, tool: OperationTool, catalog?: AssetCatalog, hosts?: PlanReadingHosts): void;
|
|
40
|
+
//# sourceMappingURL=shared-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared-tools.d.ts","sourceRoot":"","sources":["../../src/tools/shared-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EACL,KAAK,cAAc,EAGnB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,eAAe,EAUpB,KAAK,YAAY,EAGlB,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EAStB,KAAK,WAAW,EA+BjB,MAAM,8BAA8B,CAAA;AAErC,OAAO,EAGL,KAAK,sBAAsB,EAG5B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAMpD,OAAO,EAAE,KAAK,YAAY,EAAkB,MAAM,iBAAiB,CAAA;AASnE;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,SAAS,EAAE,cAAc,CAAA;IACzB,WAAW,EAAE,oBAAoB,CAAA;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;IACxC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/D,+FAA+F;IAC/F,OAAO,CAAC,EAAE,IAAI,CAAA;CACf,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,wEAAwE;IACxE,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,wFAAwF;IACxF,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAA;CAC3F,CAAA;AAmaD,wBAAgB,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,KAAK,EAAE,CAmBxD;AAED,qGAAqG;AACrG,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,gBAAgB,CAsBtE;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,WAAW,GAAG,IAAI,GAAG,SAAS,CAIpF;AAUD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,eAAe,EACvB,OAAO,GAAE,YAA6B,EACtC,KAAK,GAAE,gBAAqB,GAC3B,IAAI,CAGN;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,aAAa,EACnB,OAAO,GAAE,YAA6B,EACtC,KAAK,GAAE,gBAAqB,GAC3B,IAAI,CAuHN"}
|