@pascal-app/mcp 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +31 -10
  2. package/dist/bin/pascal-mcp.js +2 -0
  3. package/dist/bridge/agent-session.d.ts +2 -1
  4. package/dist/bridge/agent-session.d.ts.map +1 -1
  5. package/dist/bridge/agent-session.js +0 -1
  6. package/dist/bridge/scene-bridge.d.ts +21 -10
  7. package/dist/bridge/scene-bridge.d.ts.map +1 -1
  8. package/dist/bridge/scene-bridge.js +178 -194
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/lib/safe-fetch.d.ts +6 -0
  12. package/dist/lib/safe-fetch.d.ts.map +1 -1
  13. package/dist/lib/safe-fetch.js +135 -41
  14. package/dist/operations/scene-operations.d.ts +7 -0
  15. package/dist/operations/scene-operations.d.ts.map +1 -1
  16. package/dist/operations/scene-operations.js +4 -0
  17. package/dist/prompts/from-brief.js +1 -1
  18. package/dist/prompts/iterate-on-feedback.js +1 -1
  19. package/dist/prompts/renovation-from-photos.js +1 -1
  20. package/dist/prompts/scene-guidance.js +2 -2
  21. package/dist/resources/agent-guide.d.ts.map +1 -1
  22. package/dist/resources/agent-guide.js +8 -3
  23. package/dist/resources/catalog-items.d.ts +4 -1
  24. package/dist/resources/catalog-items.d.ts.map +1 -1
  25. package/dist/resources/catalog-items.js +14 -6
  26. package/dist/server.d.ts +9 -5
  27. package/dist/server.d.ts.map +1 -1
  28. package/dist/server.js +21 -5
  29. package/dist/templates/empty-studio.d.ts.map +1 -1
  30. package/dist/templates/empty-studio.js +2 -3
  31. package/dist/templates/garden-house.d.ts.map +1 -1
  32. package/dist/templates/garden-house.js +2 -3
  33. package/dist/templates/two-bedroom.d.ts.map +1 -1
  34. package/dist/templates/two-bedroom.js +2 -3
  35. package/dist/tools/add-column.d.ts.map +1 -1
  36. package/dist/tools/add-column.js +9 -6
  37. package/dist/tools/add-object.d.ts +44 -5
  38. package/dist/tools/add-object.d.ts.map +1 -1
  39. package/dist/tools/add-object.js +75 -21
  40. package/dist/tools/asset-catalog.d.ts +5 -6
  41. package/dist/tools/asset-catalog.d.ts.map +1 -1
  42. package/dist/tools/asset-catalog.js +1 -9
  43. package/dist/tools/author-scene.d.ts +8 -5
  44. package/dist/tools/author-scene.d.ts.map +1 -1
  45. package/dist/tools/author-scene.js +302 -51
  46. package/dist/tools/clear-scene.js +2 -2
  47. package/dist/tools/collections.d.ts.map +1 -1
  48. package/dist/tools/collections.js +8 -5
  49. package/dist/tools/honest-patch-guard.js +6 -5
  50. package/dist/tools/hosted-services.d.ts.map +1 -1
  51. package/dist/tools/hosted-services.js +2 -2
  52. package/dist/tools/index.d.ts +7 -2
  53. package/dist/tools/index.d.ts.map +1 -1
  54. package/dist/tools/index.js +3 -9
  55. package/dist/tools/local-geometry-scripts.d.ts.map +1 -1
  56. package/dist/tools/local-geometry-scripts.js +7 -2
  57. package/dist/tools/patch-guards.d.ts.map +1 -1
  58. package/dist/tools/patch-guards.js +24 -14
  59. package/dist/tools/project-files.d.ts.map +1 -1
  60. package/dist/tools/project-files.js +2 -2
  61. package/dist/tools/room-tools.d.ts +1 -0
  62. package/dist/tools/room-tools.d.ts.map +1 -1
  63. package/dist/tools/room-tools.js +11 -8
  64. package/dist/tools/scene-scope.d.ts +4 -0
  65. package/dist/tools/scene-scope.d.ts.map +1 -0
  66. package/dist/tools/scene-scope.js +4 -0
  67. package/dist/tools/shared-tools.d.ts +0 -2
  68. package/dist/tools/shared-tools.d.ts.map +1 -1
  69. package/dist/tools/shared-tools.js +146 -31
  70. package/dist/tools/templates/create-house-from-brief.js +1 -1
  71. package/dist/tools/view-scene.js +2 -2
  72. package/examples/coordinate-conventions-demo.md +1 -0
  73. package/examples/photo-to-scene.md +1 -0
  74. package/examples/renovate-from-photos.md +2 -0
  75. package/package.json +10 -10
  76. package/dist/tools/create-unit.d.ts +0 -29
  77. package/dist/tools/create-unit.d.ts.map +0 -1
  78. package/dist/tools/create-unit.js +0 -36
  79. package/dist/tools/list-units.d.ts +0 -38
  80. package/dist/tools/list-units.d.ts.map +0 -1
  81. package/dist/tools/list-units.js +0 -61
  82. package/dist/tools/set-unit-members.d.ts +0 -22
  83. package/dist/tools/set-unit-members.d.ts.map +0 -1
  84. package/dist/tools/set-unit-members.js +0 -43
  85. package/dist/tools/unit-members.d.ts +0 -4
  86. package/dist/tools/unit-members.d.ts.map +0 -1
  87. package/dist/tools/unit-members.js +0 -24
@@ -1,13 +1,14 @@
1
1
  import { lookup as dnsLookup } from 'node:dns/promises';
2
- import { request as httpRequest } from 'node:http';
2
+ import { request as httpRequest, STATUS_CODES } from 'node:http';
3
3
  import { request as httpsRequest } from 'node:https';
4
4
  import { isIP } from 'node:net';
