@vcad/mcp 0.10.0-main.133 → 0.10.0-main.135

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 (3) hide show
  1. package/README.md +1 -1
  2. package/index.mjs +203 -50
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -6,4 +6,4 @@ vcad's MCP server as a self-contained bundle (server + BRep kernel WASM).
6
6
  { "mcpServers": { "vcad": { "command": "npx", "args": ["-y", "@vcad/mcp"] } } }
7
7
  ```
8
8
 
9
- Built from ed9b701622258f961cdbb3fa2c2430c283a8fc5b at 2026-10-21T17:47:36.984Z. Source: https://github.com/ecto/vcad
9
+ Built from b446cf64b98e8aa00e81db540574199f61784778 at 2026-10-22T11:55:20.128Z. Source: https://github.com/ecto/vcad
package/index.mjs CHANGED
@@ -10867,18 +10867,95 @@ var init_delta = __esm({
10867
10867
  }
10868
10868
  });
10869
10869
 
10870
+ // ../engine/dist/scene-convert.js
10871
+ function wasmResultToScene(result) {
10872
+ const toMesh = (m) => ({
10873
+ positions: m.positions instanceof Float32Array ? m.positions : new Float32Array(m.positions),
10874
+ indices: m.indices instanceof Uint32Array ? m.indices : new Uint32Array(m.indices),
10875
+ normals: m.normals ? m.normals instanceof Float32Array ? m.normals : new Float32Array(m.normals) : void 0,
10876
+ faceKinds: m.faceKinds ? m.faceKinds instanceof Uint8Array ? m.faceKinds : new Uint8Array(m.faceKinds) : void 0
10877
+ });
10878
+ return {
10879
+ parts: result.parts.map((p) => ({ mesh: toMesh(p.mesh), material: p.material })),
10880
+ partDefs: result.partDefs?.map((pd) => ({ id: pd.id, mesh: toMesh(pd.mesh) })),
10881
+ instances: result.instances?.map((inst) => ({
10882
+ instanceId: inst.instance_id,
10883
+ partDefId: inst.part_def_id,
10884
+ name: inst.name,
10885
+ mesh: toMesh(inst.mesh),
10886
+ material: inst.material,
10887
+ transform: inst.transform
10888
+ })),
10889
+ clashes: result.clashes.map(toMesh),
10890
+ failures: result.failures,
10891
+ timing: result.timing
10892
+ };
10893
+ }
10894
+ function postProcessEmbroidery(scene, doc, kernel2) {
10895
+ const visibleRoots = doc.roots.filter((e) => e.visible !== false);
10896
+ let changed = false;
10897
+ const parts = scene.parts.map((p, i) => {
10898
+ if (p.mesh.positions.length === 0 && i < visibleRoots.length) {
10899
+ const emb = findEmbroideryPattern(visibleRoots[i].root, doc.nodes);
10900
+ if (emb) {
10901
+ changed = true;
10902
+ const baseMesh = embroideryPatternToMeshWithKernel(emb.pattern, kernel2);
10903
+ const mesh = transformMeshWithKernel(baseMesh, emb.transform, kernel2);
10904
+ return { mesh, material: p.material };
10905
+ }
10906
+ }
10907
+ return p;
10908
+ });
10909
+ return changed ? { ...scene, parts } : scene;
10910
+ }
10911
+ function postProcessSheetMetal(scene, doc, kernel2) {
10912
+ const visibleRoots = doc.roots.filter((e) => e.visible !== false);
10913
+ let changed = false;
10914
+ const parts = scene.parts.map((p, i) => {
10915
+ if (p.mesh.positions.length === 0 && i < visibleRoots.length) {
10916
+ try {
10917
+ const smPart = resolveSheetMetalPart(visibleRoots[i].root, doc.nodes, kernel2);
10918
+ if (smPart) {
10919
+ changed = true;
10920
+ return { ...smPart, material: p.material };
10921
+ }
10922
+ } catch (e) {
10923
+ console.warn(`[worker] sheet-metal eval failed at root[${i}]:`, e);
10924
+ }
10925
+ }
10926
+ return p;
10927
+ });
10928
+ return changed ? { ...scene, parts } : scene;
10929
+ }
10930
+ function postProcessScene(scene, doc, kernel2) {
10931
+ return postProcessSheetMetal(postProcessEmbroidery(scene, doc, kernel2), doc, kernel2);
10932
+ }
10933
+ var init_scene_convert = __esm({
10934
+ "../engine/dist/scene-convert.js"() {
10935
+ "use strict";
10936
+ init_evaluate();
10937
+ }
10938
+ });
10939
+
10870
10940
  // ../engine/dist/engine-host.js
10941
+ function resetEvaluator(host) {
10942
+ host.evaluator?.free?.();
10943
+ host.evaluator = null;
10944
+ }
10871
10945
  function handleEngineRequest(host, req) {
10872
10946
  try {
10873
10947
  let envelope;
10874
10948
  let bytes;
10949
+ let scene;
10875
10950
  switch (req.op) {
10876
10951
  case "init":
10952
+ resetEvaluator(host);
10877
10953
  host.engine?.free();
10878
10954
  host.engine = req.replica !== void 0 && host.Engine.withReplica ? host.Engine.withReplica(req.replica) : new host.Engine();
10879
10955
  envelope = host.engine.stateEnvelope();
10880
10956
  break;
10881
10957
  case "load":
10958
+ resetEvaluator(host);
10882
10959
  host.engine?.free();
10883
10960
  host.engine = host.Engine.load(req.bytes);
10884
10961
  envelope = host.engine.stateEnvelope();
@@ -10887,13 +10964,30 @@ function handleEngineRequest(host, req) {
10887
10964
  const engine2 = host.engine;
10888
10965
  if (!engine2)
10889
10966
  return { ok: false, message: "engine not initialized" };
10967
+ if ((req.op === "apply" || req.op === "applyTool") && req.expectHash !== void 0 && engine2.stateHash() !== req.expectHash) {
10968
+ return {
10969
+ ok: true,
10970
+ stale: true,
10971
+ hash: engine2.stateHash(),
10972
+ canUndo: engine2.can_undo(),
10973
+ canRedo: engine2.can_redo()
10974
+ };
10975
+ }
10890
10976
  if (req.op === "apply")
10891
10977
  envelope = engine2.applyDelta(JSON.stringify(req.command));
10892
10978
  else if (req.op === "applyTool")
10893
10979
  envelope = engine2.applyToolDelta(req.tool, JSON.stringify(req.args));
10894
10980
  else if (req.op === "state")
10895
10981
  envelope = engine2.stateEnvelope();
10896
- else
10982
+ else if (req.op === "evaluate") {
10983
+ if (!engine2.evaluateIncremental || !host.Evaluator) {
10984
+ return { ok: false, message: "engine host cannot evaluate (kernel too old)" };
10985
+ }
10986
+ host.evaluator ??= new host.Evaluator();
10987
+ const overlay = req.overlay ?? {};
10988
+ const hasOverlay = Object.keys(overlay.parameters ?? {}).length > 0 || Object.keys(overlay.bindings ?? {}).length > 0;
10989
+ scene = wasmResultToScene(engine2.evaluateIncremental(host.evaluator, hasOverlay ? JSON.stringify(overlay) : "", req.skipClashDetection ?? false));
10990
+ } else
10897
10991
  bytes = engine2.save();
10898
10992
  }
10899
10993
  }
@@ -10902,6 +10996,7 @@ function handleEngineRequest(host, req) {
10902
10996
  ok: true,
10903
10997
  envelope,
10904
10998
  bytes,
10999
+ scene,
10905
11000
  hash: engine.stateHash(),
10906
11001
  canUndo: engine.can_undo(),
10907
11002
  canRedo: engine.can_redo()
@@ -10910,16 +11005,31 @@ function handleEngineRequest(host, req) {
10910
11005
  return { ok: false, message: e instanceof Error ? e.message : String(e) };
10911
11006
  }
10912
11007
  }
10913
- function inProcessTransport(Engine2) {
10914
- const host = { Engine: Engine2, engine: null };
11008
+ function inProcessTransport(Engine2, Evaluator) {
11009
+ const host = { Engine: Engine2, engine: null, Evaluator };
10915
11010
  return {
10916
11011
  request: async (req) => handleEngineRequest(host, req),
10917
11012
  dispose: () => {
11013
+ resetEvaluator(host);
10918
11014
  host.engine?.free();
10919
11015
  host.engine = null;
10920
11016
  }
10921
11017
  };
10922
11018
  }
11019
+ function sharedEngineTransport(getEngine, Evaluator) {
11020
+ const ownerOnly = function() {
11021
+ throw new Error("shared engine: the owner constructs engines");
11022
+ };
11023
+ const host = { Engine: ownerOnly, engine: null, Evaluator };
11024
+ return {
11025
+ request: async (req) => {
11026
+ host.engine = getEngine();
11027
+ const r = req.op === "init" || req.op === "load" ? { op: "state" } : req;
11028
+ return handleEngineRequest(host, r);
11029
+ },
11030
+ dispose: () => resetEvaluator(host)
11031
+ };
11032
+ }
10923
11033
  function workerTransport(worker) {
10924
11034
  let next = 0;
10925
11035
  const pending2 = /* @__PURE__ */ new Map();
@@ -10942,6 +11052,7 @@ var init_engine_host = __esm({
10942
11052
  "../engine/dist/engine-host.js"() {
10943
11053
  "use strict";
10944
11054
  init_delta();
11055
+ init_scene_convert();
10945
11056
  EngineHostError = class extends Error {
10946
11057
  constructor(message) {
10947
11058
  super(message);
@@ -10989,6 +11100,25 @@ var init_engine_host = __esm({
10989
11100
  applyTool(tool, args) {
10990
11101
  return this.delta({ op: "applyTool", tool, args });
10991
11102
  }
11103
+ /**
11104
+ * Evaluate the hosted document where it lives (the worker, when the
11105
+ * engine is in one) through the host's incremental evaluator: the
11106
+ * document is never serialized and only what changed since the last
11107
+ * evaluation is redone. Ordered after every command already sent, so the
11108
+ * scene is of the state those commands produced. Pass `kernel` to run the
11109
+ * TS post-passes (embroidery, sheet metal) against this client's view.
11110
+ */
11111
+ evaluate(opts = {}) {
11112
+ return this.enqueue(async () => {
11113
+ const r = this.check(await this.transport.request({
11114
+ op: "evaluate",
11115
+ overlay: opts.overlay,
11116
+ skipClashDetection: opts.skipClashDetection
11117
+ }));
11118
+ const scene = r.scene;
11119
+ return opts.kernel && this.view ? postProcessScene(scene, this.view.document, opts.kernel) : scene;
11120
+ });
11121
+ }
10992
11122
  /** Snapshot the engine (document + history). */
10993
11123
  save() {
10994
11124
  return this.enqueue(async () => {
@@ -11009,10 +11139,17 @@ var init_engine_host = __esm({
11009
11139
  }
11010
11140
  delta(req) {
11011
11141
  return this.enqueue(async () => {
11012
- const base = this.view;
11013
- if (!base)
11142
+ if (!this.view)
11014
11143
  throw new EngineHostError("engine not initialized");
11015
- const r = this.check(await this.transport.request(req));
11144
+ let r = this.check(await this.transport.request({ ...req, expectHash: this.flags.hash }));
11145
+ if (r.stale) {
11146
+ const full = this.check(await this.transport.request({ op: "state" }));
11147
+ this.view = stateFromEnvelope(full.envelope);
11148
+ r = this.check(await this.transport.request({ ...req, expectHash: this.flags.hash }));
11149
+ if (r.stale)
11150
+ throw new EngineHostError("engine changed during resync");
11151
+ }
11152
+ const base = this.view;
11016
11153
  const delta = parseDeltaEnvelope(r.envelope);
11017
11154
  const { state, changes } = applyEngineDelta(base, delta);
11018
11155
  this.view = state;
@@ -12310,6 +12447,7 @@ __export(dist_exports, {
12310
12447
  searchEcadParts: () => searchEcadParts,
12311
12448
  searchParts: () => searchParts,
12312
12449
  semanticDiff: () => semanticDiff,
12450
+ sharedEngineTransport: () => sharedEngineTransport,
12313
12451
  solveDesignConstraints: () => solveDesignConstraints2,
12314
12452
  solveForwardKinematics: () => solveForwardKinematics2,
12315
12453
  stateFromEnvelope: () => stateFromEnvelope,
@@ -31847,6 +31985,7 @@ var init_document_store = __esm({
31847
31985
  init_esm2();
31848
31986
  init_wasm_singleton2();
31849
31987
  init_engine_command();
31988
+ init_dist();
31850
31989
  init_sketch_constraint_persist();
31851
31990
  init_dist2();
31852
31991
  init_dist2();
@@ -31873,6 +32012,27 @@ var init_document_store = __esm({
31873
32012
  // CRDT bridge state
31874
32013
  _crdtEngine: null,
31875
32014
  _crdtEngineClass: null,
32015
+ _engineClient: new DocumentEngineClient(sharedEngineTransport(() => get()._crdtEngine)),
32016
+ dispatch: async (command) => {
32017
+ const readOnly = useUiStore.getState().readOnlyShare;
32018
+ if (readOnly) {
32019
+ if (typeof window !== "undefined") {
32020
+ window.dispatchEvent(new CustomEvent("vcad:fork-prompt", { detail: readOnly }));
32021
+ }
32022
+ return void 0;
32023
+ }
32024
+ const client = get()._engineClient;
32025
+ if (!client.state)
32026
+ await client.init();
32027
+ const outcome = await client.apply(command);
32028
+ for (const w of outcome.warnings)
32029
+ console.warn(`[vcad] ${w}`);
32030
+ set(applyLegacyResult({
32031
+ document: outcome.state.document,
32032
+ parts: outcome.state.parts
32033
+ }));
32034
+ return outcome.createdFeatureId;
32035
+ },
31876
32036
  _initCrdt: (EngineClass) => {
31877
32037
  const existing = get()._crdtEngine;
31878
32038
  const existingClass = get()._crdtEngineClass;
@@ -31977,10 +32137,8 @@ var init_document_store = __esm({
31977
32137
  return "";
31978
32138
  }
31979
32139
  },
31980
- removePart: (partId) => {
31981
- const engine = get()._crdtEngine;
31982
- const result = applyCommand(engine, { type: "delete_feature", id: partId });
31983
- set(applyApiResult(result));
32140
+ removePart: async (partId) => {
32141
+ await get().dispatch({ type: "delete_feature", id: partId });
31984
32142
  },
31985
32143
  setTranslation: (partId, offset) => {
31986
32144
  const engine = get()._crdtEngine;
@@ -32064,10 +32222,8 @@ var init_document_store = __esm({
32064
32222
  const result = applyCommand(engine, { type: "set_param", id: partId, key: key3, value });
32065
32223
  set(applyLegacyResult(result));
32066
32224
  },
32067
- updateBooleanType: (partId, newType) => {
32068
- const engine = get()._crdtEngine;
32069
- const result = applyCommand(engine, { type: "set_param", id: partId, key: "boolean_type", value: crdtStr(newType) });
32070
- set(applyLegacyResult(result));
32225
+ updateBooleanType: async (partId, newType) => {
32226
+ await get().dispatch({ type: "set_param", id: partId, key: "boolean_type", value: crdtStr(newType) });
32071
32227
  },
32072
32228
  applyBoolean: (type, partIdA, partIdB) => {
32073
32229
  const engine = get()._crdtEngine;
@@ -32341,9 +32497,8 @@ var init_document_store = __esm({
32341
32497
  return null;
32342
32498
  }
32343
32499
  },
32344
- setThreadColor: (nodeId, threadIdx, color) => {
32500
+ setThreadColor: async (nodeId, threadIdx, color) => {
32345
32501
  const state = get();
32346
- const engine = state._crdtEngine;
32347
32502
  const part = state.parts.find((p) => p.kind === "embroidery-pattern" && p.patternNodeId === nodeId);
32348
32503
  if (!part)
32349
32504
  return;
@@ -32353,12 +32508,10 @@ var init_document_store = __esm({
32353
32508
  const d = structuredClone(design);
32354
32509
  if (d.threads[threadIdx])
32355
32510
  d.threads[threadIdx].color = color;
32356
- const result = applyCommand(engine, { type: "set_param", id: part.id, key: "design", value: crdtStr(JSON.stringify(d)) });
32357
- set(applyLegacyResult(result));
32511
+ await get().dispatch({ type: "set_param", id: part.id, key: "design", value: crdtStr(JSON.stringify(d)) });
32358
32512
  },
32359
- setThreadName: (nodeId, threadIdx, name) => {
32513
+ setThreadName: async (nodeId, threadIdx, name) => {
32360
32514
  const state = get();
32361
- const engine = state._crdtEngine;
32362
32515
  const part = state.parts.find((p) => p.kind === "embroidery-pattern" && p.patternNodeId === nodeId);
32363
32516
  if (!part)
32364
32517
  return;
@@ -32368,8 +32521,7 @@ var init_document_store = __esm({
32368
32521
  const d = structuredClone(design);
32369
32522
  if (d.threads[threadIdx])
32370
32523
  d.threads[threadIdx].name = name;
32371
- const result = applyCommand(engine, { type: "set_param", id: part.id, key: "design", value: crdtStr(JSON.stringify(d)) });
32372
- set(applyLegacyResult(result));
32524
+ await get().dispatch({ type: "set_param", id: part.id, key: "design", value: crdtStr(JSON.stringify(d)) });
32373
32525
  },
32374
32526
  setStitchGroupFillParams: (nodeId, groupIdx, params) => {
32375
32527
  const state = get();
@@ -32604,15 +32756,11 @@ var init_document_store = __esm({
32604
32756
  set(applyApiResult(result));
32605
32757
  return result.createdFeatureId ?? null;
32606
32758
  },
32607
- setPartMaterial: (partId, materialKey) => {
32608
- const engine = get()._crdtEngine;
32609
- const result = applyCommand(engine, { type: "set_material", id: partId, material: materialKey });
32610
- set(applyApiResult(result));
32759
+ setPartMaterial: async (partId, materialKey) => {
32760
+ await get().dispatch({ type: "set_material", id: partId, material: materialKey });
32611
32761
  },
32612
- renamePart: (partId, name) => {
32613
- const engine = get()._crdtEngine;
32614
- const result = applyCommand(engine, { type: "rename", id: partId, name });
32615
- set(applyApiResult(result));
32762
+ renamePart: async (partId, name) => {
32763
+ await get().dispatch({ type: "rename", id: partId, name });
32616
32764
  },
32617
32765
  undo: () => {
32618
32766
  const engine = get()._crdtEngine;
@@ -32645,10 +32793,8 @@ var init_document_store = __esm({
32645
32793
  const result = applyCommand(engine, { type: "set_param", id: instanceId, key: "transform", value: crdtStr(JSON.stringify(transform2)) });
32646
32794
  set(applyLegacyResult(result));
32647
32795
  },
32648
- setInstanceMaterial: (instanceId, materialKey) => {
32649
- const engine = get()._crdtEngine;
32650
- const result = applyCommand(engine, { type: "set_param", id: instanceId, key: "material", value: crdtStr(materialKey) });
32651
- set(applyLegacyResult(result));
32796
+ setInstanceMaterial: async (instanceId, materialKey) => {
32797
+ await get().dispatch({ type: "set_param", id: instanceId, key: "material", value: crdtStr(materialKey) });
32652
32798
  },
32653
32799
  setJointState: (jointId, jointState) => {
32654
32800
  const engine = get()._crdtEngine;
@@ -32719,10 +32865,8 @@ var init_document_store = __esm({
32719
32865
  const result = applyCommand(engine, { type: "delete_feature", id: instanceId });
32720
32866
  set(applyApiResult(result));
32721
32867
  },
32722
- deleteJoint: (jointId) => {
32723
- const engine = get()._crdtEngine;
32724
- const result = applyCommand(engine, { type: "delete_feature", id: jointId });
32725
- set(applyApiResult(result));
32868
+ deleteJoint: async (jointId) => {
32869
+ await get().dispatch({ type: "delete_feature", id: jointId });
32726
32870
  },
32727
32871
  setGroundInstance: (instanceId) => {
32728
32872
  const state = get();
@@ -32738,10 +32882,8 @@ var init_document_store = __esm({
32738
32882
  const result = applyCommand(engine, { type: "set_param", id: instanceId, key: "is_ground", value: crdtBool(true) });
32739
32883
  set(applyLegacyResult(result));
32740
32884
  },
32741
- renameInstance: (instanceId, name) => {
32742
- const engine = get()._crdtEngine;
32743
- const result = applyCommand(engine, { type: "set_param", id: instanceId, key: "name", value: crdtStr(name) });
32744
- set(applyLegacyResult(result));
32885
+ renameInstance: async (instanceId, name) => {
32886
+ await get().dispatch({ type: "set_param", id: instanceId, key: "name", value: crdtStr(name) });
32745
32887
  },
32746
32888
  setDocumentMeta: (id, name) => {
32747
32889
  set({ documentId: id, documentName: name });
@@ -32800,10 +32942,8 @@ var init_document_store = __esm({
32800
32942
  set(applyApiResult(envelope.result));
32801
32943
  return envelope;
32802
32944
  },
32803
- setPartVisible: (partId, visible) => {
32804
- const engine = get()._crdtEngine;
32805
- const result = applyCommand(engine, { type: "set_visible", id: partId, visible });
32806
- set(applyApiResult(result));
32945
+ setPartVisible: async (partId, visible) => {
32946
+ await get().dispatch({ type: "set_visible", id: partId, visible });
32807
32947
  },
32808
32948
  reorderPart: (partId, newIndex) => {
32809
32949
  const state = get();
@@ -41715,6 +41855,19 @@ var init_CHANGELOG = __esm({
41715
41855
  "lighting"
41716
41856
  ]
41717
41857
  },
41858
+ {
41859
+ id: "2026-10-21-photoreal-look-textures",
41860
+ version: "0.10.0",
41861
+ date: "2026-10-21",
41862
+ category: "feat",
41863
+ title: "Material looks and wood/fabric textures in photoreal",
41864
+ summary: "--look applies render-time material overrides by material or part (colours, roughness, emission) plus procedural oak, fabric and triplanar image textures.",
41865
+ features: [
41866
+ "photoreal",
41867
+ "render",
41868
+ "materials"
41869
+ ]
41870
+ },
41718
41871
  {
41719
41872
  id: "2026-10-21-photoreal-focus-dist",
41720
41873
  version: "0.10.0",
@@ -87126,8 +87279,8 @@ var init_server3 = __esm({
87126
87279
  init_order_feed();
87127
87280
  init_animate();
87128
87281
  PKG_VERSION = (() => {
87129
- if ("0.10.0-main.133") {
87130
- return "0.10.0-main.133";
87282
+ if ("0.10.0-main.135") {
87283
+ return "0.10.0-main.135";
87131
87284
  }
87132
87285
  try {
87133
87286
  const req = createRequire2(import.meta.url);
@@ -87136,8 +87289,8 @@ var init_server3 = __esm({
87136
87289
  return "0.0.0";
87137
87290
  }
87138
87291
  })();
87139
- BUILD_SHA = "ed9b701622258f961cdbb3fa2c2430c283a8fc5b";
87140
- BUILD_TIME = "2026-10-21T17:47:36.984Z";
87292
+ BUILD_SHA = "b446cf64b98e8aa00e81db540574199f61784778";
87293
+ BUILD_TIME = "2026-10-22T11:55:20.128Z";
87141
87294
  SHORT_SHA = BUILD_SHA === "unknown" ? "unknown" : BUILD_SHA.slice(0, 7);
87142
87295
  VERSION_WITH_BUILD = SHORT_SHA === "unknown" ? PKG_VERSION : `${PKG_VERSION}+${SHORT_SHA}`;
87143
87296
  INSTANCE_ID = randomUUID7().slice(0, 8);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vcad/mcp",
3
- "version": "0.10.0-main.133",
3
+ "version": "0.10.0-main.135",
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://vcad.io",
21
21
  "vcadBuild": {
22
- "sha": "ed9b701622258f961cdbb3fa2c2430c283a8fc5b",
23
- "builtAt": "2026-10-21T17:47:36.984Z"
22
+ "sha": "b446cf64b98e8aa00e81db540574199f61784778",
23
+ "builtAt": "2026-10-22T11:55:20.128Z"
24
24
  }
25
25
  }