@vcad/mcp 0.11.0-main.162 → 0.11.0-main.164

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/README.md CHANGED
@@ -9,4 +9,4 @@ vcad's MCP server as a self-contained bundle (server + BRep kernel WASM).
9
9
  Or install vcad.app, which registers this server with your agents on first launch.
10
10
  The agent skill ships at `skill/SKILL.md`.
11
11
 
12
- Built from 7d787f258ea026c36d80b9cb137c3f0064fcce3c at 2026-11-11T19:59:54.944Z. Source: https://github.com/muni-works/vcad
12
+ Built from f36361fa47f1a47d196b4d9a8bcdb421c1cc785f at 2026-11-11T21:56:09.744Z. Source: https://github.com/muni-works/vcad
package/index.mjs CHANGED
@@ -11312,9 +11312,9 @@ function workerTransport(worker) {
11312
11312
  pending.delete(id);
11313
11313
  });
11314
11314
  return {
11315
- request: (req) => new Promise((resolve8) => {
11315
+ request: (req) => new Promise((resolve9) => {
11316
11316
  const id = next++;
11317
- pending.set(id, resolve8);
11317
+ pending.set(id, resolve9);
11318
11318
  worker.postMessage({ id, req });
11319
11319
  }),
11320
11320
  dispose: () => worker.terminate()
@@ -12193,11 +12193,11 @@ var init_analyze = __esm({
12193
12193
  type: "module"
12194
12194
  });
12195
12195
  this.worker = worker;
12196
- this.ready = new Promise((resolve8, reject) => {
12196
+ this.ready = new Promise((resolve9, reject) => {
12197
12197
  const onMessage = (e) => {
12198
12198
  const { type, id, message, analysis } = e.data;
12199
12199
  if (type === "ready") {
12200
- resolve8();
12200
+ resolve9();
12201
12201
  return;
12202
12202
  }
12203
12203
  if (type === "error" && id === null) {
@@ -12221,9 +12221,9 @@ var init_analyze = __esm({
12221
12221
  return this.ready;
12222
12222
  }
12223
12223
  request(msg, transfer = []) {
12224
- return this.ensureWorker().then(() => new Promise((resolve8, reject) => {
12224
+ return this.ensureWorker().then(() => new Promise((resolve9, reject) => {
12225
12225
  const id = this.nextId++;
12226
- this.pending.set(id, { resolve: resolve8, reject });
12226
+ this.pending.set(id, { resolve: resolve9, reject });
12227
12227
  this.worker.postMessage({ ...msg, id }, transfer);
12228
12228
  }));
12229
12229
  }
@@ -12840,11 +12840,11 @@ var init_dist = __esm({
12840
12840
  return;
12841
12841
  try {
12842
12842
  const worker = new Worker(new URL("./eval-worker.js", import.meta.url), { type: "module" });
12843
- this.workerReady = new Promise((resolve8, reject) => {
12843
+ this.workerReady = new Promise((resolve9, reject) => {
12844
12844
  const onMessage = (e) => {
12845
12845
  if (e.data.type === "ready") {
12846
12846
  worker.removeEventListener("message", onMessage);
12847
- resolve8();
12847
+ resolve9();
12848
12848
  } else if (e.data.type === "error" && e.data.id === null) {
12849
12849
  worker.removeEventListener("message", onMessage);
12850
12850
  console.warn("[ENGINE] Worker WASM init failed:", e.data.message);
@@ -13067,7 +13067,7 @@ var init_dist = __esm({
13067
13067
  const worker = this.worker;
13068
13068
  const id = Math.random().toString(36).slice(2);
13069
13069
  const skipClash = options.skipClashDetection ?? false;
13070
- return new Promise((resolve8, reject) => {
13070
+ return new Promise((resolve9, reject) => {
13071
13071
  const onMessage = (e) => {
13072
13072
  if (e.data.id !== id)
13073
13073
  return;
@@ -13077,7 +13077,7 @@ var init_dist = __esm({
13077
13077
  if (scene.timing && _Engine.DEV) {
13078
13078
  _Engine.logTiming(scene.timing, e.data.workerTotalMs);
13079
13079
  }
13080
- resolve8(scene);
13080
+ resolve9(scene);
13081
13081
  } else if (e.data.type === "error") {
13082
13082
  reject(new Error(e.data.message));
13083
13083
  }
@@ -17365,7 +17365,7 @@ function isRef(value) {
17365
17365
  function cloneIssues(issues) {
17366
17366
  return issues.map((iss) => iss.path ? { ...iss, path: iss.path.slice() } : { ...iss });
17367
17367
  }
17368
- function isRecursive(inst, stack, resolve8) {
17368
+ function isRecursive(inst, stack, resolve9) {
17369
17369
  const cached3 = recursive.get(inst);
17370
17370
  if (cached3 !== void 0)
17371
17371
  return cached3 ? PROVEN : NONE;
@@ -17375,7 +17375,7 @@ function isRecursive(inst, stack, resolve8) {
17375
17375
  let result = NONE;
17376
17376
  const check = (child) => {
17377
17377
  if (result !== PROVEN && child?._zod) {
17378
- const answer = isRecursive(child, stack, resolve8);
17378
+ const answer = isRecursive(child, stack, resolve9);
17379
17379
  if (answer > result)
17380
17380
  result = answer;
17381
17381
  }
@@ -17386,7 +17386,7 @@ function isRecursive(inst, stack, resolve8) {
17386
17386
  const desc = Object.getOwnPropertyDescriptor(sh, key3);
17387
17387
  if (spread && !desc.enumerable)
17388
17388
  continue;
17389
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve8) : NONE;
17389
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve9) : NONE;
17390
17390
  if (child > answer)
17391
17391
  answer = child;
17392
17392
  }
@@ -17450,7 +17450,7 @@ function isRecursive(inst, stack, resolve8) {
17450
17450
  break;
17451
17451
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
17452
17452
  case "lazy": {
17453
- const inner = def._cachedInner ?? (resolve8 ? inst._zod.innerType : void 0);
17453
+ const inner = def._cachedInner ?? (resolve9 ? inst._zod.innerType : void 0);
17454
17454
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
17455
17455
  break;
17456
17456
  }
@@ -22204,12 +22204,12 @@ var init_stdio2 = __esm({
22204
22204
  this.onclose?.();
22205
22205
  }
22206
22206
  send(message) {
22207
- return new Promise((resolve8) => {
22207
+ return new Promise((resolve9) => {
22208
22208
  const json = serializeMessage(message);
22209
22209
  if (this._stdout.write(json)) {
22210
- resolve8();
22210
+ resolve9();
22211
22211
  } else {
22212
- this._stdout.once("drain", resolve8);
22212
+ this._stdout.once("drain", resolve9);
22213
22213
  }
22214
22214
  });
22215
22215
  }
@@ -23246,7 +23246,7 @@ var init_protocol = __esm({
23246
23246
  return;
23247
23247
  }
23248
23248
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
23249
- await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
23249
+ await new Promise((resolve9) => setTimeout(resolve9, pollInterval));
23250
23250
  options?.signal?.throwIfAborted();
23251
23251
  }
23252
23252
  } catch (error2) {
@@ -23263,7 +23263,7 @@ var init_protocol = __esm({
23263
23263
  */
23264
23264
  request(request, resultSchema, options) {
23265
23265
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
23266
- return new Promise((resolve8, reject) => {
23266
+ return new Promise((resolve9, reject) => {
23267
23267
  const earlyReject = (error2) => {
23268
23268
  reject(error2);
23269
23269
  };
@@ -23341,7 +23341,7 @@ var init_protocol = __esm({
23341
23341
  if (!parseResult.success) {
23342
23342
  reject(parseResult.error);
23343
23343
  } else {
23344
- resolve8(parseResult.data);
23344
+ resolve9(parseResult.data);
23345
23345
  }
23346
23346
  } catch (error2) {
23347
23347
  reject(error2);
@@ -23602,12 +23602,12 @@ var init_protocol = __esm({
23602
23602
  }
23603
23603
  } catch {
23604
23604
  }
23605
- return new Promise((resolve8, reject) => {
23605
+ return new Promise((resolve9, reject) => {
23606
23606
  if (signal.aborted) {
23607
23607
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
23608
23608
  return;
23609
23609
  }
23610
- const timeoutId = setTimeout(resolve8, interval);
23610
+ const timeoutId = setTimeout(resolve9, interval);
23611
23611
  signal.addEventListener("abort", () => {
23612
23612
  clearTimeout(timeoutId);
23613
23613
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -26634,7 +26634,7 @@ var require_compile = __commonJS({
26634
26634
  const schOrFunc = root.refs[ref];
26635
26635
  if (schOrFunc)
26636
26636
  return schOrFunc;
26637
- let _sch = resolve8.call(this, root, ref);
26637
+ let _sch = resolve9.call(this, root, ref);
26638
26638
  if (_sch === void 0) {
26639
26639
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
26640
26640
  const { schemaId } = this.opts;
@@ -26661,7 +26661,7 @@ var require_compile = __commonJS({
26661
26661
  function sameSchemaEnv(s1, s2) {
26662
26662
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
26663
26663
  }
26664
- function resolve8(root, ref) {
26664
+ function resolve9(root, ref) {
26665
26665
  let sch;
26666
26666
  while (typeof (sch = this.refs[ref]) == "string")
26667
26667
  ref = sch;
@@ -27236,7 +27236,7 @@ var require_fast_uri = __commonJS({
27236
27236
  }
27237
27237
  return uri;
27238
27238
  }
27239
- function resolve8(baseURI, relativeURI, options) {
27239
+ function resolve9(baseURI, relativeURI, options) {
27240
27240
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
27241
27241
  const resolved = resolveComponent(parse5(baseURI, schemelessOptions), parse5(relativeURI, schemelessOptions), schemelessOptions, true);
27242
27242
  schemelessOptions.skipEscape = true;
@@ -27463,7 +27463,7 @@ var require_fast_uri = __commonJS({
27463
27463
  var fastUri = {
27464
27464
  SCHEMES,
27465
27465
  normalize: normalize2,
27466
- resolve: resolve8,
27466
+ resolve: resolve9,
27467
27467
  resolveComponent,
27468
27468
  equal,
27469
27469
  serialize,
@@ -33635,21 +33635,21 @@ var require_react_development = __commonJS({
33635
33635
  );
33636
33636
  actScopeDepth = prevActScopeDepth;
33637
33637
  }
33638
- function recursivelyFlushAsyncActWork(returnValue, resolve8, reject) {
33638
+ function recursivelyFlushAsyncActWork(returnValue, resolve9, reject) {
33639
33639
  var queue = ReactSharedInternals.actQueue;
33640
33640
  if (null !== queue)
33641
33641
  if (0 !== queue.length)
33642
33642
  try {
33643
33643
  flushActQueue(queue);
33644
33644
  enqueueTask(function() {
33645
- return recursivelyFlushAsyncActWork(returnValue, resolve8, reject);
33645
+ return recursivelyFlushAsyncActWork(returnValue, resolve9, reject);
33646
33646
  });
33647
33647
  return;
33648
33648
  } catch (error2) {
33649
33649
  ReactSharedInternals.thrownErrors.push(error2);
33650
33650
  }
33651
33651
  else ReactSharedInternals.actQueue = null;
33652
- 0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve8(returnValue);
33652
+ 0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve9(returnValue);
33653
33653
  }
33654
33654
  function flushActQueue(queue) {
33655
33655
  if (!isFlushing) {
@@ -33836,7 +33836,7 @@ var require_react_development = __commonJS({
33836
33836
  ));
33837
33837
  });
33838
33838
  return {
33839
- then: function(resolve8, reject) {
33839
+ then: function(resolve9, reject) {
33840
33840
  didAwaitActCall = true;
33841
33841
  thenable.then(
33842
33842
  function(returnValue) {
@@ -33846,7 +33846,7 @@ var require_react_development = __commonJS({
33846
33846
  flushActQueue(queue), enqueueTask(function() {
33847
33847
  return recursivelyFlushAsyncActWork(
33848
33848
  returnValue,
33849
- resolve8,
33849
+ resolve9,
33850
33850
  reject
33851
33851
  );
33852
33852
  });
@@ -33860,7 +33860,7 @@ var require_react_development = __commonJS({
33860
33860
  ReactSharedInternals.thrownErrors.length = 0;
33861
33861
  reject(_thrownError);
33862
33862
  }
33863
- } else resolve8(returnValue);
33863
+ } else resolve9(returnValue);
33864
33864
  },
33865
33865
  function(error2) {
33866
33866
  popActScope(prevActQueue, prevActScopeDepth);
@@ -33882,15 +33882,15 @@ var require_react_development = __commonJS({
33882
33882
  if (0 < ReactSharedInternals.thrownErrors.length)
33883
33883
  throw callback = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, callback;
33884
33884
  return {
33885
- then: function(resolve8, reject) {
33885
+ then: function(resolve9, reject) {
33886
33886
  didAwaitActCall = true;
33887
33887
  0 === prevActScopeDepth ? (ReactSharedInternals.actQueue = queue, enqueueTask(function() {
33888
33888
  return recursivelyFlushAsyncActWork(
33889
33889
  returnValue$jscomp$0,
33890
- resolve8,
33890
+ resolve9,
33891
33891
  reject
33892
33892
  );
33893
- })) : resolve8(returnValue$jscomp$0);
33893
+ })) : resolve9(returnValue$jscomp$0);
33894
33894
  }
33895
33895
  };
33896
33896
  };
@@ -44853,6 +44853,19 @@ var init_CHANGELOG = __esm({
44853
44853
  "mac-app"
44854
44854
  ]
44855
44855
  },
44856
+ {
44857
+ id: "2026-11-11-per-instance-agent-discovery",
44858
+ version: "undefined",
44859
+ date: "2026-11-11",
44860
+ category: "fix",
44861
+ title: "Agents no longer drive the wrong vcad.app window",
44862
+ summary: "Each vcad.app instance registers its own discovery file; clients pick one via VCAD_APP_PID/URL/DOCUMENT, every bridge result names its window, and a relaunch is reported.",
44863
+ features: [
44864
+ "agent-bridge",
44865
+ "mcp",
44866
+ "mac-app"
44867
+ ]
44868
+ },
44856
44869
  {
44857
44870
  id: "2026-11-11-one-click-drawings",
44858
44871
  version: "0.11.0",
@@ -44919,6 +44932,34 @@ var init_CHANGELOG = __esm({
44919
44932
  "ask_user"
44920
44933
  ]
44921
44934
  },
44935
+ {
44936
+ id: "2026-11-11-live-qa-round-2",
44937
+ version: "0.11.0",
44938
+ date: "2026-11-11",
44939
+ category: "fix",
44940
+ title: "Mac app: Find works on a new file; print orient fits bed",
44941
+ summary: "Find offers Sketch/Extrude/Fillet before the first edit and closes on click-outside, Esc, or a mode switch; Orient never defaults to a pose that overflows the printer.",
44942
+ features: [
44943
+ "mac-app",
44944
+ "command-palette",
44945
+ "3d-print"
44946
+ ]
44947
+ },
44948
+ {
44949
+ id: "2026-11-11-huge-assembly-open",
44950
+ version: "0.11.0",
44951
+ date: "2026-11-11",
44952
+ category: "perf",
44953
+ title: "Huge STEP assemblies open in seconds",
44954
+ summary: "Identical imported parts tessellate once and are instanced, parts stream in as they finish, and big assemblies draw a quick preview first, then refine. Voron 2.4: first part 425 s \u2192 1.4 s.",
44955
+ features: [
44956
+ "step",
44957
+ "import",
44958
+ "assembly",
44959
+ "native-app",
44960
+ "performance"
44961
+ ]
44962
+ },
44922
44963
  {
44923
44964
  id: "2026-11-11-history-edits-keep-references",
44924
44965
  version: "undefined",
@@ -44932,6 +44973,19 @@ var init_CHANGELOG = __esm({
44932
44973
  "history"
44933
44974
  ]
44934
44975
  },
44976
+ {
44977
+ id: "2026-11-11-fusion-model-edges",
44978
+ version: "1.0.0",
44979
+ date: "2026-11-11",
44980
+ category: "feat",
44981
+ title: "Model edges drawn like Fusion 360",
44982
+ summary: "The Mac app now outlines every B-rep face boundary, tangent fillet edges included, and draws outlines on assembly instances too. Mesh-only parts keep crease edges.",
44983
+ features: [
44984
+ "viewport",
44985
+ "edges",
44986
+ "assembly"
44987
+ ]
44988
+ },
44935
44989
  {
44936
44990
  id: "2026-11-11-direct-manipulation",
44937
44991
  version: "undefined",
@@ -57378,10 +57432,86 @@ var init_source_provenance = __esm({
57378
57432
  }
57379
57433
  });
57380
57434
 
57381
- // src/app-link.ts
57382
- import { readFileSync as readFileSync5 } from "node:fs";
57435
+ // src/app-discovery.ts
57436
+ import { readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
57383
57437
  import { homedir } from "node:os";
57384
- import { dirname, isAbsolute as isAbsolute4, join as join3 } from "node:path";
57438
+ import { join as join3, resolve as resolve3 } from "node:path";
57439
+ function isAlive(pid) {
57440
+ if (typeof pid !== "number" || pid <= 0) return false;
57441
+ try {
57442
+ process.kill(pid, 0);
57443
+ return true;
57444
+ } catch (e) {
57445
+ return e.code === "EPERM";
57446
+ }
57447
+ }
57448
+ function describeInstance(r) {
57449
+ return `pid ${r.pid} (${r.document_name ?? r.document_path ?? "no document"})`;
57450
+ }
57451
+ function listInstances({ home = homedir(), alive = isAlive } = {}) {
57452
+ const root = join3(home, ".vcad");
57453
+ const read = (p) => {
57454
+ try {
57455
+ return JSON.parse(readFileSync5(p, "utf8"));
57456
+ } catch {
57457
+ return null;
57458
+ }
57459
+ };
57460
+ let records = [];
57461
+ try {
57462
+ const dir = join3(root, "instances");
57463
+ records = readdirSync2(dir).filter((f) => f.endsWith(".json")).map((f) => read(join3(dir, f))).filter((r) => r !== null);
57464
+ } catch {
57465
+ }
57466
+ if (records.length === 0) {
57467
+ const legacy = read(join3(root, "app.json"));
57468
+ if (legacy) records = [legacy];
57469
+ }
57470
+ return records.filter((r) => alive(r.pid)).sort((a, b) => String(b.started ?? "").localeCompare(String(a.started ?? "")));
57471
+ }
57472
+ function selectInstance({
57473
+ env = process.env,
57474
+ instances,
57475
+ documentPath
57476
+ }) {
57477
+ if (env.VCAD_APP_URL) {
57478
+ const url = env.VCAD_APP_URL;
57479
+ return { url, instance: instances.find((r) => urlOf(r) === url) ?? null };
57480
+ }
57481
+ if (env.VCAD_APP_PID) {
57482
+ const pid = Number(env.VCAD_APP_PID);
57483
+ const instance2 = instances.find((r) => r.pid === pid);
57484
+ if (!instance2) {
57485
+ const others2 = instances.length ? ` Running: ${instances.map(describeInstance).join(", ")}.` : "";
57486
+ return { error: `VCAD_APP_PID=${env.VCAD_APP_PID}: no running vcad.app with that pid.${others2}` };
57487
+ }
57488
+ return { url: urlOf(instance2), instance: instance2 };
57489
+ }
57490
+ if (instances.length === 0) return { error: "no running vcad.app found (~/.vcad/instances is empty)" };
57491
+ const wanted = documentPath ?? env.VCAD_APP_DOCUMENT;
57492
+ if (wanted) {
57493
+ const abs = resolve3(wanted);
57494
+ const instance2 = instances.find((r) => r.document_path && resolve3(r.document_path) === abs);
57495
+ if (instance2) return { url: urlOf(instance2), instance: instance2 };
57496
+ }
57497
+ const [instance, ...others] = instances;
57498
+ if (!others.length) return { url: urlOf(instance), instance };
57499
+ return {
57500
+ url: urlOf(instance),
57501
+ instance,
57502
+ warning: `${instances.length} vcad.app instances are running; using the newest, ${describeInstance(instance)}. Others: ${others.map(describeInstance).join(", ")}. Set VCAD_APP_PID to choose.`
57503
+ };
57504
+ }
57505
+ var urlOf;
57506
+ var init_app_discovery = __esm({
57507
+ "src/app-discovery.ts"() {
57508
+ "use strict";
57509
+ urlOf = (r) => r.url ?? `http://127.0.0.1:${r.port}/mcp`;
57510
+ }
57511
+ });
57512
+
57513
+ // src/app-link.ts
57514
+ import { dirname, isAbsolute as isAbsolute4 } from "node:path";
57385
57515
  function noteDocumentFolder(documentId, folder) {
57386
57516
  folders.set(documentId, folder);
57387
57517
  }
@@ -57393,13 +57523,27 @@ function documentFolder(documentId, document) {
57393
57523
  }
57394
57524
  function endpoint() {
57395
57525
  if (process.env.VCAD_APP === "0") return null;
57396
- try {
57397
- const info = JSON.parse(readFileSync5(join3(homedir(), ".vcad", "app.json"), "utf8"));
57398
- if (typeof info.pid === "number") process.kill(info.pid, 0);
57399
- return info.url ?? `http://127.0.0.1:${info.port}/mcp`;
57400
- } catch {
57401
- return null;
57526
+ const instances = listInstances();
57527
+ if (pinned && (process.env.VCAD_APP_URL || instances.some((r) => r.pid === pinned?.instance?.pid))) {
57528
+ return pinned.url;
57529
+ }
57530
+ const pick2 = selectInstance({ instances });
57531
+ if (pick2.error !== void 0) return null;
57532
+ if (pick2.warning && pick2.warning !== warned) {
57533
+ warned = pick2.warning;
57534
+ process.stderr.write(`[vcad-mcp] ${pick2.warning}
57535
+ `);
57536
+ }
57537
+ const prev = pinned?.instance;
57538
+ if (prev && pick2.instance && prev.instance_id !== pick2.instance.instance_id) {
57539
+ appChanged(prev, pick2.instance);
57402
57540
  }
57541
+ pinned = { url: pick2.url, instance: pick2.instance };
57542
+ return pick2.url;
57543
+ }
57544
+ function appChanged(prev, next) {
57545
+ const why = `The vcad app changed: ${describeInstance(prev)} is gone, now linked to ${describeInstance(next)} (it relaunched, or another window took over). Edits stay in this session. To show it there, call show_in_app.`;
57546
+ for (const id of links.keys()) loseWindow(id, why);
57403
57547
  }
57404
57548
  async function reconcileBeforeWrite(documentId, documents2, call = callApp) {
57405
57549
  const link = links.get(documentId);
@@ -57613,14 +57757,17 @@ function pushPose(documentId, joints, call = callApp, now = Date.now(), base) {
57613
57757
  sendPose(frame, now);
57614
57758
  return true;
57615
57759
  }
57616
- var links, folders, callApp, PUSH_WAIT_MS, RECONCILE_WAIT_MS, wait, pushes, lateNotes, LOST_WINDOW, OPENING_TOOLS, toolDefs3, POSE_INTERVAL_MS, poseBusy, lastPose, pendingRestart, pendingLatest, poseTimer;
57760
+ var links, folders, pinned, warned, callApp, PUSH_WAIT_MS, RECONCILE_WAIT_MS, wait, pushes, lateNotes, LOST_WINDOW, OPENING_TOOLS, toolDefs3, POSE_INTERVAL_MS, poseBusy, lastPose, pendingRestart, pendingLatest, poseTimer;
57617
57761
  var init_app_link = __esm({
57618
57762
  "src/app-link.ts"() {
57619
57763
  "use strict";
57764
+ init_app_discovery();
57620
57765
  init_session_core();
57621
57766
  init_tool_def();
57622
57767
  links = /* @__PURE__ */ new Map();
57623
57768
  folders = /* @__PURE__ */ new Map();
57769
+ pinned = null;
57770
+ warned = "";
57624
57771
  callApp = async (tool, args) => {
57625
57772
  const url = endpoint();
57626
57773
  if (!url) return null;
@@ -57690,7 +57837,7 @@ var init_app_link = __esm({
57690
57837
 
57691
57838
  // src/tools/session.ts
57692
57839
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync6 } from "node:fs";
57693
- import { dirname as dirname2, resolve as resolve3 } from "node:path";
57840
+ import { dirname as dirname2, resolve as resolve4 } from "node:path";
57694
57841
  import { randomBytes as randomBytes2 } from "node:crypto";
57695
57842
  import { AsyncLocalStorage } from "node:async_hooks";
57696
57843
  function durabilityWarning() {
@@ -57716,8 +57863,16 @@ function runInSessionScope(user, fn) {
57716
57863
  return out;
57717
57864
  }
57718
57865
  async function hydrateSession(store, documentId) {
57719
- if (documents.has(documentId)) return true;
57866
+ const proxied = process.env.VCAD_MCP_PROXIED === "1";
57867
+ if (documents.has(documentId) && !proxied) return true;
57720
57868
  const doc = await store.load(documentId);
57869
+ if (doc && proxied && documents.has(documentId)) {
57870
+ if (JSON.stringify(documents.get(documentId)) !== JSON.stringify(doc)) {
57871
+ documents.set(documentId, doc);
57872
+ runDocumentEnterHooks(doc);
57873
+ }
57874
+ return true;
57875
+ }
57721
57876
  if (doc) {
57722
57877
  documents.set(documentId, doc);
57723
57878
  runDocumentEnterHooks(doc);
@@ -57762,7 +57917,7 @@ function openDocument(args) {
57762
57917
  };
57763
57918
  }
57764
57919
  function writeLocalSnapshot(id, json) {
57765
- const dir = resolve3(stateRoot(), "get_document");
57920
+ const dir = resolve4(stateRoot(), "get_document");
57766
57921
  mkdirSync2(dir, { recursive: true });
57767
57922
  const path = resolveWithinRoot(`${id}.vcad`, dir);
57768
57923
  writeFileSync2(path, json);
@@ -57975,7 +58130,7 @@ async function loadDocumentFromPath(path, store, engine) {
57975
58130
  "`path` loads are local/stdio only \u2014 this server's filesystem is ephemeral. Pass the source inline to create_cad_loon, or save with save_document and reopen by `name`."
57976
58131
  );
57977
58132
  }
57978
- const abs = resolve3(path);
58133
+ const abs = resolve4(path);
57979
58134
  let raw;
57980
58135
  try {
57981
58136
  raw = readFileSync6(abs, "utf8");
@@ -61780,8 +61935,8 @@ async function openBridge(createServer2, clientInfo, clientCapabilities, hooks)
61780
61935
  await server.connect(serverSide);
61781
61936
  const rpc = (method, params) => {
61782
61937
  const id = nextId3++;
61783
- return new Promise((resolve8) => {
61784
- pending.set(id, (m) => resolve8(m));
61938
+ return new Promise((resolve9) => {
61939
+ pending.set(id, (m) => resolve9(m));
61785
61940
  void clientSide.send({
61786
61941
  jsonrpc: "2.0",
61787
61942
  id,
@@ -62118,8 +62273,8 @@ function taskElicitation(bridge, reqParams) {
62118
62273
  method: "elicitation/create",
62119
62274
  params: cleanParams
62120
62275
  };
62121
- return new Promise((resolve8) => {
62122
- record2.pendingInputs.set(key3, resolve8);
62276
+ return new Promise((resolve9) => {
62277
+ record2.pendingInputs.set(key3, resolve9);
62123
62278
  touchTask(record2, "input_required", "waiting for client input");
62124
62279
  });
62125
62280
  }
@@ -62144,11 +62299,11 @@ function handleTaskMethod(id, method, params) {
62144
62299
  if (method === "tasks/update") {
62145
62300
  const responses = params.inputResponses && typeof params.inputResponses === "object" ? params.inputResponses : {};
62146
62301
  for (const [key3, response] of Object.entries(responses)) {
62147
- const resolve8 = record2.pendingInputs.get(key3);
62148
- if (!resolve8) continue;
62302
+ const resolve9 = record2.pendingInputs.get(key3);
62303
+ if (!resolve9) continue;
62149
62304
  record2.pendingInputs.delete(key3);
62150
62305
  delete record2.inputRequests[key3];
62151
- resolve8(response);
62306
+ resolve9(response);
62152
62307
  }
62153
62308
  if (record2.status === "input_required" && Object.keys(record2.inputRequests).length === 0) {
62154
62309
  touchTask(record2, "working", `running ${record2.toolName}`);
@@ -62160,10 +62315,10 @@ function handleTaskMethod(id, method, params) {
62160
62315
  }
62161
62316
  record2.cancelRequested = true;
62162
62317
  if (record2.status === "working" || record2.status === "input_required") {
62163
- for (const [key3, resolve8] of record2.pendingInputs) {
62318
+ for (const [key3, resolve9] of record2.pendingInputs) {
62164
62319
  record2.pendingInputs.delete(key3);
62165
62320
  delete record2.inputRequests[key3];
62166
- resolve8({ action: "cancel" });
62321
+ resolve9({ action: "cancel" });
62167
62322
  }
62168
62323
  touchTask(record2, "cancelled", "cancelled by client");
62169
62324
  }
@@ -62841,7 +62996,7 @@ function normalizeOp(op, index) {
62841
62996
  return { name, args };
62842
62997
  }
62843
62998
  function resolveSymbolicRefs(args, createdIds, index) {
62844
- const resolve8 = (v) => {
62999
+ const resolve9 = (v) => {
62845
63000
  if (typeof v !== "string") return v;
62846
63001
  const m = SYMBOLIC_REF.exec(v);
62847
63002
  if (!m) return v;
@@ -62861,15 +63016,15 @@ function resolveSymbolicRefs(args, createdIds, index) {
62861
63016
  };
62862
63017
  const out = { ...args };
62863
63018
  for (const key3 of ["part_id", "node_id"]) {
62864
- if (key3 in out) out[key3] = resolve8(out[key3]);
63019
+ if (key3 in out) out[key3] = resolve9(out[key3]);
62865
63020
  }
62866
63021
  if (out.params && typeof out.params === "object" && !Array.isArray(out.params)) {
62867
63022
  const params = { ...out.params };
62868
63023
  for (const key3 of ["child", "left", "right", "sketch"]) {
62869
- if (key3 in params) params[key3] = resolve8(params[key3]);
63024
+ if (key3 in params) params[key3] = resolve9(params[key3]);
62870
63025
  }
62871
63026
  if (Array.isArray(params.sketches)) {
62872
- params.sketches = params.sketches.map(resolve8);
63027
+ params.sketches = params.sketches.map(resolve9);
62873
63028
  }
62874
63029
  out.params = params;
62875
63030
  }
@@ -67532,7 +67687,7 @@ async function flattenSchematic(sheet, netlister) {
67532
67687
  if (def.body.hierarchy) {
67533
67688
  err9(`Sheet '${def.name}' body has its own hierarchy \u2014 nest sheets with \`instances\` instead (ignored)`);
67534
67689
  }
67535
- const resolve8 = (name) => {
67690
+ const resolve9 = (name) => {
67536
67691
  if (ports.has(name)) {
67537
67692
  const bound = binding.get(name);
67538
67693
  if (bound !== void 0) return bound;
@@ -67560,7 +67715,7 @@ async function flattenSchematic(sheet, netlister) {
67560
67715
  if (pins.length === 0) continue;
67561
67716
  if (isAutoName(name) && pins.length < 2) continue;
67562
67717
  usedNames.add(name);
67563
- const flat = resolve8(name);
67718
+ const flat = resolve9(name);
67564
67719
  if (!ports.has(name) && !globals.has(name) && rootNames.has(flat)) {
67565
67720
  err9(
67566
67721
  `Net scope collision: local net '${name}' of sheet instance '${path}' flattens to '${flat}', which the root sheet also uses`
@@ -67584,9 +67739,9 @@ async function flattenSchematic(sheet, netlister) {
67584
67739
  warn(`Hierarchical port '${port}' of sheet '${def.name}' connects to nothing inside the sheet`);
67585
67740
  }
67586
67741
  }
67587
- for (const flag of def.body.power_flags ?? []) outFlags.push({ ...flag, net: resolve8(flag.net) });
67742
+ for (const flag of def.body.power_flags ?? []) outFlags.push({ ...flag, net: resolve9(flag.net) });
67588
67743
  instances.push({ path, sheet: def.name, refs });
67589
- await elaborateAll(def.instances ?? [], path, offset, resolve8, stack);
67744
+ await elaborateAll(def.instances ?? [], path, offset, resolve9, stack);
67590
67745
  };
67591
67746
  await elaborateAll(h.instances ?? [], "", { x: 0, y: 0 }, (n) => n, []);
67592
67747
  const nets = {};
@@ -69141,12 +69296,12 @@ var init_enclosure = __esm({
69141
69296
  // src/wasm/ecad-diff.ts
69142
69297
  import { createRequire } from "node:module";
69143
69298
  import { fileURLToPath } from "node:url";
69144
- import { dirname as dirname3, resolve as resolve4 } from "node:path";
69299
+ import { dirname as dirname3, resolve as resolve5 } from "node:path";
69145
69300
  function load() {
69146
69301
  if (cached2 !== void 0) return cached2;
69147
69302
  try {
69148
69303
  const here = dirname3(fileURLToPath(import.meta.url));
69149
- const pkg = resolve4(here, "../../../../crates/vcad-ecad-diff-wasm/pkg/vcad_ecad_diff_wasm.js");
69304
+ const pkg = resolve5(here, "../../../../crates/vcad-ecad-diff-wasm/pkg/vcad_ecad_diff_wasm.js");
69150
69305
  const req = createRequire(import.meta.url);
69151
69306
  cached2 = req(pkg);
69152
69307
  } catch {
@@ -83028,7 +83183,7 @@ import { execFile } from "child_process";
83028
83183
  import {
83029
83184
  existsSync as existsSync4,
83030
83185
  mkdtempSync as mkdtempSync2,
83031
- readdirSync as readdirSync2,
83186
+ readdirSync as readdirSync3,
83032
83187
  readFileSync as readFileSync8,
83033
83188
  rmSync,
83034
83189
  writeFileSync as writeFileSync5
@@ -83086,7 +83241,7 @@ function listEvalTasks(args) {
83086
83241
  const tasksDir = join6(mechevalRoot, "tasks");
83087
83242
  const suiteFilter = args.suite ? String(args.suite).toUpperCase() : null;
83088
83243
  const tasks2 = [];
83089
- for (const file of readdirSync2(tasksDir).sort()) {
83244
+ for (const file of readdirSync3(tasksDir).sort()) {
83090
83245
  if (!file.endsWith(".json")) continue;
83091
83246
  try {
83092
83247
  const raw = JSON.parse(readFileSync8(join6(tasksDir, file), "utf8"));
@@ -87474,14 +87629,14 @@ var init_acoustics = __esm({
87474
87629
  // src/tools/import.ts
87475
87630
  import { createHash as createHash6 } from "node:crypto";
87476
87631
  import { readFileSync as readFileSync9, existsSync as existsSync5, statSync as statSync2, mkdirSync as mkdirSync3, writeFileSync as writeFileSync7 } from "node:fs";
87477
- import { basename, join as join7, resolve as resolve5, sep as sep3 } from "node:path";
87632
+ import { basename, join as join7, resolve as resolve6, sep as sep3 } from "node:path";
87478
87633
  function exportRoot() {
87479
87634
  return process.env.VCAD_MCP_EXPORT_DIR ?? process.cwd();
87480
87635
  }
87481
87636
  function isWithinExportRoot(path) {
87482
87637
  if (path.includes("\0")) return false;
87483
- const root = resolve5(exportRoot());
87484
- const abs = resolve5(path);
87638
+ const root = resolve6(exportRoot());
87639
+ const abs = resolve6(path);
87485
87640
  return abs === root || abs.startsWith(root + sep3);
87486
87641
  }
87487
87642
  function stepNodePath(bytes, diskPath, label) {
@@ -87492,7 +87647,7 @@ function stepNodePath(bytes, diskPath, label) {
87492
87647
  mkdirSync3(dir, { recursive: true });
87493
87648
  const file = join7(dir, `${digest}.step`);
87494
87649
  if (!existsSync5(file)) writeFileSync7(file, bytes);
87495
- return { path: resolve5(file), portable: true };
87650
+ return { path: resolve6(file), portable: true };
87496
87651
  } catch {
87497
87652
  return { path: `step:${digest}/${label}`, portable: false };
87498
87653
  }
@@ -87568,7 +87723,7 @@ function readStepInput(input) {
87568
87723
  if (stat.size > MAX_STEP_BYTES) {
87569
87724
  throw new Error(`STEP file exceeds ${MAX_STEP_BYTES} byte limit`);
87570
87725
  }
87571
- return { bytes: readFileSync9(filepath), diskPath: resolve5(filepath) };
87726
+ return { bytes: readFileSync9(filepath), diskPath: resolve6(filepath) };
87572
87727
  }
87573
87728
  function skippedFaceSummary(report, warnings) {
87574
87729
  const skipped = report.reduce((sum, s) => sum + s.skipped_faces.length, 0);
@@ -87741,11 +87896,11 @@ var init_import = __esm({
87741
87896
 
87742
87897
  // src/tools/import-urdf.ts
87743
87898
  import { readFileSync as readFileSync10, existsSync as existsSync6, statSync as statSync3 } from "node:fs";
87744
- import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute5, resolve as resolve6, sep as sep4 } from "node:path";
87899
+ import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute5, resolve as resolve7, sep as sep4 } from "node:path";
87745
87900
  function resolveMeshPath(filename, urdfDir, packageRoots, confineRoot) {
87746
87901
  const confined = (p) => {
87747
- const abs = resolve6(p);
87748
- const root = resolve6(confineRoot);
87902
+ const abs = resolve7(p);
87903
+ const root = resolve7(confineRoot);
87749
87904
  const rootSep = root.endsWith(sep4) ? root : root + sep4;
87750
87905
  if (abs !== root && !abs.startsWith(rootSep)) return null;
87751
87906
  return existsSync6(abs) && statSync3(abs).isFile() ? abs : null;
@@ -87754,14 +87909,14 @@ function resolveMeshPath(filename, urdfDir, packageRoots, confineRoot) {
87754
87909
  const pkgMatch = filename.match(/^package:\/\/([^/]+)\/?(.*)$/);
87755
87910
  if (pkgMatch) {
87756
87911
  for (const root of packageRoots) {
87757
- const hit = confined(resolve6(root, pkgMatch[1], pkgMatch[2]));
87912
+ const hit = confined(resolve7(root, pkgMatch[1], pkgMatch[2]));
87758
87913
  if (hit) return hit;
87759
87914
  }
87760
87915
  return null;
87761
87916
  }
87762
87917
  const stripped = filename.startsWith("file://") ? filename.slice("file://".length) : filename;
87763
87918
  if (isAbsolute5(stripped)) return confined(stripped);
87764
- if (urdfDir) return confined(resolve6(urdfDir, stripped));
87919
+ if (urdfDir) return confined(resolve7(urdfDir, stripped));
87765
87920
  return null;
87766
87921
  }
87767
87922
  function inlineMeshImports2(doc, engine, urdfDir, packageRoots, confineRoot) {
@@ -91004,13 +91159,13 @@ ${issues.map((i) => i.problem).join("\n")}`
91004
91159
  }
91005
91160
  async function hasFfmpeg() {
91006
91161
  if (ffmpegAvailable !== null) return ffmpegAvailable;
91007
- ffmpegAvailable = await new Promise((resolve8) => {
91162
+ ffmpegAvailable = await new Promise((resolve9) => {
91008
91163
  try {
91009
91164
  const p = spawn("ffmpeg", ["-version"], { stdio: "ignore" });
91010
- p.on("error", () => resolve8(false));
91011
- p.on("exit", (code) => resolve8(code === 0));
91165
+ p.on("error", () => resolve9(false));
91166
+ p.on("exit", (code) => resolve9(code === 0));
91012
91167
  } catch {
91013
- resolve8(false);
91168
+ resolve9(false);
91014
91169
  }
91015
91170
  });
91016
91171
  return ffmpegAvailable;
@@ -91049,13 +91204,13 @@ function startMp4Encoder(width, height, fps) {
91049
91204
  const exitError = (code) => new Error(
91050
91205
  `ffmpeg exited ${code}: ${Buffer.concat(errChunks).toString().slice(-400)}`
91051
91206
  );
91052
- const done = new Promise((resolve8, reject) => {
91207
+ const done = new Promise((resolve9, reject) => {
91053
91208
  p.on("error", (e) => {
91054
91209
  failure2 = e;
91055
91210
  reject(e);
91056
91211
  });
91057
91212
  p.on("close", (code) => {
91058
- if (code === 0) resolve8(Buffer.concat(out));
91213
+ if (code === 0) resolve9(Buffer.concat(out));
91059
91214
  else {
91060
91215
  failure2 ??= exitError(code);
91061
91216
  reject(failure2);
@@ -91066,15 +91221,15 @@ function startMp4Encoder(width, height, fps) {
91066
91221
  });
91067
91222
  return {
91068
91223
  writeFrame(rgba) {
91069
- return new Promise((resolve8, reject) => {
91224
+ return new Promise((resolve9, reject) => {
91070
91225
  if (failure2) return reject(failure2);
91071
91226
  if (p.exitCode !== null || p.stdin.destroyed) {
91072
91227
  return reject(failure2 ?? exitError(p.exitCode));
91073
91228
  }
91074
- if (p.stdin.write(rgba)) return resolve8();
91229
+ if (p.stdin.write(rgba)) return resolve9();
91075
91230
  const onDrain = () => {
91076
91231
  p.removeListener("close", onClose);
91077
- resolve8();
91232
+ resolve9();
91078
91233
  };
91079
91234
  const onClose = () => {
91080
91235
  p.stdin.removeListener("drain", onDrain);
@@ -92634,8 +92789,8 @@ var init_server3 = __esm({
92634
92789
  resolveLoonAssets(doc, { base_dir }, engine);
92635
92790
  });
92636
92791
  PKG_VERSION = (() => {
92637
- if ("0.11.0-main.162") {
92638
- return "0.11.0-main.162";
92792
+ if ("0.11.0-main.164") {
92793
+ return "0.11.0-main.164";
92639
92794
  }
92640
92795
  try {
92641
92796
  const req = createRequire3(import.meta.url);
@@ -92644,8 +92799,8 @@ var init_server3 = __esm({
92644
92799
  return "0.0.0";
92645
92800
  }
92646
92801
  })();
92647
- BUILD_SHA = "7d787f258ea026c36d80b9cb137c3f0064fcce3c";
92648
- BUILD_TIME = "2026-11-11T19:59:54.944Z";
92802
+ BUILD_SHA = "f36361fa47f1a47d196b4d9a8bcdb421c1cc785f";
92803
+ BUILD_TIME = "2026-11-11T21:56:09.744Z";
92649
92804
  SHORT_SHA = BUILD_SHA === "unknown" ? "unknown" : BUILD_SHA.slice(0, 7);
92650
92805
  VERSION_WITH_BUILD = SHORT_SHA === "unknown" ? PKG_VERSION : `${PKG_VERSION}+${SHORT_SHA}`;
92651
92806
  INSTANCE_ID = randomUUID5().slice(0, 8);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vcad/mcp",
3
- "version": "0.11.0-main.162",
3
+ "version": "0.11.0-main.164",
4
4
  "description": "vcad MCP server — parametric CAD + PCB design tools for AI agents (self-contained: bundled server + kernel WASM)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "homepage": "https://github.com/muni-works/vcad",
21
21
  "vcadBuild": {
22
- "sha": "7d787f258ea026c36d80b9cb137c3f0064fcce3c",
23
- "builtAt": "2026-11-11T19:59:54.944Z"
22
+ "sha": "f36361fa47f1a47d196b4d9a8bcdb421c1cc785f",
23
+ "builtAt": "2026-11-11T21:56:09.744Z"
24
24
  }
25
25
  }
package/skill/SKILL.md CHANGED
@@ -81,6 +81,28 @@ Call `server_info` first.
81
81
  Tell the user and suggest restarting their agent (or updating vcad.app).
82
82
  - `durable: false` → sessions won't survive a restart; keep the loon source.
83
83
 
84
+ ## More than one vcad.app
85
+
86
+ Each running vcad.app registers itself in `~/.vcad/instances/<pid>.json`
87
+ (pid, port, open document, launch time); `~/.vcad/app.json` only points at
88
+ the newest. Every app-bridge result ends with `[vcad.app pid N · document]`
89
+ and carries `_meta["io.vcad/instance"]`: check it names the window you mean.
90
+ A `⚠ vcad.app CHANGED` notice means the app relaunched or another window took
91
+ over — re-check the open document before trusting earlier state.
92
+
93
+ Pick the window explicitly when more than one runs:
94
+
95
+ - `VCAD_APP_PID=<pid>` — that instance; fails loudly if it's gone.
96
+ - `VCAD_APP_URL=http://127.0.0.1:<port>/mcp` — that endpoint, verbatim.
97
+ - `VCAD_APP_DOCUMENT=<path>` — the instance with that file open.
98
+ - otherwise the newest, with a warning listing the others. The choice is
99
+ pinned for the client's lifetime; a later launch never steals it.
100
+ - `VCAD_APP=0` — `@vcad/mcp` doesn't link to any app.
101
+
102
+ **Never `pkill`/`killall` vcad.app by pattern.** Other sessions may be driving
103
+ other windows, and to them it looks like a crash. Quit only an instance you
104
+ launched, by its pid (`kill <pid>`, from `~/.vcad/instances/`).
105
+
84
106
  ## Ground rules
85
107
 
86
108
  - vcad has no hosted services. Never send the user to a website to view,