5
5
  import { ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/server';
6
+ import { ALLOWED_ORIGINS_ENV, allowedOriginsFrom } from '@pascal-app/core/schema';
6
7
  /**
7
8
  * SSRF-safe fetch for user-supplied URLs (image URLs in vision tools).
8
9
  *
9
10
  * Every hop (the initial URL and each redirect target) is checked the same way:
10
- * - only http(s) schemes
11
+ * - only http(s) schemes, on ports 80/443 unless the origin is allowlisted
11
12
  * - well-known local hostnames are refused outright
12
13
  * - the host is resolved once and *every* resolved address must be public;
13
14
  * IPv6 forms that embed an IPv4 address (mapped, compatible, 6to4) are
@@ -15,6 +16,8 @@ import { ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/server';
15
16
  * - the socket connects only to the addresses that were checked, so a DNS
16
17
  * answer that changes between check and connect cannot reach a private host
17
18
  * - optional origin allowlist via `PASCAL_ALLOWED_ASSET_ORIGINS` (comma-separated)
19
+ * - listed origins skip the port limit: an allowlisted origin may name any port
20
+ * - parsed by core's `allowedOriginsFrom`, the same reading as scene asset URLs
18
21
  * - max body size (default 20 MB) and request timeout (default 10 s)
19
22
  *
20
23
  * Used by photo_to_scene / analyze_floorplan_image / analyze_room_photo.
@@ -22,6 +25,8 @@ import { ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/server';
22
25
  const DEFAULT_MAX_BYTES = 20 * 1024 * 1024; // 20 MB
23
26
  const DEFAULT_TIMEOUT_MS = 10_000;
24
27
  const MAX_REDIRECTS = 3;
28
+ /** `URL.port` is '' when the URL uses its scheme's default port. */
29
+ const ALLOWED_PORTS = new Set(['', '80', '443']);
25
30
  function parseIPv4(addr) {
26
31
  const parts = addr.split('.');
27
32
  if (parts.length !== 4 || parts.some((p) => !/^\d{1,3}$/.test(p)))
@@ -64,7 +69,11 @@ function parseIPv6(addr) {
64
69
  return null;
65
70
  return [...head, ...new Array(8 - explicit).fill(0), ...rest, ...tail];
66
71
  }
67
- function isBlockedV4([a, b]) {
72
+ /**
73
+ * Ranges from the IANA IPv4 special-purpose registry that are not globally
74
+ * reachable, plus multicast. Globally reachable entries (AS112, AMT) stay allowed.
75
+ */
76
+ function isBlockedV4([a, b, c]) {
68
77
  if (a === 0)
69
78
  return true; // 0.0.0.0/8 "this network"
70
79
  if (a === 10)
@@ -77,12 +86,20 @@ function isBlockedV4([a, b]) {
77
86
  return true; // 169.254.0.0/16 link-local (cloud metadata)
78
87
  if (a === 172 && b >= 16 && b <= 31)
79
88
  return true; // 172.16.0.0/12 private
80
- if (a === 192 && b === 0)
89
+ if (a === 192 && b === 0 && c === 0)
81
90
  return true; // 192.0.0.0/24 IETF protocol assignments
91
+ if (a === 192 && b === 0 && c === 2)
92
+ return true; // 192.0.2.0/24 TEST-NET-1
93
+ if (a === 192 && b === 88 && c === 99)
94
+ return true; // 192.88.99.0/24 deprecated 6to4 relay anycast
82
95
  if (a === 192 && b === 168)
83
96
  return true; // 192.168.0.0/16 private
84
97
  if (a === 198 && (b === 18 || b === 19))
85
98
  return true; // 198.18.0.0/15 benchmarking
99
+ if (a === 198 && b === 51 && c === 100)
100
+ return true; // 198.51.100.0/24 TEST-NET-2
101
+ if (a === 203 && b === 0 && c === 113)
102
+ return true; // 203.0.113.0/24 TEST-NET-3
86
103
  if (a >= 224)
87
104
  return true; // multicast, reserved, broadcast
88
105
  return false;
@@ -100,12 +117,30 @@ function isBlockedV6(g) {
100
117
  }
101
118
  if (g0 === 0x64 && g1 === 0xff9b)
102
119
  return true; // 64:ff9b::/96 and 64:ff9b:1::/48 NAT64
103
- if (g0 === 0x100 && g1 === 0 && g2 === 0 && g3 === 0)
104
- return true; // 100::/64 discard
105
- if (g0 === 0x2001 && g1 === 0)
106
- return true; // 2001::/32 Teredo
120
+ if (g0 === 0x100 && g1 === 0 && g2 === 0 && (g3 === 0 || g3 === 1))
121
+ return true; // 100::/64 discard, 100:0:0:1::/64 dummy
122
+ // 2001::/23 IETF protocol assignments (incl. Teredo, benchmarking, ORCHID),
123
+ // except the entries IANA lists as globally reachable.
124
+ if (g0 === 0x2001 && g1 < 0x200) {
125
+ const anycast = g1 === 1 && g2 === 0 && g3 === 0 && g4 === 0 && g5 === 0 && g6 === 0;
126
+ if (anycast && g7 >= 1 && g7 <= 3)
127
+ return false; // 2001:1::1-3 PCP, TURN, DNS-SD SRP anycast
128
+ if (g1 === 3)
129
+ return false; // 2001:3::/32 AMT
130
+ if (g1 === 4 && g2 === 0x112)
131
+ return false; // 2001:4:112::/48 AS112-v6
132
+ if (g1 >= 0x20 && g1 <= 0x3f)
133
+ return false; // 2001:20::/28 ORCHIDv2, 2001:30::/28 drone remote ID
134
+ return true;
135
+ }
136
+ if (g0 === 0x2001 && g1 === 0xdb8)
137
+ return true; // 2001:db8::/32 documentation
107
138
  if (g0 === 0x2002)
108
139
  return isBlockedV4(embeddedV4(g1, g2)); // 2002::/16 6to4
140
+ if (g0 === 0x3fff && (g1 & 0xf000) === 0)
141
+ return true; // 3fff::/20 documentation
142
+ if (g0 === 0x5f00)
143
+ return true; // 5f00::/16 SRv6 SIDs
109
144
  if ((g0 & 0xfe00) === 0xfc00)
110
145
  return true; // fc00::/7 ULA (incl. fd00:ec2::254 metadata)
111
146
  if ((g0 & 0xffc0) === 0xfe80)
@@ -139,40 +174,82 @@ function isLocalHostname(hostname) {
139
174
  lower.endsWith('.internal') ||
140
175
  lower.endsWith('.corp'));
141
176
  }
142
- function blocked(reason, details, log = {}) {
143
- console.error(`[pascal-mcp] safe_fetch_blocked ${JSON.stringify({ reason, ...details, ...log })}`);
144
- return protocolError(reason, details);
177
+ const MAX_LOGGED_PATH = 128;
178
+ /**
179
+ * Scheme, host and a truncated path only: query strings can carry presigned
180
+ * tokens, userinfo carries credentials. Non-http(s) URLs (`data:`,
181
+ * `javascript:`) carry their payload in the path, so they log scheme and host.
182
+ */
183
+ export function loggableUrl(url) {
184
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
185
+ return url.host ? `${url.protocol}//${url.host}` : url.protocol;
186
+ }
187
+ const path = url.pathname.length > MAX_LOGGED_PATH
188
+ ? `${url.pathname.slice(0, MAX_LOGGED_PATH)}…`
189
+ : url.pathname;
190
+ return `${url.protocol}//${url.host}${path}`;
191
+ }
192
+ function blocked(reason, url, details = {}, log = {}) {
193
+ console.error(`[pascal-mcp] safe_fetch_blocked ${JSON.stringify({ reason, url: loggableUrl(url), ...details, ...log })}`);
194
+ return protocolError(reason, { url: loggableUrl(url), ...details });
145
195
  }
146
196
  function protocolError(reason, details) {
147
197
  throw new ProtocolError(ProtocolErrorCode.InvalidParams, reason, details);
148
198
  }
199
+ /** A system error's code (`ENOTFOUND`, `ECONNRESET`), never its message, which can quote the URL. */
200
+ function errorCode(err) {
201
+ const code = err?.code;
202
+ return typeof code === 'string' && /^[A-Z0-9_]{1,64}$/.test(code) ? code : 'unknown';
203
+ }
149
204
  function assertAllowedUrl(url) {
150
205
  let parsed;
151
206
  try {
152
207
  parsed = new URL(url);
153
208
  }
154
209
  catch {
155
- return protocolError('invalid_url', { url });
210
+ return protocolError('invalid_url', {});
156
211
  }
157
212
  if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
158
- blocked('url_scheme_not_allowed', { url, protocol: parsed.protocol });
213
+ blocked('url_scheme_not_allowed', parsed, { protocol: parsed.protocol });
159
214
  }
160
- const allowEnv = process.env.PASCAL_ALLOWED_ASSET_ORIGINS;
161
- if (allowEnv) {
162
- const origins = allowEnv
163
- .split(',')
164
- .map((s) => s.trim())
165
- .filter(Boolean);
166
- if (!origins.includes(parsed.origin)) {
167
- blocked('url_origin_not_allowlisted', { url, origin: parsed.origin });
215
+ // Node would send `user:pass@` as a Basic `Authorization` header.
216
+ if (parsed.username || parsed.password)
217
+ blocked('url_credentials_not_allowed', parsed);
218
+ const allowlist = allowedOriginsFrom(process.env[ALLOWED_ORIGINS_ENV]);
219
+ if (allowlist) {
220
+ if (!allowlist.has(parsed.origin)) {
221
+ blocked('url_origin_not_allowlisted', parsed, { origin: parsed.origin });
168
222
  }
169
223
  }
224
+ else if (!ALLOWED_PORTS.has(parsed.port)) {
225
+ // An operator-allowlisted origin may name any port; otherwise only web ports,
226
+ // so a public host cannot be used to probe its other services.
227
+ blocked('url_port_not_allowed', parsed, { port: parsed.port });
228
+ }
170
229
  return parsed;
171
230
  }
172
- /** Resolves `url`'s host and returns its addresses only if every one of them is public. */
173
- async function resolveCheckedAddresses(url, resolve) {
231
+ /** Settles with `promise`, or rejects with the abort reason as soon as `signal` aborts. */
232
+ function untilAborted(promise, signal) {
233
+ if (signal.aborted)
234
+ return Promise.reject(signal.reason);
235
+ return new Promise((resolve, reject) => {
236
+ const onAbort = () => reject(signal.reason);
237
+ signal.addEventListener('abort', onAbort, { once: true });
238
+ const settle = (done) => (arg) => {
239
+ signal.removeEventListener('abort', onAbort);
240
+ done(arg);
241
+ };
242
+ promise.then(settle(resolve), settle(reject));
243
+ });
244
+ }
245
+ /**
246
+ * Resolves `url`'s host and returns its addresses only if every one of them is
247
+ * public. The lookup races `signal`: `dns.lookup` cannot be cancelled, so a
248
+ * hung resolver must not hold the fetch past its timeout.
249
+ */
250
+ async function resolveCheckedAddresses(url, resolve, signal) {
174
251
  const host = url.hostname.replace(/^\[|\]$/g, '');
175
- const details = { url: url.toString(), hostname: url.hostname };
252
+ const details = { hostname: url.hostname };
176
253
  const literalFamily = isIP(host);
177
254
  let addresses;
178
255
  if (literalFamily) {
@@ -180,22 +257,25 @@ async function resolveCheckedAddresses(url, resolve) {
180
257
  }
181
258
  else {
182
259
  if (isLocalHostname(host))
183
- blocked('url_host_blocked', details);
260
+ blocked('url_host_blocked', url, details);
184
261
  try {
185
- addresses = await resolve(host);
262
+ addresses = await untilAborted(resolve(host), signal);
186
263
  }
187
264
  catch (err) {
265
+ if (signal.aborted)
266
+ throw err;
188
267
  return protocolError('dns_lookup_failed', {
268
+ url: loggableUrl(url),
189
269
  ...details,
190
- message: err instanceof Error ? err.message : String(err),
270
+ code: errorCode(err),
191
271
  });
192
272
  }
193
273
  }
194
274
  if (addresses.length === 0)
195
- protocolError('dns_lookup_failed', details);
275
+ protocolError('dns_lookup_failed', { url: loggableUrl(url), ...details, code: 'no_addresses' });
196
276
  const unsafe = addresses.find((a) => isBlockedAddress(a.address));
197
277
  if (unsafe)
198
- blocked('url_host_blocked', details, { address: unsafe.address });
278
+ blocked('url_host_blocked', url, details, { address: unsafe.address });
199
279
  return addresses;
200
280
  }
201
281
  /** A socket `lookup` that only ever answers with the given, already-checked addresses. */
@@ -247,40 +327,45 @@ export async function safeFetch(urlStr, opts = {}, deps = defaultDeps) {
247
327
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
248
328
  const hops = [];
249
329
  let current = urlStr;
330
+ let inFlight;
250
331
  const controller = new AbortController();
251
332
  const timer = setTimeout(() => controller.abort(), timeoutMs);
252
333
  try {
253
334
  for (let i = 0; i <= MAX_REDIRECTS; i++) {
254
335
  const parsed = assertAllowedUrl(current);
336
+ inFlight = parsed;
255
337
  hops.push(parsed.toString());
256
- const addresses = await resolveCheckedAddresses(parsed, deps.resolve);
338
+ const addresses = await resolveCheckedAddresses(parsed, deps.resolve, controller.signal);
257
339
  const res = await deps.request(parsed, {
258
340
  headers: opts.accept ? { Accept: opts.accept } : {},
259
341
  lookup: pinnedLookup(addresses),
260
342
  signal: controller.signal,
261
343
  });
262
344
  const status = res.statusCode ?? 0;
345
+ // Redirect and error bodies are never read: destroying the response
346
+ // (no keep-alive, `agent: false`) bounds them at zero bytes.
263
347
  if (status >= 300 && status < 400) {
264
- res.resume();
348
+ res.destroy();
265
349
  const location = header(res, 'location');
266
350
  if (!location) {
267
- protocolError('redirect_without_location', { url: parsed.toString(), status });
351
+ protocolError('redirect_without_location', { url: loggableUrl(parsed), status });
268
352
  }
269
353
  current = new URL(location, parsed).toString();
270
354
  continue;
271
355
  }
272
356
  if (status < 200 || status >= 300) {
273
- res.resume();
357
+ res.destroy();
274
358
  protocolError('fetch_failed', {
275
- url: parsed.toString(),
359
+ url: loggableUrl(parsed),
276
360
  status,
277
- statusText: res.statusMessage ?? '',
361
+ // The standard text, never the server's reason phrase, which can echo the URL.
362
+ statusText: STATUS_CODES[status] ?? '',
278
363
  });
279
364
  }
280
365
  const declared = Number(header(res, 'content-length') ?? Number.NaN);
281
366
  if (Number.isFinite(declared) && declared > maxBytes) {
282
367
  res.destroy();
283
- protocolError('response_too_large', { url: parsed.toString(), declared, maxBytes });
368
+ protocolError('response_too_large', { url: loggableUrl(parsed), declared, maxBytes });
284
369
  }
285
370
  // Running cap so servers that lie about length still get bounded.
286
371
  const chunks = [];
@@ -290,7 +375,11 @@ export async function safeFetch(urlStr, opts = {}, deps = defaultDeps) {
290
375
  total += buf.byteLength;
291
376
  if (total > maxBytes) {
292
377
  res.destroy();
293
- protocolError('response_too_large', { url: parsed.toString(), received: total, maxBytes });
378
+ protocolError('response_too_large', {
379
+ url: loggableUrl(parsed),
380
+ received: total,
381
+ maxBytes,
382
+ });
294
383
  }
295
384
  chunks.push(buf);
296
385
  }
@@ -301,17 +390,22 @@ export async function safeFetch(urlStr, opts = {}, deps = defaultDeps) {
301
390
  hops,
302
391
  };
303
392
  }
304
- return protocolError('too_many_redirects', { hops: hops.slice(0, MAX_REDIRECTS + 1) });
393
+ return protocolError('too_many_redirects', {
394
+ hops: hops.slice(0, MAX_REDIRECTS + 1).map((hop) => loggableUrl(new URL(hop))),
395
+ });
305
396
  }
306
397
  catch (err) {
307
398
  if (err instanceof ProtocolError)
308
399
  throw err;
309
400
  if (controller.signal.aborted || err.name === 'AbortError') {
310
- return protocolError('fetch_timeout', { url: urlStr, timeoutMs });
401
+ return protocolError('fetch_timeout', {
402
+ ...(inFlight ? { url: loggableUrl(inFlight) } : {}),
403
+ timeoutMs,
404
+ });
311
405
  }
312
406
  return protocolError('fetch_error', {
313
- url: urlStr,
314
- message: err instanceof Error ? err.message : String(err),
407
+ ...(inFlight ? { url: loggableUrl(inFlight) } : {}),
408
+ code: errorCode(err),
315
409
  });
316
410
  }
317
411
  finally {
@@ -91,6 +91,13 @@ export interface SceneOperations {
91
91
  };
92
92
  clearHistory(): void;
93
93
  runAsSingleHistoryStep<T>(run: () => T): T;
94
+ /**
95
+ * Runs `run` synchronously in the scene's scope: its ids, history environment and disposal guard
96
+ * (`SceneBridge` over a runtime). Optional: an implementation without it runs `run` as is, which
97
+ * is right for a scene with random ids. One that computes on a detached scene must provide it,
98
+ * or the ids its tools mint are not the scene's.
99
+ */
100
+ runInScene?<T>(run: () => T): T;
94
101
  createProject(options: ProjectCreateOptions): Promise<ProjectStatus>;
95
102
  getProjectStatus(id: string): Promise<ProjectStatus | null>;
96
103
  saveScene(options: SceneSaveOptions): Promise<SceneMeta>;
@@ -1 +1 @@
1
- {"version":3,"file":"scene-operations.d.ts","sourceRoot":"","sources":["../../src/operations/scene-operations.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scene-operations.d.ts","sourceRoot":"","sources":["../../src/operations/scene-operations.ts"],"names":[],"mappings":"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"}
@@ -155,6 +155,10 @@ class SceneOperationsFacade {
155
155
  runAsSingleHistoryStep(run) {
156
156
  return this.requireBridge().runAsSingleHistoryStep(run);
157
157
  }
158
+ runInScene(run) {
159
+ // Store-only operations have no scene to scope: their tools (list_scenes) run as they are.
160
+ return this.#bridge ? this.#bridge.runInScene(run) : run();
161
+ }
158
162
  async createProject(options) {
159
163
  const store = this.requireStore();
160
164
  if (!store.createProject) {
@@ -15,7 +15,7 @@ const PREAMBLE = [
15
15
  ' - Floor items live under a Level; wall/ceiling-attached items live under their target Wall or Ceiling. Do not place items directly under the Site node.',
16
16
  'Use realistic dimensions in meters. Keep wall thickness small (0.1–0.3 m) and ceiling height 2.4–3.0 m unless the brief dictates otherwise.',
17
17
  SCENE_DESIGN_GUIDANCE,
18
- 'Respond ONLY with tool calls. Do not produce verbose narrative or prose; keep any explanations in short tool-call arguments.',
18
+ 'When the work is done, report what `verify_scene` and `get_project_status` confirmed and the project `editorUrl`.',
19
19
  ].join('\n');
20
20
  /**
21
21
  * Build the user-facing prompt text for `from_brief`. Pure function for testability.
@@ -11,7 +11,7 @@ const PREAMBLE = [
11
11
  ' - Bundle related mutations into a single `apply_patch` call so they share one undo step.',
12
12
  ' - For multi-room changes, call verify_scene after the mutation and fix reported issues.',
13
13
  SCENE_DESIGN_GUIDANCE,
14
- ' - Respond ONLY with tool calls. No prose.',
14
+ " - When the work is done, report what you checked (`verify_scene` reports the scene's issues) and the project `editorUrl`, which `get_project_status` returns.",
15
15
  ].join('\n');
16
16
  /**
17
17
  * Build the user-facing prompt text for `iterate_on_feedback`.
@@ -10,7 +10,7 @@ const PREAMBLE = [
10
10
  'Rules:',
11
11
  ' - Do not invent dimensions. Pull them from the analysis tool results.',
12
12
  ' - Do not modify nodes that are unrelated to the goals.',
13
- ' - Respond ONLY with tool calls. No prose.',
13
+ " - When the work is done, report what you checked (`verify_scene` reports the scene's issues) and the project `editorUrl`, which `get_project_status` returns.",
14
14
  ].join('\n');
15
15
  function isDataUrl(s) {
16
16
  return s.startsWith('data:');
@@ -3,14 +3,14 @@ export const SCENE_DESIGN_GUIDANCE = [
3
3
  ' - Use meters. X/Z are horizontal floor-plan axes; Y is vertical.',
4
4
  ' - Read `pascal://agent/guide` when you need construction rules; do not inspect repository code for ordinary scene editing.',
5
5
  ' - Door default: 0.9m wide by 2.1m high, floor-mounted.',
6
- ' - Window default: 1.5m wide by 1.5m high with a 0.9m sill height.',
6
+ ' - Window default: 1.5m wide by 1.5m high, centred vertically on its wall unless sillHeight is given.',
7
7
  ' - For clear concrete requests, act instead of asking for clarification.',
8
8
  ' - Defaults are for technical values (thicknesses, clearances, heights).',
9
9
  ' - What a request states (sizes, counts, rooms, style, references, "catalog only", a single room) binds the work exactly: a request for one bedroom is one bedroom.',
10
10
  ' - What it leaves open is design left to the agent: the size and number of storeys, the massing and roofscape, the site and outdoor spaces, the furniture and decor, and the floor, wall and ceiling finishes and materials. An open brief is room for a building with presence (composed volumes, interesting wings, roofs at different heights, good proportions, a considered site, rooms furnished and finished with intent) that stays credible and buildable, rather than a standard catalogue plan.',
11
11
  ' - When a choice would change the project, the person can be asked.',
12
12
  " - The sources to build a scene from stand side by side: the catalog (search_assets, place_items, furnish_room), procedural designs (place_design, validate_design), authored objects of the agent's own (add_object), native materials (paint). The catalog holds a limited set that varies by server: what a search returns is what exists.",
13
- ' - design_brief records the request read axis by axis (action, size, pieces, openings, style, materials, source, dressing: each bounded by the person’s own words or open with a design statement) and where the objects come from; place_items, furnish_room, place_in_room, add_object and place_design follow it, run_batch included (search_assets and view_assets do not). Source open opens the made pieces and the catalog pieces named in asIs; catalog, brand and existing open the catalog.',
13
+ ' - design_brief records the request read axis by axis (action, size, form, facade, pieces, openings, style, materials, source, dressing: each bounded by the person’s own words or open with a design statement) and where the objects come from; place_items, furnish_room, place_in_room, add_object and place_design follow it, run_batch included (search_assets and view_assets do not). Source open opens the made pieces and places technical equipment and plants from the catalog; catalog, brand and existing open the catalog.',
14
14
  ' - Naming a piece (a double bed) states its function; its form, size within reason and finish are design unless the request fixes them.',
15
15
  ' - For full homes/apartments, include realistic support spaces: kitchen, living/dining, bathrooms, hallway/entry, storage/laundry where appropriate.',
16
16
  ' - For multi-story buildings, create separate level-owned story shells. Do not stretch first-floor exterior walls to cover upper floors.',
@@ -1 +1 @@
1
- {"version":3,"file":"agent-guide.d.ts","sourceRoot":"","sources":["../../src/resources/agent-guide.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAEpD,eAAO,MAAM,WAAW,QAwFZ,CAAA;AAEZ,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI,CAwCpF"}
1
+ {"version":3,"file":"agent-guide.d.ts","sourceRoot":"","sources":["../../src/resources/agent-guide.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAEpD,eAAO,MAAM,WAAW,QA6FZ,CAAA;AAEZ,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI,CAwCpF"}
@@ -8,7 +8,7 @@ export const AGENT_GUIDE = [
8
8
  '## Projects, Saving and Output',
9
9
  '',
10
10
  '- `create_project` makes a browser-visible project and binds it to the session; `save_scene` without an id saves to the bound project. `get_project_status` is the authoritative status of a project: `editorUrl`, `browserVisibleVersion`, `latestVersion`, `publishedVersion`, `nodeCount` and `graphHash`.',
11
- '- `create_house_from_brief` chooses a starter house from a brief, creates, saves and publishes it, and returns the editor URL. `create_story_shell`, `create_room`, `add_door`, `add_window`, `furnish_room`, `create_roof` and `place_items` each make one part of a building with the numbers the call gives.',
11
+ '- `create_house_from_brief` replaces the scene with a starter template picked from its bedroom count, rooms and landscaping (not from the brief’s words), saves it as an unpublished draft and returns the editor URL. `create_story_shell`, `create_room`, `add_door`, `add_window`, `furnish_room`, `create_roof` and `place_items` each make one part of a building with the numbers the call gives.',
12
12
  '- Semantic tools update the browser-visible draft as they run. `save_scene` with `saveMode: "draft"` saves the draft, autosave-style; `saveMode: "checkpoint"` records a version, a meaningful milestone.',
13
13
  '- `verify_scene` reports a scene’s issues (`hasIssues`) and inventory; `get_project_status.nodeCount` is above 0 for a non-empty design.',
14
14
  '- `editorUrl` is the link that opens the project in the browser editor, and the one the person is handed: it comes from tool output, and a route inferred from the project id is not a substitute.',
@@ -36,6 +36,10 @@ export const AGENT_GUIDE = [
36
36
  '## Scene Facts and Tools',
37
37
  '',
38
38
  '- `get_site` gives the lot, the buildable area inside its setbacks with its centre and its frontage and depth on the street, the footprint a house usually covers, the ground and whether the lot is only the new-project placeholder, in the level coordinates you draw in. `place_building` takes the outline of the plan about to be drawn, in the coordinates it will be drawn in (and the yards a site plan gives), so the house rises inside the lot, centred. `create_room` and `add_wall` take your coordinates as given and say when they cross the lot line; `verify_scene` reports a house past it.',
39
+ '- `get_walls.wallTypes` lists assembly preset and project type ids and labels. `add_wall.type` copies the assembly and its total thickness; `set_wall_type({ wallIds, type })` changes existing standard walls (null clears the assembly, keeping thickness). Unknown ids refuse `unknown_wall_type`. Each wall reads its known type as `{ id, label }`, or null.',
40
+ '- `add_column` with role post makes a framing post: postSize 4x4, 6x6 (default), or 8x8; postBase none (default), post-base (steel), or pier (concrete). Height in metres defaults to the beam above or ceiling plane, as the Post tile does.',
41
+ '- `add_wall.wallType` is standard (default) or railing. A railing defaults to the Build panel post-and-rail guard, height 0.9144 m, with style-specific members. railing accepts style, postSpacing, postSize (sets thickness), infillSize, infillGap, bottomClearance (metres) and cap. It takes no assembly or foundation. Windows refuse railing_window; skirting refuses railing_wall; doors are gaps. `get_walls` reports wallType.',
42
+ '- `get_zones` reports roomType room (when absent), porch, deck or patio; floor.drop in metres (0 when absent; explicit floor.elevation wins); porchFraming auto, postSize, postSpacing in metres, beamSize, postCount and beamCount of generated parts still present (null with no outdoor/framing intent); and decking boardSize, gap in metres, direction, including deck defaults. Covered porches frame by default; decks/patios only with auto true. auto false leaves framing to hand edits. `set_zone_intent.patch` uses the same names and units.',
39
43
  '- A semantic tool performs the editor’s own operation for what it makes; `apply_patch` writes graph nodes as given, so it carries none of that.',
40
44
  "- A call whose arguments a tool's schema rejects is refused with the code `invalid_input` before anything runs and its message names the arguments. A level you write without a `height` is given the height the editor would derive from what it holds (an empty level 2.5 m), and `apply_patch` says so in `notes`.",
41
45
  '- `apply_patch` takes create/update/delete ops in one call, applied in order so later ops can reference ids created earlier. A single call is atomic (all or nothing).',
@@ -50,6 +54,7 @@ export const AGENT_GUIDE = [
50
54
  '- Adjacent rooms can share one wall (or have openings that line up). A door keeps about 0.65 m clear on both sides; furniture footprints do not overlap.',
51
55
  '- A complete home is an exterior shell, interior rooms, openings, a roof, furniture and landscaping.',
52
56
  '- On doors and windows, `t` or `position` runs from 0 to 1 along the wall unless a tool explicitly says otherwise.',
57
+ '- `move_node` moves one node re-seated on its host, as the editor’s move tool does: a door, window or wall item slides along its wall (plan target projected onto the wall, clamped, overlap refused unless force), a ceiling item in plan under its ceiling, a floor node in level plan, a roof piece in its segment-local plan, a wall by translation only. `duplicate_node` copies nodes with their hosted children onto fresh ids, on the same hosts or another level for level-parented roots.',
53
58
  '- X/Z are floor-plan axes and Y is vertical; dimensions are meters.',
54
59
  "- `create_roof` roofs the storeys and exterior walls standing when it runs. A building's roof goes on its roof level, the level directly above the top storey (named Roof, made when there is none), so exploded and solo level views lift it off the plan; a roof level is not an occupied story. `add_level` puts a new storey under it. A storey takes one roof: `roof_exists` names the one there. A project built before the roof level holds its roof on the storey (`verify_scene`'s `roof_mixed_in_storey`): `move_roofs_to_roof_level` moves it there as it stands, without a delete and rebuild.",
55
60
  '- Glazed roof construction supports gable, straight shed and barrel roofs. For barrel, depth is the span and barrelRise is the arch rise, at most half the span. glazing configures frame, glass, grids and panels. Paint glazing-frame, glazing-glass or glazing-solid; without a role, frame and solid panels are painted while glass keeps its finish.',
@@ -62,7 +67,7 @@ export const AGENT_GUIDE = [
62
67
  '',
63
68
  '## Looking and Starting Over',
64
69
  '',
65
- "- `view_scene` renders what was built, every storey and the roof, from a viewpoint you pick (a compass side, a side as a front photo names it, from the street the site records or else the house's own front, a height, a street-level eye, an orthographic face, a photo's own camera, in the photo's light). It is a picture to compare against a reference, not a measurement. `interior: { zoneId }` stands inside a room at eye height, a step in from a corner and looking across it. `interior: { levelId }` shows the rooms of one floor: the other floors, its roof and its ceilings are left out of the picture and everything on it, walls, openings and furniture alike, is cut at 0.65 of the storey's height. On a hosted server the person's open editor tab renders it, and a refusal says what is wrong: `editor_tab_required` (no tab has the project open: it carries the editor link, and the person's \"Agents may open the editor for me\" setting decides what you may do about it, in the refusal's own words), `editor_tab_hidden` (a tab is in the background: ask the person to bring it to the front, then call again) and `editor_tab_busy` (a tab is open but slow or still loading: it carries no link; wait a few seconds and call again, and tell the person only if it is still busy after about a minute; never ask them to open anything).",
70
+ "- `view_scene` renders what was built, every storey and the roof, from a viewpoint you pick (a compass side, a side as a front photo names it, from the street the site records or else the house's own front, a height, a street-level eye, an orthographic face, a photo's own camera, in the photo's light). It is a picture to compare against a reference, not a measurement. `interior: { zoneId }` stands inside a room at eye height, a step in from a corner and looking across it. `interior: { levelId }` shows the rooms of one floor: the other floors, its roof and its ceilings are left out of the picture and everything on it, walls, openings and furniture alike, is cut at 0.65 of the storey's height. The server has no renderer of its own: the host renders the view, through an editor tab open on the project or a renderer of its own. When nothing can render it, the call is refused with its reason (`view_unavailable` on a server with no rendering; a hosted server may answer with its own codes, such as `editor_tab_required`, `editor_tab_hidden` or `editor_tab_busy`, each saying what to do next): follow the refusal's own words, and take sizes and counts from the tools.",
66
71
  '- `clear_scene` empties the project on purpose, for when the person asked to start over, keeping the site, the building and one level. Any other write that would leave the project empty is refused as `scene_wipe_blocked`, and nothing changes.',
67
72
  '',
68
73
  '## Custom Designs',
@@ -81,7 +86,7 @@ export const AGENT_GUIDE = [
81
86
  '',
82
87
  '- What a request states (sizes, counts, rooms, style, references, "catalog only", a single room) binds the work exactly: a request for one bedroom is one bedroom. What it leaves open is design left to the agent: the size and number of storeys, the massing and roofscape, the site and outdoor spaces, the furniture and decor, and the floor, wall and ceiling finishes and materials. Defaults are for technical values (thicknesses, clearances, heights). An open brief is room for a building with presence (composed volumes, interesting wings, roofs at different heights, good proportions, a considered site, rooms furnished and finished with intent) that stays credible and buildable, rather than a standard catalogue plan. When a choice would change the project, the person can be asked.',
83
88
  '- The sources to build a scene from stand side by side: the catalog (`search_assets`, `place_items`, `furnish_room`), procedural designs (`place_design`, `validate_design`), authored objects of the agent’s own (`add_object`), native materials (`paint`). The catalog holds a limited set that varies by server: what a search returns is what exists.',
84
- '- Naming a piece (a double bed) states its function; its form, size within reason and finish are design unless the request fixes them. `design_brief` records the request as a brief, read axis by axis (action, size, pieces, openings, style, materials, source, dressing: each bounded by the person’s own words or open with a design statement; the source is `open`, `catalog`, `brand` or `existing`) and furnishings are placed or made after it, inside `run_batch` too: `search_assets` and `view_assets` read the catalog at any time. Source `open` opens `add_object` and `place_design`, and places unchanged technical equipment only when the brief names it in `asIs` with a reason after `view_assets` showed it in this session; `furnish_room` stays closed. The other sources open `place_items`, `furnish_room` and `place_in_room`, with creation for what the catalog lacks. A non-open source quotes the request in `sourceQuote`. `beyond` records eight dressing layers; an open `dressing` axis names pieces in at least six, and the result returns them as `dressing`. The brief is the session’s: a scene load starts another, and the next `design_brief` replaces it.',
89
+ '- Naming a piece (a double bed) states its function; its form, size within reason and finish are design unless the request fixes them. `design_brief` records the request as a brief, read axis by axis (action, size, form, facade, pieces, openings, style, materials, source, dressing: each bounded by the person’s own words or open with a design statement; the source is `open`, `catalog`, `brand` or `existing`) and furnishings are placed or made after it, inside `run_batch` too: `search_assets` and `view_assets` read the catalog at any time. Source `open` opens `add_object` and `place_design`, and places technical equipment and plants from the catalog (the catalog’s function tags tell them from furniture); every other catalog piece is made, and `furnish_room` stays closed. The other sources open `place_items`, `furnish_room` and `place_in_room`, with creation for what the catalog lacks. A non-open source quotes the request in `sourceQuote`. `beyond` records eight dressing layers; an open `dressing` axis names pieces in at least six, and the result returns them as `dressing`. The brief is the session’s: a scene load starts another, and the next `design_brief` replaces it.',
85
90
  '',
86
91
  '## Output Contract',
87
92
  '',
@@ -1,9 +1,12 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import type { SceneOperations } from '../operations/index.js';
3
3
  import { type AssetCatalog } from '../tools/asset-catalog.js';
4
+ /** The items the resource lists of a library searched where it is kept; search_assets finds the rest. */
5
+ export declare const CATALOG_RESOURCE_LIMIT = 100;
4
6
  /**
5
7
  * `pascal://catalog/items` — the items place_items draws from: the host's library when it passes
6
- * one, else a dependency-free built-in subset, so headless agents can still place furniture.
8
+ * one, else a dependency-free built-in subset, so headless agents can still place furniture. A
9
+ * library kept elsewhere is listed by its first items and its size, never whole.
7
10
  */
8
11
  export declare function registerCatalogItems(server: McpServer, _bridge: SceneOperations, catalog?: AssetCatalog): void;
9
12
  //# sourceMappingURL=catalog-items.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"catalog-items.d.ts","sourceRoot":"","sources":["../../src/resources/catalog-items.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,EAAE,KAAK,YAAY,EAAkB,MAAM,wBAAwB,CAAA;AAE1E;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,eAAe,EACxB,OAAO,GAAE,YAA6B,GACrC,IAAI,CA0BN"}
1
+ {"version":3,"file":"catalog-items.d.ts","sourceRoot":"","sources":["../../src/resources/catalog-items.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAE7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,EAAE,KAAK,YAAY,EAAkB,MAAM,wBAAwB,CAAA;AAE1E,yGAAyG;AACzG,eAAO,MAAM,sBAAsB,MAAM,CAAA;AAKzC;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,eAAe,EACxB,OAAO,GAAE,YAA6B,GACrC,IAAI,CA6BN"}
@@ -1,7 +1,12 @@
1
+ import { isAssetCatalogSource } from '@pascal-app/core/agent-operations';
1
2
  import { builtInCatalog } from '../tools/asset-catalog.js';
3
+ /** The items the resource lists of a library searched where it is kept; search_assets finds the rest. */
4
+ export const CATALOG_RESOURCE_LIMIT = 100;
5
+ const NOTE = 'A fixture this catalog lacks (a wall light, a house number) is built as a design: place_design.';
2
6
  /**
3
7
  * `pascal://catalog/items` — the items place_items draws from: the host's library when it passes
4
- * one, else a dependency-free built-in subset, so headless agents can still place furniture.
8
+ * one, else a dependency-free built-in subset, so headless agents can still place furniture. A
9
+ * library kept elsewhere is listed by its first items and its size, never whole.
5
10
  */
6
11
  export function registerCatalogItems(server, _bridge, catalog = builtInCatalog) {
7
12
  server.registerResource('catalog-items', 'pascal://catalog/items', {
@@ -9,11 +14,14 @@ export function registerCatalogItems(server, _bridge, catalog = builtInCatalog)
9
14
  description: "Placeable items: the host's library when it has one, else a built-in subset.",
10
15
  mimeType: 'application/json',
11
16
  }, async (uri) => {
12
- const payload = {
13
- status: 'ok',
14
- items: await catalog(),
15
- note: 'A fixture this catalog lacks (a wall light, a house number) is built as a design: place_design.',
16
- };
17
+ const payload = isAssetCatalogSource(catalog)
18
+ ? await catalog.search({ limit: CATALOG_RESOURCE_LIMIT }).then(({ items, total }) => ({
19
+ status: 'ok',
20
+ items,
21
+ total,
22
+ note: `${items.length < total ? `The library holds ${total} items; these are the first ${items.length}, and search_assets finds any of them. ` : ''}${NOTE}`,
23
+ }))
24
+ : { status: 'ok', items: catalog, note: NOTE };
17
25
  return {
18
26
  contents: [
19
27
  {
package/dist/server.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { McpServer } from '@modelcontextprotocol/server';
2
- import { type HostedServiceExecutor, type ProjectFileToolExecutor } from '@pascal-app/core/agent-operations';
2
+ import { type HostedServiceExecutor, type ProjectFileReader, type ProjectFileToolExecutor } from '@pascal-app/core/agent-operations';
3
3
  import type { SceneBridge } from './bridge/scene-bridge.js';
4
4
  import { type SceneOperations } from './operations/index.js';
5
5
  import type { SceneStore } from './storage/types.js';
6
- import type { GeometryScriptHost } from './tools/add-object.js';
7
- import { type AssetCatalog } from './tools/asset-catalog.js';
6
+ import type { GeometryScriptHost, ObjectMakerHost } from './tools/add-object.js';
7
+ import type { AssetCatalog } from './tools/asset-catalog.js';
8
8
  import type { PlanReadingHosts } from './tools/shared-tools.js';
9
9
  import type { SceneViewHost } from './tools/view-scene.js';
10
10
  export type PascalMcpToolExecutor = <Result>(input: {
@@ -28,16 +28,20 @@ export type CreatePascalMcpServerOptions = {
28
28
  */
29
29
  executeTool?: PascalMcpToolExecutor;
30
30
  /**
31
- * The items search_assets, place_items and furnish_room draw from, read once per server. The
32
- * hosted app passes its published library; without it, a small built-in list.
31
+ * The items search_assets, place_items and furnish_room draw from: a list, or a source each call
32
+ * reads what it needs of (the hosted app's published library); without it, a small built-in list.
33
33
  */
34
34
  catalog?: AssetCatalog;
35
35
  /** Runs and stores `add_object` modules; without it the tool answers `scripts_unavailable`. */
36
36
  geometryScripts?: GeometryScriptHost;
37
+ /** Makes `add_object`'s pieces from words with the host's models; without it they are refused. */
38
+ objectMakers?: ObjectMakerHost;
37
39
  /** Optional authenticated hosted services; local scene tools remain usable without them. */
38
40
  services?: HostedServiceExecutor;
39
41
  /** Authenticated project file catalog; absent for a local scene without hosted files. */
40
42
  files?: ProjectFileToolExecutor;
43
+ /** Reads a project file's bytes (author_scene's payloadFile); absent without hosted files. */
44
+ readFile?: ProjectFileReader;
41
45
  /** Asks an editor open on the project for a picture (`view_scene`); without it, refused. */
42
46
  sceneViews?: SceneViewHost;
43
47
  /** Reads catalog items' pictures and surfaces before view_assets and paint. */
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EAKV,MAAM,8BAA8B,CAAA;AACrC,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAG7B,MAAM,mCAAmC,CAAA;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA;AAG1E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,EAAE,KAAK,YAAY,EAAiB,MAAM,uBAAuB,CAAA;AAKxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAIvD,MAAM,MAAM,qBAAqB,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE;IAClD,IAAI,EAAE,MAAM,CAAA;IACZ,wEAAwE;IACxE,SAAS,EAAE,OAAO,CAAA;IAClB,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;CAC/B,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;AAErB,MAAM,MAAM,4BAA4B,GAAG;IACzC,MAAM,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,mFAAmF;IACnF,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,WAAW,CAAC,EAAE,qBAAqB,CAAA;IACnC;;;OAGG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,+FAA+F;IAC/F,eAAe,CAAC,EAAE,kBAAkB,CAAA;IACpC,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,qBAAqB,CAAA;IAChC,yFAAyF;IACzF,KAAK,CAAC,EAAE,uBAAuB,CAAA;IAC/B,4FAA4F;IAC5F,UAAU,CAAC,EAAE,aAAa,CAAA;IAC1B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;IAC3C,yFAAyF;IACzF,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,4BAA4B,GAAG,SAAS,CA4BnF"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EAKV,MAAM,8BAA8B,CAAA;AACrC,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAG7B,MAAM,mCAAmC,CAAA;AAE1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA;AAG1E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAKzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAIvD,MAAM,MAAM,qBAAqB,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE;IAClD,IAAI,EAAE,MAAM,CAAA;IACZ,wEAAwE;IACxE,SAAS,EAAE,OAAO,CAAA;IAClB,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;CAC/B,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;AAErB,MAAM,MAAM,4BAA4B,GAAG;IACzC,MAAM,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,mFAAmF;IACnF,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,WAAW,CAAC,EAAE,qBAAqB,CAAA;IACnC;;;OAGG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,+FAA+F;IAC/F,eAAe,CAAC,EAAE,kBAAkB,CAAA;IACpC,kGAAkG;IAClG,YAAY,CAAC,EAAE,eAAe,CAAA;IAC9B,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,qBAAqB,CAAA;IAChC,yFAAyF;IACzF,KAAK,CAAC,EAAE,uBAAuB,CAAA;IAC/B,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,iBAAiB,CAAA;IAC5B,4FAA4F;IAC5F,UAAU,CAAC,EAAE,aAAa,CAAA;IAC1B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;IAC3C,yFAAyF;IACzF,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,4BAA4B,GAAG,SAAS,CA8BnF"}