@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
package/dist/bin/pascal-mcp.js
CHANGED
|
@@ -6,7 +6,8 @@ import { parseArgs } from 'node:util';
|
|
|
6
6
|
import { SceneBridge } from '../bridge/scene-bridge.js';
|
|
7
7
|
import { version } from '../index.js';
|
|
8
8
|
import { createPascalMcpServer } from '../server.js';
|
|
9
|
-
import { createSceneStore } from '../storage/index.js';
|
|
9
|
+
import { createSceneStore, resolveDefaultDatabasePath } from '../storage/index.js';
|
|
10
|
+
import { localGeometryScripts } from '../tools/local-geometry-scripts.js';
|
|
10
11
|
import { connectHttp } from '../transports/http.js';
|
|
11
12
|
import { connectStdio } from '../transports/stdio.js';
|
|
12
13
|
const HELP = `pascal-mcp — MCP server for the Pascal editor
|
|
@@ -54,7 +55,12 @@ async function main() {
|
|
|
54
55
|
bridge.loadJSON(readFileSync(values.scene, 'utf8'));
|
|
55
56
|
else
|
|
56
57
|
bridge.loadDefault();
|
|
57
|
-
|
|
58
|
+
bridge.clearHistory();
|
|
59
|
+
return createPascalMcpServer({
|
|
60
|
+
bridge,
|
|
61
|
+
store,
|
|
62
|
+
geometryScripts: localGeometryScripts(resolveDefaultDatabasePath()),
|
|
63
|
+
});
|
|
58
64
|
};
|
|
59
65
|
if (values.http) {
|
|
60
66
|
const portNum = Number.parseInt(values.port ?? '3917', 10);
|
|
@@ -88,7 +94,7 @@ async function main() {
|
|
|
88
94
|
}
|
|
89
95
|
else {
|
|
90
96
|
// --stdio is the default when no transport flag is passed.
|
|
91
|
-
|
|
97
|
+
connectStdio(createServer);
|
|
92
98
|
console.error('[pascal-mcp] stdio server running');
|
|
93
99
|
}
|
|
94
100
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type SessionKeep, type SessionRecord } from '@pascal-app/core/agent-operations';
|
|
2
|
+
/**
|
|
3
|
+
* Where a host keeps what the agent's session should remember: the moments it named and what it
|
|
4
|
+
* made. Never scene data. A host with sessions that outlive the process keeps it beside them.
|
|
5
|
+
*/
|
|
6
|
+
export type AgentSession = {
|
|
7
|
+
read(): SessionRecord;
|
|
8
|
+
keep(keep: SessionKeep): void;
|
|
9
|
+
};
|
|
10
|
+
/** The standalone server's: in memory, for as long as the bridge holds a scene. */
|
|
11
|
+
export declare class InMemoryAgentSession implements AgentSession {
|
|
12
|
+
#private;
|
|
13
|
+
read(): SessionRecord;
|
|
14
|
+
keep(keep: SessionKeep): void;
|
|
15
|
+
/** Binding another scene starts another session. */
|
|
16
|
+
replace(record?: SessionRecord): void;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=agent-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-session.d.ts","sourceRoot":"","sources":["../../src/bridge/agent-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,aAAa,EACnB,MAAM,mCAAmC,CAAA;AAE1C;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,IAAI,aAAa,CAAA;IACrB,IAAI,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAAA;CAC9B,CAAA;AAED,mFAAmF;AACnF,qBAAa,oBAAqB,YAAW,YAAY;;IAGvD,IAAI,IAAI,aAAa,CAEpB;IAED,IAAI,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAE5B;IAED,oDAAoD;IACpD,OAAO,CAAC,MAAM,GAAE,aAA6B,GAAG,IAAI,CAEnD;CACF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { EMPTY_SESSION, keepInSession, } from '@pascal-app/core/agent-operations';
|
|
2
|
+
/** The standalone server's: in memory, for as long as the bridge holds a scene. */
|
|
3
|
+
export class InMemoryAgentSession {
|
|
4
|
+
#record = EMPTY_SESSION;
|
|
5
|
+
read() {
|
|
6
|
+
return this.#record;
|
|
7
|
+
}
|
|
8
|
+
keep(keep) {
|
|
9
|
+
this.#record = keepInSession(this.#record, keep);
|
|
10
|
+
}
|
|
11
|
+
/** Binding another scene starts another session. */
|
|
12
|
+
replace(record = EMPTY_SESSION) {
|
|
13
|
+
this.#record = record;
|
|
14
|
+
}
|
|
15
|
+
}
|
package/dist/bridge/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export type { ActiveSceneMeta, CreatePatch, DeletePatch, Patch, UpdatePatch, ValidationError, ValidationResult, } from './scene-bridge.js';
|
|
1
|
+
export type { ActiveSceneMeta, CreatePatch, DeletePatch, MaterialUpsertPatch, Patch, UpdatePatch, ValidationError, ValidationResult, } from './scene-bridge.js';
|
|
2
2
|
export { SceneBridge } from './scene-bridge.js';
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bridge/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,WAAW,EACX,WAAW,EACX,KAAK,EACL,WAAW,EACX,eAAe,EACf,gBAAgB,GACjB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bridge/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,WAAW,EACX,WAAW,EACX,mBAAmB,EACnB,KAAK,EACL,WAAW,EACX,eAAe,EACf,gBAAgB,GACjB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import './node-shims.js';
|
|
2
|
+
import { type NodeDeletionPlan, type NodeDeletionScene } from '@pascal-app/core';
|
|
2
3
|
import type { SceneGraph } from '@pascal-app/core/clone-scene-graph';
|
|
3
|
-
import type { AnyNode } from '@pascal-app/core/schema';
|
|
4
|
-
import { type AnyNodeId, type AnyNodeType } from '@pascal-app/core/schema';
|
|
4
|
+
import type { AnyNode, Collection, CollectionId, SceneMaterialId } from '@pascal-app/core/schema';
|
|
5
|
+
import { type AnyNodeId, type AnyNodeType, SceneMaterial } from '@pascal-app/core/schema';
|
|
5
6
|
import useScene from '@pascal-app/core/store';
|
|
6
7
|
import type { SceneMeta } from '../storage/types.js';
|
|
8
|
+
import { InMemoryAgentSession } from './agent-session.js';
|
|
7
9
|
export type ValidationError = {
|
|
8
10
|
nodeId: string;
|
|
9
11
|
path: string;
|
|
@@ -30,8 +32,13 @@ export type DeletePatch = {
|
|
|
30
32
|
id: AnyNodeId;
|
|
31
33
|
cascade?: boolean;
|
|
32
34
|
};
|
|
33
|
-
|
|
34
|
-
export type
|
|
35
|
+
/** Adds or replaces a scene material, written before the nodes of the same patch. */
|
|
36
|
+
export type MaterialUpsertPatch = {
|
|
37
|
+
op: 'upsert_material';
|
|
38
|
+
material: SceneMaterial;
|
|
39
|
+
};
|
|
40
|
+
export type Patch = CreatePatch | UpdatePatch | DeletePatch | MaterialUpsertPatch;
|
|
41
|
+
export type ActiveSceneMeta = Pick<SceneMeta, 'id' | 'name' | 'projectId' | 'ownerId' | 'thumbnailUrl' | 'version' | 'graphHash'>;
|
|
35
42
|
/** The `extra` bag `setScene` accepts — collections, materials, plugin state. */
|
|
36
43
|
type SetSceneExtra = Parameters<ReturnType<typeof useScene.getState>['setScene']>[2];
|
|
37
44
|
/**
|
|
@@ -42,7 +49,11 @@ type SetSceneExtra = Parameters<ReturnType<typeof useScene.getState>['setScene']
|
|
|
42
49
|
* `flushDirty()` for observability.
|
|
43
50
|
*/
|
|
44
51
|
export declare class SceneBridge {
|
|
52
|
+
/** `applyPatch` takes `upsert_material` patches, in the patch's one history step. */
|
|
53
|
+
readonly supportsMaterialUpserts = true;
|
|
45
54
|
private activeScene;
|
|
55
|
+
/** What the agent's session has kept: its named moments, and what it made. */
|
|
56
|
+
readonly agentSession: InMemoryAgentSession;
|
|
46
57
|
/**
|
|
47
58
|
* Scene identity currently bound to this bridge. MCP tools use this to know
|
|
48
59
|
* which editor scene should receive live events after mutations.
|
|
@@ -67,6 +78,9 @@ export declare class SceneBridge {
|
|
|
67
78
|
getNode(id: AnyNodeId): AnyNode | null;
|
|
68
79
|
/** All nodes (live reference into the store — do NOT mutate). */
|
|
69
80
|
getNodes(): Record<AnyNodeId, AnyNode>;
|
|
81
|
+
getCollections(): Record<CollectionId, Collection>;
|
|
82
|
+
getMaterials(): Record<SceneMaterialId, SceneMaterial>;
|
|
83
|
+
setCollections(collections: Record<CollectionId, Collection>): void;
|
|
70
84
|
/** Root node IDs. */
|
|
71
85
|
getRootNodeIds(): AnyNodeId[];
|
|
72
86
|
/**
|
|
@@ -118,14 +132,34 @@ export declare class SceneBridge {
|
|
|
118
132
|
deleteNode(id: AnyNodeId, cascade?: boolean): string[];
|
|
119
133
|
/**
|
|
120
134
|
* Atomic multi-op patch. Validates EVERY patch first (dry run); only if all
|
|
121
|
-
* pass does it apply
|
|
122
|
-
*
|
|
135
|
+
* pass does it apply through the store actions in one history step. Throws on
|
|
136
|
+
* validation failure without mutating state.
|
|
123
137
|
*/
|
|
124
138
|
applyPatch(patches: Patch[]): {
|
|
125
139
|
appliedOps: number;
|
|
126
140
|
deletedIds: AnyNodeId[];
|
|
127
141
|
createdIds: AnyNodeId[];
|
|
128
142
|
};
|
|
143
|
+
/**
|
|
144
|
+
* Preview what deleting `ids` does to `scene` with this bridge's semantics
|
|
145
|
+
* (core's store planner), without committing or minting default gutters.
|
|
146
|
+
* The apply_patch guard runs its dry run through this.
|
|
147
|
+
*/
|
|
148
|
+
planDeletion(scene: NodeDeletionScene, ids: AnyNodeId[]): NodeDeletionPlan;
|
|
149
|
+
/**
|
|
150
|
+
* Derive this scene's construction the way the browser's commit subscriber
|
|
151
|
+
* does: one pass of the shared kernel writes the room zones, the auto
|
|
152
|
+
* ceilings, the wall side classification and the floor plates.
|
|
153
|
+
*
|
|
154
|
+
* The headless bridge has no space-detection subscriber, so the semantic
|
|
155
|
+
* tools that state room intent call this to materialise the construction
|
|
156
|
+
* they report. Writes are history-paused, so undo returns to the state
|
|
157
|
+
* before the triggering tool call rather than stranding derived nodes.
|
|
158
|
+
*/
|
|
159
|
+
deriveStructure(levelIds?: AnyNodeId[]): {
|
|
160
|
+
createdIds: AnyNodeId[];
|
|
161
|
+
deletedIds: AnyNodeId[];
|
|
162
|
+
};
|
|
129
163
|
/** Undo. Returns the number of steps actually undone. */
|
|
130
164
|
undo(steps?: number): number;
|
|
131
165
|
/** Redo. Returns the number of steps actually redone. */
|
|
@@ -147,6 +181,8 @@ export declare class SceneBridge {
|
|
|
147
181
|
};
|
|
148
182
|
/** Clear the temporal undo/redo history. */
|
|
149
183
|
clearHistory(): void;
|
|
184
|
+
runAsSingleHistoryStep<T>(run: () => T): T;
|
|
185
|
+
private reconcileDependentStructure;
|
|
150
186
|
/**
|
|
151
187
|
* Return the node whose `children` array (string or object form) contains
|
|
152
188
|
* the given id, or null if none. Used as a fallback when `parentId` is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene-bridge.d.ts","sourceRoot":"","sources":["../../src/bridge/scene-bridge.ts"],"names":[],"mappings":"AACA,OAAO,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"scene-bridge.d.ts","sourceRoot":"","sources":["../../src/bridge/scene-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// Side-effect import MUST come first: installs RAF polyfill before core loads.
|
|
2
2
|
import './node-shims.js';
|
|
3
|
-
import { HIDDEN_SITE_NOTE } from '@pascal-app/core';
|
|
4
|
-
import { AnyNode as AnyNodeSchema, parseNode, } from '@pascal-app/core/schema';
|
|
3
|
+
import { adjacentLevelId, applyStructureReconciliation, assertDerivedNodeWrites, changedStairOpeningOwners, floorOpeningTargets, HIDDEN_SITE_NOTE, pauseSceneHistory, pauseSpaceDetection, planNodeDeletion, planOwnedFloorOpenings, planWallDeletion, resumeSceneHistory, resumeSpaceDetection, runAsSingleSceneHistoryStep, stairDeckLevelId, structureChangeBatch, } from '@pascal-app/core';
|
|
4
|
+
import { AnyNode as AnyNodeSchema, generateId, parseNode, SceneMaterial, } from '@pascal-app/core/schema';
|
|
5
5
|
// Per PLAN §0.6: `useScene` is the DEFAULT export from `@pascal-app/core/store`.
|
|
6
6
|
import useScene from '@pascal-app/core/store';
|
|
7
|
+
import { InMemoryAgentSession } from './agent-session.js';
|
|
7
8
|
/**
|
|
8
9
|
* Headless bridge to the `@pascal-app/core` Zustand store.
|
|
9
10
|
*
|
|
@@ -12,7 +13,11 @@ import useScene from '@pascal-app/core/store';
|
|
|
12
13
|
* `flushDirty()` for observability.
|
|
13
14
|
*/
|
|
14
15
|
export class SceneBridge {
|
|
16
|
+
/** `applyPatch` takes `upsert_material` patches, in the patch's one history step. */
|
|
17
|
+
supportsMaterialUpserts = true;
|
|
15
18
|
activeScene = null;
|
|
19
|
+
/** What the agent's session has kept: its named moments, and what it made. */
|
|
20
|
+
agentSession = new InMemoryAgentSession();
|
|
16
21
|
/**
|
|
17
22
|
* Scene identity currently bound to this bridge. MCP tools use this to know
|
|
18
23
|
* which editor scene should receive live events after mutations.
|
|
@@ -25,6 +30,7 @@ export class SceneBridge {
|
|
|
25
30
|
ownerId: meta.ownerId,
|
|
26
31
|
thumbnailUrl: meta.thumbnailUrl,
|
|
27
32
|
version: meta.version,
|
|
33
|
+
...(meta.graphHash === undefined ? {} : { graphHash: meta.graphHash }),
|
|
28
34
|
};
|
|
29
35
|
}
|
|
30
36
|
getActiveScene() {
|
|
@@ -35,10 +41,12 @@ export class SceneBridge {
|
|
|
35
41
|
}
|
|
36
42
|
/** Load initial state; if empty, creates default Site → Building → Level. */
|
|
37
43
|
loadDefault() {
|
|
44
|
+
this.agentSession.replace();
|
|
38
45
|
useScene.getState().loadScene();
|
|
39
46
|
}
|
|
40
47
|
/** Replace entire scene (undoable via Zundo). */
|
|
41
48
|
setScene(nodes, rootNodeIds, extra) {
|
|
49
|
+
this.agentSession.replace();
|
|
42
50
|
useScene.getState().setScene(nodes, rootNodeIds, extra);
|
|
43
51
|
}
|
|
44
52
|
/** Full snapshot for export, including collections and the material palette. */
|
|
@@ -119,6 +127,17 @@ export class SceneBridge {
|
|
|
119
127
|
getNodes() {
|
|
120
128
|
return useScene.getState().nodes;
|
|
121
129
|
}
|
|
130
|
+
getCollections() {
|
|
131
|
+
return useScene.getState().collections;
|
|
132
|
+
}
|
|
133
|
+
getMaterials() {
|
|
134
|
+
return useScene.getState().materials;
|
|
135
|
+
}
|
|
136
|
+
setCollections(collections) {
|
|
137
|
+
if (useScene.getState().readOnly)
|
|
138
|
+
return;
|
|
139
|
+
useScene.setState({ collections });
|
|
140
|
+
}
|
|
122
141
|
/** Root node IDs. */
|
|
123
142
|
getRootNodeIds() {
|
|
124
143
|
return useScene.getState().rootNodeIds;
|
|
@@ -237,7 +256,12 @@ export class SceneBridge {
|
|
|
237
256
|
* Returns the generated id.
|
|
238
257
|
*/
|
|
239
258
|
createNode(node, parentId) {
|
|
240
|
-
useScene.getState().
|
|
259
|
+
const before = useScene.getState().nodes;
|
|
260
|
+
assertDerivedNodeWrites(before, { create: [{ node: parentId ? { ...node, parentId } : node }] });
|
|
261
|
+
runAsSingleSceneHistoryStep(useScene, () => {
|
|
262
|
+
useScene.getState().createNode(node, parentId);
|
|
263
|
+
this.reconcileDependentStructure(before);
|
|
264
|
+
});
|
|
241
265
|
return node.id;
|
|
242
266
|
}
|
|
243
267
|
/** Update node fields (shallow merge through the core store). */
|
|
@@ -245,7 +269,12 @@ export class SceneBridge {
|
|
|
245
269
|
if (!useScene.getState().nodes[id]) {
|
|
246
270
|
throw new Error(`node not found: ${id}`);
|
|
247
271
|
}
|
|
248
|
-
useScene.getState().
|
|
272
|
+
const before = useScene.getState().nodes;
|
|
273
|
+
assertDerivedNodeWrites(before, { update: [{ id, data }] });
|
|
274
|
+
runAsSingleSceneHistoryStep(useScene, () => {
|
|
275
|
+
useScene.getState().updateNode(id, data);
|
|
276
|
+
this.reconcileDependentStructure(before);
|
|
277
|
+
});
|
|
249
278
|
}
|
|
250
279
|
/**
|
|
251
280
|
* Delete a node. If the node has children and `cascade === false`, throws.
|
|
@@ -265,7 +294,18 @@ export class SceneBridge {
|
|
|
265
294
|
throw new Error(`node has ${descendants.length - 1} descendant(s); pass cascade: true to delete recursively`);
|
|
266
295
|
}
|
|
267
296
|
const before = new Set(Object.keys(state.nodes));
|
|
268
|
-
|
|
297
|
+
const wallLevels = [
|
|
298
|
+
...new Set(descendants.flatMap((nodeId) => {
|
|
299
|
+
const deleted = state.nodes[nodeId];
|
|
300
|
+
return deleted?.type === 'wall' && deleted.parentId ? [deleted.parentId] : [];
|
|
301
|
+
})),
|
|
302
|
+
];
|
|
303
|
+
runAsSingleSceneHistoryStep(useScene, () => {
|
|
304
|
+
useScene.getState().deleteNode(id);
|
|
305
|
+
if (wallLevels.length)
|
|
306
|
+
this.deriveStructure(wallLevels);
|
|
307
|
+
this.reconcileDependentStructure(state.nodes);
|
|
308
|
+
});
|
|
269
309
|
const afterNodes = useScene.getState().nodes;
|
|
270
310
|
const removed = [];
|
|
271
311
|
for (const prevId of before) {
|
|
@@ -276,12 +316,14 @@ export class SceneBridge {
|
|
|
276
316
|
}
|
|
277
317
|
/**
|
|
278
318
|
* Atomic multi-op patch. Validates EVERY patch first (dry run); only if all
|
|
279
|
-
* pass does it apply
|
|
280
|
-
*
|
|
319
|
+
* pass does it apply through the store actions in one history step. Throws on
|
|
320
|
+
* validation failure without mutating state.
|
|
281
321
|
*/
|
|
282
322
|
applyPatch(patches) {
|
|
283
323
|
const state = useScene.getState();
|
|
284
324
|
const nodes = state.nodes;
|
|
325
|
+
if (state.readOnly)
|
|
326
|
+
throw new Error('scene_read_only');
|
|
285
327
|
// Track synthesized state as we dry-run so later ops can reference
|
|
286
328
|
// earlier-created ids and reflect earlier-deleted ids.
|
|
287
329
|
const simAvailable = new Set(Object.keys(nodes));
|
|
@@ -290,11 +332,18 @@ export class SceneBridge {
|
|
|
290
332
|
// Zod-normalised copy (which has a generated id if the caller omitted one)
|
|
291
333
|
// instead of the unparsed input.
|
|
292
334
|
const parsedCreateNodes = new Map();
|
|
335
|
+
const materialUpserts = new Map();
|
|
293
336
|
for (let i = 0; i < patches.length; i++) {
|
|
294
337
|
const p = patches[i];
|
|
295
338
|
if (!p)
|
|
296
339
|
throw new Error(`invalid patch: patches[${i}] is undefined`);
|
|
297
|
-
if (p.op === '
|
|
340
|
+
if (p.op === 'upsert_material') {
|
|
341
|
+
const parsed = SceneMaterial.safeParse(p.material);
|
|
342
|
+
if (!parsed.success)
|
|
343
|
+
throw new Error(`invalid patch: patches[${i}] material failed schema: ${parsed.error.message}`);
|
|
344
|
+
materialUpserts.set(parsed.data.id, parsed.data);
|
|
345
|
+
}
|
|
346
|
+
else if (p.op === 'create') {
|
|
298
347
|
const res = parseNode(p.node);
|
|
299
348
|
if (!res.success) {
|
|
300
349
|
throw new Error(`invalid patch: patches[${i}] create node failed schema: ${res.error.message}`);
|
|
@@ -302,7 +351,7 @@ export class SceneBridge {
|
|
|
302
351
|
if (p.parentId !== undefined && !simAvailable.has(p.parentId)) {
|
|
303
352
|
throw new Error(`invalid patch: patches[${i}] create parentId "${p.parentId}" not found`);
|
|
304
353
|
}
|
|
305
|
-
parsedCreateNodes.set(i, res.data);
|
|
354
|
+
parsedCreateNodes.set(i, p.parentId ? { ...res.data, parentId: p.parentId } : res.data);
|
|
306
355
|
simAvailable.add(res.data.id);
|
|
307
356
|
}
|
|
308
357
|
else if (p.op === 'update') {
|
|
@@ -334,47 +383,75 @@ export class SceneBridge {
|
|
|
334
383
|
throw new Error(`invalid patch: patches[${i}] unknown op`);
|
|
335
384
|
}
|
|
336
385
|
}
|
|
337
|
-
//
|
|
338
|
-
//
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
386
|
+
// Derived construction is refused BEFORE anything is applied, so a patch
|
|
387
|
+
// that tries to author a floor plate or an auto ceiling leaves the scene
|
|
388
|
+
// untouched like any other invalid patch.
|
|
389
|
+
assertDerivedNodeWrites(nodes, {
|
|
390
|
+
create: [...parsedCreateNodes.values()].map((node) => ({ node })),
|
|
391
|
+
update: patches.flatMap((patch) => patch.op === 'update' ? [{ id: patch.id, data: patch.data }] : []),
|
|
392
|
+
});
|
|
393
|
+
const createOps = new Map();
|
|
394
|
+
const updateOps = new Map();
|
|
395
|
+
const deleteIds = new Set();
|
|
396
|
+
const nextNodes = { ...nodes };
|
|
397
|
+
for (const [index, patch] of patches.entries()) {
|
|
398
|
+
if (patch.op === 'create') {
|
|
399
|
+
const parsed = parsedCreateNodes.get(index);
|
|
400
|
+
const node = { ...parsed, parentId: patch.parentId ?? parsed.parentId };
|
|
401
|
+
createOps.set(node.id, { node, parentId: patch.parentId });
|
|
402
|
+
deleteIds.delete(node.id);
|
|
403
|
+
nextNodes[node.id] = node;
|
|
350
404
|
}
|
|
351
|
-
if (
|
|
352
|
-
|
|
353
|
-
|
|
405
|
+
else if (patch.op === 'update') {
|
|
406
|
+
nextNodes[patch.id] = { ...nextNodes[patch.id], ...patch.data };
|
|
407
|
+
const created = createOps.get(patch.id);
|
|
408
|
+
if (created) {
|
|
409
|
+
created.node = nextNodes[patch.id];
|
|
410
|
+
if (patch.data.parentId !== undefined)
|
|
411
|
+
created.parentId = patch.data.parentId;
|
|
412
|
+
}
|
|
413
|
+
else
|
|
414
|
+
updateOps.set(patch.id, { ...updateOps.get(patch.id), ...patch.data });
|
|
354
415
|
}
|
|
355
|
-
if (
|
|
356
|
-
|
|
357
|
-
|
|
416
|
+
else if (patch.op === 'delete') {
|
|
417
|
+
deleteIds.add(patch.id);
|
|
418
|
+
createOps.delete(patch.id);
|
|
419
|
+
updateOps.delete(patch.id);
|
|
420
|
+
delete nextNodes[patch.id];
|
|
358
421
|
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
422
|
+
}
|
|
423
|
+
// Plan the entire deletion selection before applying anything. Interleaved
|
|
424
|
+
// update operations must not turn an all-boundaries deletion into balconies.
|
|
425
|
+
const planned = structureChangeBatch(planWallDeletion(nodes, {
|
|
426
|
+
nodeIds: [...deleteIds],
|
|
427
|
+
nextNodes,
|
|
428
|
+
mintId: generateId,
|
|
429
|
+
}).changes);
|
|
430
|
+
for (const { id, data } of planned.update)
|
|
431
|
+
updateOps.set(id, { ...updateOps.get(id), ...data });
|
|
432
|
+
const wallLevels = [
|
|
433
|
+
...new Set(planned.delete.flatMap((id) => {
|
|
434
|
+
const node = nodes[id];
|
|
435
|
+
return node?.type === 'wall' && node.parentId ? [node.parentId] : [];
|
|
436
|
+
})),
|
|
437
|
+
];
|
|
438
|
+
runAsSingleSceneHistoryStep(useScene, () => {
|
|
439
|
+
pauseSpaceDetection();
|
|
440
|
+
try {
|
|
441
|
+
for (const material of materialUpserts.values())
|
|
442
|
+
useScene.getState().addSceneMaterial(material);
|
|
443
|
+
useScene.getState().createNodes([...createOps.values(), ...planned.create]);
|
|
444
|
+
useScene.getState().updateNodes([...updateOps].map(([id, data]) => ({ id, data })));
|
|
445
|
+
useScene.getState().deleteNodes(planned.delete);
|
|
371
446
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
deleteIds.push(p.id);
|
|
447
|
+
finally {
|
|
448
|
+
resumeSpaceDetection();
|
|
375
449
|
}
|
|
376
|
-
|
|
377
|
-
|
|
450
|
+
if (wallLevels.length)
|
|
451
|
+
this.deriveStructure(wallLevels);
|
|
452
|
+
this.reconcileDependentStructure(nodes);
|
|
453
|
+
});
|
|
454
|
+
const createdIds = Object.keys(useScene.getState().nodes).filter((id) => !nodes[id]);
|
|
378
455
|
// Compute actual deleted ids by diffing pre/post snapshots.
|
|
379
456
|
const postNodes = useScene.getState().nodes;
|
|
380
457
|
const deletedIds = [];
|
|
@@ -388,6 +465,40 @@ export class SceneBridge {
|
|
|
388
465
|
createdIds,
|
|
389
466
|
};
|
|
390
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* Preview what deleting `ids` does to `scene` with this bridge's semantics
|
|
470
|
+
* (core's store planner), without committing or minting default gutters.
|
|
471
|
+
* The apply_patch guard runs its dry run through this.
|
|
472
|
+
*/
|
|
473
|
+
planDeletion(scene, ids) {
|
|
474
|
+
return planNodeDeletion(scene, ids, { mintDefaults: false });
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Derive this scene's construction the way the browser's commit subscriber
|
|
478
|
+
* does: one pass of the shared kernel writes the room zones, the auto
|
|
479
|
+
* ceilings, the wall side classification and the floor plates.
|
|
480
|
+
*
|
|
481
|
+
* The headless bridge has no space-detection subscriber, so the semantic
|
|
482
|
+
* tools that state room intent call this to materialise the construction
|
|
483
|
+
* they report. Writes are history-paused, so undo returns to the state
|
|
484
|
+
* before the triggering tool call rather than stranding derived nodes.
|
|
485
|
+
*/
|
|
486
|
+
deriveStructure(levelIds) {
|
|
487
|
+
pauseSceneHistory(useScene);
|
|
488
|
+
try {
|
|
489
|
+
const patches = applyStructureReconciliation(useScene, {
|
|
490
|
+
levelIds,
|
|
491
|
+
mintId: (kind) => generateId(kind),
|
|
492
|
+
});
|
|
493
|
+
return {
|
|
494
|
+
createdIds: patches.flatMap((patch) => patch.op === 'create' ? [patch.node.id] : []),
|
|
495
|
+
deletedIds: patches.flatMap((patch) => (patch.op === 'delete' ? [patch.id] : [])),
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
finally {
|
|
499
|
+
resumeSceneHistory(useScene);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
391
502
|
/** Undo. Returns the number of steps actually undone. */
|
|
392
503
|
undo(steps = 1) {
|
|
393
504
|
const before = useScene.temporal.getState().pastStates.length;
|
|
@@ -451,6 +562,82 @@ export class SceneBridge {
|
|
|
451
562
|
clearHistory() {
|
|
452
563
|
useScene.temporal.getState().clear();
|
|
453
564
|
}
|
|
565
|
+
runAsSingleHistoryStep(run) {
|
|
566
|
+
return runAsSingleSceneHistoryStep(useScene, run);
|
|
567
|
+
}
|
|
568
|
+
reconcileDependentStructure(before) {
|
|
569
|
+
const currentNodes = this.getNodes();
|
|
570
|
+
const authoredInputChanged = [
|
|
571
|
+
...new Set([...Object.keys(before), ...Object.keys(currentNodes)]),
|
|
572
|
+
].some((id) => {
|
|
573
|
+
if (before[id] === currentNodes[id])
|
|
574
|
+
return false;
|
|
575
|
+
const type = (currentNodes[id] ?? before[id])?.type;
|
|
576
|
+
return (type === 'stair' ||
|
|
577
|
+
type === 'stair-segment' ||
|
|
578
|
+
type === 'elevator' ||
|
|
579
|
+
type === 'level' ||
|
|
580
|
+
type === 'building' ||
|
|
581
|
+
type === 'slab' ||
|
|
582
|
+
type === 'ceiling');
|
|
583
|
+
});
|
|
584
|
+
if (authoredInputChanged) {
|
|
585
|
+
const ownerIds = changedStairOpeningOwners(before, currentNodes);
|
|
586
|
+
for (const node of [...Object.values(before), ...Object.values(currentNodes)]) {
|
|
587
|
+
if (node.type === 'elevator' &&
|
|
588
|
+
(before[node.id] !== currentNodes[node.id] ||
|
|
589
|
+
Object.values(currentNodes).some((level) => level.type === 'level' && before[level.id] !== level)))
|
|
590
|
+
ownerIds.add(node.id);
|
|
591
|
+
}
|
|
592
|
+
const owned = planOwnedFloorOpenings(currentNodes, { ownerIds });
|
|
593
|
+
if (owned.length) {
|
|
594
|
+
pauseSpaceDetection();
|
|
595
|
+
try {
|
|
596
|
+
useScene.getState().applyNodeChanges(structureChangeBatch(owned));
|
|
597
|
+
}
|
|
598
|
+
finally {
|
|
599
|
+
resumeSpaceDetection();
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
const current = this.getNodes();
|
|
604
|
+
const levels = new Set();
|
|
605
|
+
for (const id of new Set([...Object.keys(before), ...Object.keys(current)])) {
|
|
606
|
+
if (before[id] === current[id])
|
|
607
|
+
continue;
|
|
608
|
+
for (const nodes of [before, current]) {
|
|
609
|
+
const level = stairDeckLevelId(nodes, nodes[id]);
|
|
610
|
+
if (level)
|
|
611
|
+
levels.add(level);
|
|
612
|
+
const node = nodes[id];
|
|
613
|
+
if (node?.type === 'level') {
|
|
614
|
+
levels.add(node.id);
|
|
615
|
+
const lower = adjacentLevelId(nodes, node.id, -1);
|
|
616
|
+
if (lower)
|
|
617
|
+
levels.add(lower);
|
|
618
|
+
}
|
|
619
|
+
if (node?.type === 'stair')
|
|
620
|
+
for (const opening of Object.values(nodes))
|
|
621
|
+
if (opening.type === 'floor-opening' && opening.ownerId === node.id)
|
|
622
|
+
for (const target of floorOpeningTargets(nodes, opening))
|
|
623
|
+
levels.add(target.levelId);
|
|
624
|
+
if (node?.type === 'floor-opening')
|
|
625
|
+
for (const target of floorOpeningTargets(nodes, node))
|
|
626
|
+
levels.add(target.levelId);
|
|
627
|
+
if (node?.type === 'door' || node?.type === 'window') {
|
|
628
|
+
const wall = nodes[node.parentId];
|
|
629
|
+
if (wall?.type === 'wall' && wall.parentId)
|
|
630
|
+
levels.add(wall.parentId);
|
|
631
|
+
}
|
|
632
|
+
else if ((node?.type === 'zone' || node?.type === 'slab' || node?.type === 'wall') &&
|
|
633
|
+
node.parentId) {
|
|
634
|
+
levels.add(node.parentId);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
if (levels.size)
|
|
639
|
+
this.deriveStructure([...levels]);
|
|
640
|
+
}
|
|
454
641
|
// ---- internal helpers ----
|
|
455
642
|
/**
|
|
456
643
|
* Return the node whose `children` array (string or object form) contains
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export { SceneBridge } from './bridge/scene-bridge.js';
|
|
2
2
|
export { createSceneOperations, type SceneOperations } from './operations/index.js';
|
|
3
3
|
export { type CreatePascalMcpServerOptions, createPascalMcpServer } from './server.js';
|
|
4
|
+
export type { GeometryScriptHost, ScriptedKind } from './tools/add-object.js';
|
|
5
|
+
export { type AssetCatalog, builtInCatalog } from './tools/asset-catalog.js';
|
|
6
|
+
export type { SceneViewCapture, SceneViewHost } from './tools/view-scene.js';
|
|
4
7
|
export { version } from './version.js';
|
|
5
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,qBAAqB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA;AAC1E,OAAO,EAAE,KAAK,4BAA4B,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AACnF,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,qBAAqB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA;AAC1E,OAAO,EAAE,KAAK,4BAA4B,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AACnF,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAC1E,OAAO,EAAE,KAAK,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACzE,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACzE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { SceneBridge } from './bridge/scene-bridge.js';
|
|
2
2
|
export { createSceneOperations } from './operations/index.js';
|
|
3
3
|
export { createPascalMcpServer } from './server.js';
|
|
4
|
+
export { builtInCatalog } from './tools/asset-catalog.js';
|
|
4
5
|
export { version } from './version.js';
|
package/dist/lib/safe-fetch.d.ts
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
import type { LookupAddress } from 'node:dns';
|
|
2
|
+
import { type IncomingMessage } from 'node:http';
|
|
3
|
+
import { type LookupFunction } from 'node:net';
|
|
4
|
+
/** True for any address a server-side fetch must not reach. Unparseable input is blocked. */
|
|
5
|
+
export declare function isBlockedAddress(address: string): boolean;
|
|
6
|
+
export type Resolver = (hostname: string) => Promise<LookupAddress[]>;
|
|
7
|
+
/** A socket `lookup` that only ever answers with the given, already-checked addresses. */
|
|
8
|
+
export declare function pinnedLookup(addresses: LookupAddress[]): LookupFunction;
|
|
9
|
+
export type PinnedRequestInit = {
|
|
10
|
+
headers: Record<string, string>;
|
|
11
|
+
lookup: LookupFunction;
|
|
12
|
+
signal: AbortSignal;
|
|
13
|
+
};
|
|
14
|
+
export type Requester = (url: URL, init: PinnedRequestInit) => Promise<IncomingMessage>;
|
|
15
|
+
export type SafeFetchDeps = {
|
|
16
|
+
resolve: Resolver;
|
|
17
|
+
request: Requester;
|
|
18
|
+
};
|
|
1
19
|
export type SafeFetchOptions = {
|
|
2
20
|
maxBytes?: number;
|
|
3
21
|
timeoutMs?: number;
|
|
@@ -11,9 +29,9 @@ export type SafeFetchResult = {
|
|
|
11
29
|
hops: string[];
|
|
12
30
|
};
|
|
13
31
|
/**
|
|
14
|
-
* SSRF-safe fetch that follows redirects manually,
|
|
15
|
-
*
|
|
16
|
-
* URLs, non-2xx responses, oversize bodies, or timeouts.
|
|
32
|
+
* SSRF-safe fetch that follows redirects manually, re-resolving and rechecking
|
|
33
|
+
* the host on every hop and connecting only to the checked addresses. Throws
|
|
34
|
+
* `ProtocolError` for blocked URLs, non-2xx responses, oversize bodies, or timeouts.
|
|
17
35
|
*/
|
|
18
|
-
export declare function safeFetch(urlStr: string, opts?: SafeFetchOptions): Promise<SafeFetchResult>;
|
|
36
|
+
export declare function safeFetch(urlStr: string, opts?: SafeFetchOptions, deps?: SafeFetchDeps): Promise<SafeFetchResult>;
|
|
19
37
|
//# sourceMappingURL=safe-fetch.d.ts.map
|