wave3d-agent-sdk 0.2.13 → 0.2.15

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/dist/cli.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { mkdir as mkdir3, readFile as readFile2, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
4
+ import { mkdir as mkdir4, readFile as readFile2, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
5
5
  import { existsSync as existsSync2 } from "node:fs";
6
6
  import { execFile } from "node:child_process";
7
7
  import { createHash as createHash6 } from "node:crypto";
8
8
  import { homedir as homedir2 } from "node:os";
9
- import path4 from "node:path";
9
+ import path5 from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import { promisify } from "node:util";
12
12
  import JSZip from "jszip";
@@ -14,7 +14,7 @@ import MiniSearch2 from "minisearch";
14
14
 
15
15
  // ../../scripts/wavegenie-sdk/server.ts
16
16
  import { createServer } from "node:http";
17
- import { randomUUID as randomUUID4 } from "node:crypto";
17
+ import { randomUUID as randomUUID5 } from "node:crypto";
18
18
  import { createReadStream } from "node:fs";
19
19
 
20
20
  // ../../src/lib/waveStudio/aiAssist/core/resultEnvelope.ts
@@ -182,10 +182,10 @@ var WaveGenieBridgeCommandBroker = class {
182
182
  };
183
183
 
184
184
  // ../../scripts/wavegenie-sdk/mcp.ts
185
- import { randomUUID } from "node:crypto";
186
- import { mkdir, writeFile } from "node:fs/promises";
187
- import { tmpdir } from "node:os";
188
- import path2 from "node:path";
185
+ import { randomUUID as randomUUID2 } from "node:crypto";
186
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
187
+ import { tmpdir as tmpdir2 } from "node:os";
188
+ import path3 from "node:path";
189
189
 
190
190
  // ../../wave-engine/dist/src/core/assetPipeline/registry/gaussianSplatAssetTypes.js
191
191
  var WAVE_GAUSSIAN_SPLAT_RUNTIME_ASSET_ROOT = "/defaultAssets/runtime/adobe-spz";
@@ -531,6 +531,7 @@ var PROJECT_ASSET_ACCESSOR_BUCKETS = [
531
531
  "materials",
532
532
  "textures",
533
533
  "animations",
534
+ "vertexAnimations",
534
535
  "poses",
535
536
  "audios",
536
537
  "instruments",
@@ -555,7 +556,6 @@ var WaveModelPartScope;
555
556
  (function(WaveModelPartScope2) {
556
557
  WaveModelPartScope2["Available"] = "available";
557
558
  WaveModelPartScope2["Created"] = "created";
558
- WaveModelPartScope2["Promoted"] = "promoted";
559
559
  WaveModelPartScope2["All"] = "all";
560
560
  })(WaveModelPartScope || (WaveModelPartScope = {}));
561
561
  var WaveModelPartLifecycleState;
@@ -25422,12 +25422,12 @@ var Tools = class {
25422
25422
  * @param path defines the path to use
25423
25423
  * @returns the filename
25424
25424
  */
25425
- static GetFilename(path5) {
25426
- const index = path5.lastIndexOf("/");
25425
+ static GetFilename(path6) {
25426
+ const index = path6.lastIndexOf("/");
25427
25427
  if (index < 0) {
25428
- return path5;
25428
+ return path6;
25429
25429
  }
25430
- return path5.substring(index + 1);
25430
+ return path6.substring(index + 1);
25431
25431
  }
25432
25432
  /**
25433
25433
  * Extracts the "folder" part of a path (everything before the filename).
@@ -41609,7 +41609,7 @@ var DEFAULT_PHYSICS = {
41609
41609
  };
41610
41610
 
41611
41611
  // ../../src/lib/waveStudio/assets/contracts/waveStudioEngineAssetContracts.ts
41612
- var WAVE_STUDIO_ENGINE_ACCESSOR_BUCKET_EXTENSIONS = ["cubeMaps", "poses"];
41612
+ var WAVE_STUDIO_ENGINE_ACCESSOR_BUCKET_EXTENSIONS = ["cubeMaps", "poses", "vertexAnimations"];
41613
41613
  function withAccessorBucketExtensions(buckets) {
41614
41614
  const existingBuckets = new Set(buckets);
41615
41615
  return Object.freeze([
@@ -41996,23 +41996,36 @@ var WAVE_STUDIO_PRIMARY_ASSET_BUCKET_SPECS = Object.freeze([
41996
41996
  },
41997
41997
  {
41998
41998
  bucket: "animations",
41999
- assetTypes: ["animation", "vertexAnimation"],
41999
+ assetTypes: ["animation"],
42000
42000
  label: "Animation",
42001
42001
  pluralLabel: "Animations",
42002
42002
  defaultExtension: ".anim.json",
42003
42003
  baseCategoryAliases: [
42004
42004
  "animations",
42005
42005
  "animation",
42006
- "fbx_animation",
42006
+ "fbx_animation"
42007
+ ],
42008
+ pathFolderAliases: ["animations"],
42009
+ pathExtensions: ["anim.json", "glb", "gltf", "fbx"],
42010
+ assetManagerAlias: "animationNames",
42011
+ monaco: { interfaceName: "AnimationNames", propertyName: "animationNames" }
42012
+ },
42013
+ {
42014
+ bucket: "vertexAnimations",
42015
+ assetTypes: ["vertexAnimation"],
42016
+ label: "Vertex Animation",
42017
+ pluralLabel: "Vertex Animations",
42018
+ defaultExtension: ".vat.json",
42019
+ baseCategoryAliases: [
42007
42020
  "vertexanimation",
42008
42021
  "vertexanimations",
42009
42022
  "vertex_animation",
42010
42023
  "vat"
42011
42024
  ],
42012
- pathFolderAliases: ["animations"],
42013
- pathExtensions: ["anim.json", "vat.json", "glb", "gltf", "fbx"],
42014
- assetManagerAlias: "animationNames",
42015
- monaco: { interfaceName: "AnimationNames", propertyName: "animationNames" }
42025
+ pathFolderAliases: ["vertexAnimations", "vertexanimations"],
42026
+ pathExtensions: ["vat.json"],
42027
+ assetManagerAlias: "vertexAnimationNames",
42028
+ monaco: { interfaceName: "VertexAnimationNames", propertyName: "vertexAnimationNames" }
42016
42029
  },
42017
42030
  {
42018
42031
  bucket: "poses",
@@ -42520,8 +42533,8 @@ function normalizeWaveGenieAssetUploadRequest(request) {
42520
42533
 
42521
42534
  // ../../src/lib/waveStudio/aiAssist/bridge/localAgentSdkContract.ts
42522
42535
  var WAVE3D_AGENT_SDK_PACKAGE_NAME = "wave3d-agent-sdk";
42523
- var WAVE3D_AGENT_SDK_REQUIRED_VERSION = "0.2.13";
42524
- var WAVE3D_AGENT_SDK_REQUIRED_BUILD = "agent-sdk-20260625.1";
42536
+ var WAVE3D_AGENT_SDK_REQUIRED_VERSION = "0.2.15";
42537
+ var WAVE3D_AGENT_SDK_REQUIRED_BUILD = "agent-sdk-20260627.1";
42525
42538
  var WAVE3D_AGENT_SDK_PACKAGE_SPEC = `${WAVE3D_AGENT_SDK_PACKAGE_NAME}@${WAVE3D_AGENT_SDK_REQUIRED_VERSION}`;
42526
42539
  var WAVE3D_AGENT_SDK_START_COMMAND = `npx -y ${WAVE3D_AGENT_SDK_PACKAGE_SPEC} start`;
42527
42540
  var WAVE3D_AGENT_SDK_TOKEN_ENV_VAR = "WAVE3D_MCP_TOKEN";
@@ -42660,7 +42673,12 @@ var WAVE_MCP_TOOL_ALIAS_CANONICAL_NAMES = {
42660
42673
  mcp_status: "get_wave_mcp_health",
42661
42674
  get_mcp_health: "get_wave_mcp_health",
42662
42675
  get_mcp_status: "get_wave_mcp_health",
42663
- get_wave_mcp_status: "get_wave_mcp_health"
42676
+ get_wave_mcp_status: "get_wave_mcp_health",
42677
+ generate_wave_3d_model: "create_wave_3d_modeling_job",
42678
+ create_wave_3d_model: "create_wave_3d_modeling_job",
42679
+ create_wave_3d_model_from_screenshots: "create_wave_3d_modeling_job",
42680
+ make_wave_3d_model_from_screenshot: "create_wave_3d_modeling_job",
42681
+ scaffold_wave_3d_modeling_job: "create_wave_3d_modeling_job"
42664
42682
  };
42665
42683
  function canonicalizeWaveMcpToolName(toolName) {
42666
42684
  return WAVE_MCP_TOOL_ALIAS_CANONICAL_NAMES[toolName] ?? toolName;
@@ -42802,6 +42820,7 @@ var WAVE_MCP_TOOL_CONTRACTS = {
42802
42820
  get_wave_runtime_entity_snapshot: { workMode: "observe", domain: "runtime", safety: "readOnly", targetPolicy: "none", requiresTaskRoute: false, supportsTaskRoute: true },
42803
42821
  list_wave_runtime_markers: { workMode: "observe", domain: "marker", safety: "readOnly", targetPolicy: "none", requiresTaskRoute: false, supportsTaskRoute: true },
42804
42822
  capture_wave_screenshot: { workMode: "observe", domain: "visual", safety: "readOnly", targetPolicy: "none", requiresTaskRoute: false, supportsTaskRoute: true },
42823
+ create_wave_3d_modeling_job: { workMode: "operate", domain: "modeling", safety: "workspaceOperation", targetPolicy: "none", requiresTaskRoute: false, supportsTaskRoute: true, directIntentKind: "asset.manage", directEditIntent: "modifyAssets" },
42805
42824
  query_wave_api: { workMode: "author", domain: "code", safety: "authoringMutation", targetPolicy: "none", requiresTaskRoute: true, supportsTaskRoute: true },
42806
42825
  edit_wave_file: { workMode: "author", domain: "code", safety: "authoringMutation", targetPolicy: "exactPathRequired", requiresTaskRoute: true, supportsTaskRoute: true },
42807
42826
  apply_wave_patch: { workMode: "author", domain: "code", safety: "authoringMutation", targetPolicy: "exactPathRequired", requiresTaskRoute: true, supportsTaskRoute: true },
@@ -42944,6 +42963,9 @@ var WAVE_MCP_ASSET_STAGING_STORE_TOOL_NAMES = /* @__PURE__ */ new Set([
42944
42963
  "get_wave_asset_upload_status",
42945
42964
  "abort_wave_asset_upload"
42946
42965
  ]);
42966
+ var WAVE_MCP_LOCAL_SDK_WORKSPACE_TOOL_NAMES = /* @__PURE__ */ new Set([
42967
+ "create_wave_3d_modeling_job"
42968
+ ]);
42947
42969
  function getWaveMcpToolExecutionScope(toolName) {
42948
42970
  const canonicalToolName = canonicalizeWaveMcpToolName(toolName);
42949
42971
  if (WAVE_MCP_SESSION_DISCOVERY_TOOL_NAMES.has(canonicalToolName)) return "sessionDiscovery";
@@ -42952,6 +42974,7 @@ function getWaveMcpToolExecutionScope(toolName) {
42952
42974
  if (WAVE_MCP_REFERENCE_LOOKUP_TOOL_NAMES.has(canonicalToolName)) return "referenceLookup";
42953
42975
  if (canonicalToolName === "get_wave_command_result") return "commandStatus";
42954
42976
  if (WAVE_MCP_ASSET_STAGING_STORE_TOOL_NAMES.has(canonicalToolName)) return "assetStagingStore";
42977
+ if (WAVE_MCP_LOCAL_SDK_WORKSPACE_TOOL_NAMES.has(canonicalToolName)) return "localSdkWorkspace";
42955
42978
  return "pairedStudioSession";
42956
42979
  }
42957
42980
  function waveMcpToolRequiresStudioSession(toolName) {
@@ -42973,6 +42996,7 @@ function waveMcpToolFamily(toolName) {
42973
42996
  const contract = getWaveMcpToolContract(toolName);
42974
42997
  if (contract?.domain === "vfs") return "studio.vfs";
42975
42998
  if (contract?.domain === "asset") return "studio.assets";
42999
+ if (contract?.domain === "modeling") return "wave.modeling";
42976
43000
  if (contract?.domain === "project") return "studio.project";
42977
43001
  if (contract?.domain === "preview") return "studio.preview";
42978
43002
  if (contract?.domain === "runtime") return "studio.preview";
@@ -43548,7 +43572,7 @@ function buildWaveMcpTaskRouteRecommendedNextToolCall(route) {
43548
43572
  if (route.editIntent === "modifyAssets") {
43549
43573
  return {
43550
43574
  tool: "find_wave_assets_by_category | upload_wave_asset | create_wave_asset_upload | rename_wave_asset | delete_wave_asset",
43551
- arguments: { category: "<models|textures|materials|audios|hdr|animations|...>" },
43575
+ arguments: { category: "<models|textures|materials|audios|hdr|animations|vertexAnimations|...>" },
43552
43576
  note: "Use category first to reduce search space."
43553
43577
  };
43554
43578
  }
@@ -43720,8 +43744,8 @@ function hasStringProperty(args, names) {
43720
43744
  return names.some((name) => typeof args[name] === "string");
43721
43745
  }
43722
43746
  function editWaveFileArgsAreExact(args) {
43723
- const path5 = trimString(args?.path, 240);
43724
- if (!path5) return false;
43747
+ const path6 = trimString(args?.path, 240);
43748
+ if (!path6) return false;
43725
43749
  const hasExactLineRange = isPositiveInteger(args?.startLine) && isPositiveInteger(args?.endLine) && hasStringProperty(args, ["text", "replacement"]);
43726
43750
  const hasExactTextReplace = typeof args?.oldText === "string" && args.oldText.length > 0 && hasStringProperty(args, ["newText", "replacement"]);
43727
43751
  return hasExactLineRange || hasExactTextReplace;
@@ -43755,7 +43779,7 @@ function waveMcpToolRoutingNote(toolName) {
43755
43779
  return " Direct Observe tool: call directly without start_wave_task. Pass taskRouteId only to attach this observation to an active Author/Diagnose route.";
43756
43780
  }
43757
43781
  if (contract.workMode === "operate") {
43758
- return " Direct Studio Operate tool: when the user asks for this exact Studio operation, call directly without start_wave_task. Pass taskRouteId only if this operation belongs to an active Author/Diagnose route.";
43782
+ return " Direct Operate tool: when the user asks for this exact operation, call directly without start_wave_task. Pass taskRouteId only if this operation belongs to an active Author/Diagnose route.";
43759
43783
  }
43760
43784
  if (contract.workMode === "author" && contract.safety === "readOnly") {
43761
43785
  return " Direct Author lookup tool: callable without start_wave_task, but for normal code authoring use after intent chunks/Core Intent Pass and skill-family selection.";
@@ -44090,14 +44114,14 @@ function validateWaveMcpTaskToolCall(input) {
44090
44114
  function recordWaveMcpTaskEvidenceUse(input) {
44091
44115
  let evidence = input.evidence;
44092
44116
  const canonicalToolName = canonicalizeWaveMcpToolName(input.toolName);
44093
- const path5 = trimString(input.args?.path, 240);
44117
+ const path6 = trimString(input.args?.path, 240);
44094
44118
  const skillId = trimString(input.args?.skillId, 120) || trimString(input.args?.id, 120);
44095
44119
  if (canonicalToolName === "list_wave_files") evidence = { ...evidence, filesListed: true };
44096
44120
  if (canonicalToolName === "read_wave_file") {
44097
44121
  evidence = {
44098
44122
  ...evidence,
44099
44123
  filesListed: true,
44100
- filesRead: uniqueStrings([...evidence.filesRead, path5].filter(Boolean), 64)
44124
+ filesRead: uniqueStrings([...evidence.filesRead, path6].filter(Boolean), 64)
44101
44125
  };
44102
44126
  }
44103
44127
  if (canonicalToolName === "find_wave_assets_by_category") evidence = { ...evidence, assetsListed: true };
@@ -44245,7 +44269,7 @@ var WAVE_AUTHORING_CORE_INTENT_STAMPS = [
44245
44269
  "Multiplicity/performance: wave.group.instancing-and-batching for local repeated model-backed objects; classify per-instance needs before choosing real entities, InstanceMeshGroup/waveInstanceGroup/waveInstanceMesh, or waveThinBatch. Count alone never chooses thin batch. Terrain/world/streamed population is a separate world-elements route.",
44246
44270
  'Soft custom geometry: wave.geometry.sdf-surface and assetManager.createSdfSurface("assetName") recipes for buns, cushions, puffy/squishy toys, and organic rounded forms before plain primitives, raw mesh buffers, or vertex editing.',
44247
44271
  "Path-bound surface geometry: wave.geometry.surface-ribbons and wavePathSurface for roads, trails, walkways, bridge decks, waterfalls, and continuous strips before asset-manager factories, scattered planes/cubes, or raw mesh buffers.",
44248
- "Asset argument vs authoring: typed refs such as models.<asset>, textures.<asset>, materials.<asset>, audios.<asset>, hdr.<asset>, cubeMaps.<asset>, and animations.<asset> for existing assets; waveMaterial for material handles; assetManager only when an exact skill/API requires a low-level generated asset factory."
44272
+ "Asset argument vs authoring: typed refs such as models.<asset>, textures.<asset>, materials.<asset>, audios.<asset>, hdr.<asset>, cubeMaps.<asset>, animations.<asset>, and vertexAnimations.<asset> for existing assets; waveMaterial for material handles; assetManager only when an exact skill/API requires a low-level generated asset factory."
44249
44273
  ];
44250
44274
  var WAVE_AUTHORING_CORE_INTENT_PASS_QUICK_GUIDE = [
44251
44275
  WAVE_AUTHORING_CORE_RULES.coreIntentPass,
@@ -44348,7 +44372,7 @@ function buildWaveMcpAuthoringReminder(kind) {
44348
44372
  ...base,
44349
44373
  nextStep: "Use exact asset refs as method inputs, then return to the authoring strategy.",
44350
44374
  checklist: [
44351
- "Use returned code refs exactly, such as models.X, textures.X, materials.X, audios.X, or animations.X.",
44375
+ "Use returned code refs exactly, such as models.X, textures.X, materials.X, audios.X, animations.X, or vertexAnimations.X.",
44352
44376
  "For authoring new materials, prefer waveMaterial; quote materials.X only for existing material assets.",
44353
44377
  "If a newly referenced asset changes bootstrap needs, let Studio hot reload escalate internally."
44354
44378
  ],
@@ -44454,7 +44478,7 @@ var WAVE_MCP_AGENT_BRIEF = [
44454
44478
  "VFS modules: scene-root modules and their owned helpers run inside the Studio authoring context, but helpers should stay pure or parameterized. Put live object ownership in scene-root/feature files, not shared helpers.",
44455
44479
  'VFS HTML sketches: for diagram/chart/doc-sketch requests, create or edit an exact `.html` file with Studio VFS tools. Mermaid renders from `<div class="mermaid">flowchart TD ...</div>` when that HTML file is active in the editor/coding pane; HTML sketches must never replace the live engine preview iframe. Do not put Mermaid in markdown fences unless user specifically wants markdown text.',
44456
44480
  'VFS hot reload ownership: Studio hot reload targets the active/open edited file when safe. Editing `/main.ts` reloads the main graph; editing a scene-root module reloads that root; bootstrap/scene registry/assets/network/ambiguous helper surfaces escalate. In multi-file projects, standalone `hot_reload_wave_preview` follows the active Studio editor file, so do not expect it to reload an arbitrary unopened module by path. Put heavy world loading in bootstrap or a coarse world module. Static VFS/package/asset-surface changes can be detected from file/package diffs and Studio may upgrade the tier automatically. Runtime-authored UI state changes cannot be inferred from files; if UI in `main.ts` changes bootstrap/world-provider/runtime-envelope state, store the state and call `waveStudio.reloadPreview({ reason, invalidates: ["world-provider"] })`. Valid invalidations are `bootstrap`, `world-provider`, and `runtime-envelope`. Do not call `myScene.hotReload(...)` from Studio-authored code because it bypasses Studio VFS/package/source-patch orchestration. For intentional cross-module live entities, the owner module calls `waveStudio.exportEntity("Name", entity)` and importer modules call `waveStudio.importEntity<T>("Name")`; Studio restores the exported baseline and replays active importer overlays during source hot reload. Source execution order is dependency-driven: exporter roots run before importer roots; an exporter root runs before `main.ts` when main imports it; importer overlays run after exporter/main baseline. Entity names must be static string literals, unique, and acyclic or run/hot reload stops before execution. Keep shared helpers pure or parameterized.',
44457
- "Assets: when quoting or passing an existing asset, use typed bare refs in main files: `models.X`, `textures.X`, `materials.X`, `audios.X`, `hdr.X`, `cubeMaps.X`, `animations.X`, etc. Do not paste raw paths or edit Studio-managed generated files just to register assets. If the exact ref is not already visible, call category-first asset discovery (`find_wave_assets_by_category`, alias `list_wave_assets_by_category`, alias `search_wave_assets_by_category`, or tolerant `list_wave_assets`) with required `category` from user intent, semantic `query`, and small `limit` before editing code; category-filtered results can still be capped/truncated, so refine query before concluding absence. Use `list_wave_explorer_assets` only for uploaded library metadata, rename/delete, URL, path, size, or display/ref-name management.",
44481
+ "Assets: when quoting or passing an existing asset, use typed bare refs in main files: `models.X`, `textures.X`, `materials.X`, `audios.X`, `hdr.X`, `cubeMaps.X`, `animations.X`, `vertexAnimations.X`, etc. Do not paste raw paths or edit Studio-managed generated files just to register assets. If the exact ref is not already visible, call category-first asset discovery (`find_wave_assets_by_category`, alias `list_wave_assets_by_category`, alias `search_wave_assets_by_category`, or tolerant `list_wave_assets`) with required `category` from user intent, semantic `query`, and small `limit` before editing code; category-filtered results can still be capped/truncated, so refine query before concluding absence. Use `list_wave_explorer_assets` only for uploaded library metadata, rename/delete, URL, path, size, or display/ref-name management.",
44458
44482
  "Asset authoring facade split: typed refs quote existing assets; `waveMaterial` authors material handles; `assetManager` is only for exact asset-pipeline/generated-asset APIs that a local SDK skill or API lookup names, such as SDF surfaces or surface ribbons. Do not treat `assetManager` as the default creative facade for materials or normal asset arguments.",
44459
44483
  "Material authoring: when creating, configuring, forking, or intentionally editing material handles, route to `wave.material.authoring` and start from `waveMaterial` (`createWater`, `createGrid`, `createSplatMaterial`, `createFromAsset`, `editAsset`). If a material handle later needs runtime sync, `assetManager` may be passed as the sync target; it is still not the authoring root.",
44460
44484
  "Preview: default to hot reload after code changes. On the final edit use `awaitHotReload: true` when you want edit + hot reload + diagnostics in one response; `requestHotReload:true` now also waits for diagnostics by default unless paired with `awaitHotReload:false`. Standalone `run_wave_preview` / `hot_reload_wave_preview` wait by default and return same-call bounded diagnostics in `runtimeVerification`; set `awaitRuntimeResult:false` only for intentional fire-and-poll. Wave Studio may upgrade the hot-reload tier internally for asset/bootstrap/runtime-surface changes, including a full scene rebuild only when required. Caveat: standalone `hot_reload_wave_preview` mirrors the toolbar button and reloads the currently open/active TypeScript file in multi-file projects; it does not take a target path or reload every user-created .ts module. Use `pause_wave_preview` / `resume_wave_preview` for toolbar-equivalent engine pause/resume.",
@@ -44477,7 +44501,7 @@ var WAVE_MCP_AUTHORING_GLOBALS_GUIDE = [
44477
44501
  "- Direction and input enum-style globals: `Direction.X/Y/Z`, `Direction.Left/Right/Up/Down/Forward/Backward`, `Keyboard`, `MouseButton`, `GamepadButton`, `InputPhase`, `MovementMode`, `RotationMode`.",
44478
44502
  "- Colors/materials/fx helpers: `COLOR`, `PALETTE`, `waveCOLOR`, `waveMaterial`, `shaderUniform`, `waveFx`, `waveFxPresets`, `FxAnchor`, `FxCondition`.",
44479
44503
  "- Controller movement helpers: `waveKinematicActor`, `netKinematicActor`, `KinematicHumanoidMovementState`, `KinematicVehicleMovementState`, and `KinematicFlightMovementState`. Use actor-level `.asHumanoid(...)`, `.asVehicle(...)`, or `.asFlight(...)` for player/controller movement.",
44480
- "- Asset maps: use category-first refs returned by `find_wave_assets_by_category`, commonly `models`, `gaussianSplats`, `animations`, `materials`, `guis`, `textures`, `audios`, `instruments`, `videos`, `hdr`, `fonts`, `serializedData`, `terrains`, `fx`, and `particles`.",
44504
+ "- Asset maps: use category-first refs returned by `find_wave_assets_by_category`, commonly `models`, `gaussianSplats`, `animations`, `vertexAnimations`, `materials`, `guis`, `textures`, `audios`, `instruments`, `videos`, `hdr`, `fonts`, `serializedData`, `terrains`, `fx`, and `particles`.",
44481
44505
  '- Scene/runtime handles: prefer `myScene` for scene composition and `waveStudio` for Studio operations. If authored UI changes bootstrap/world-provider/runtime-envelope state and needs the Studio preview package to rebuild, call `waveStudio.reloadPreview({ reason, invalidates: ["world-provider"] })`, not `myScene.hotReload(...)`. Valid invalidations are `bootstrap`, `world-provider`, and `runtime-envelope`; use the narrowest true reason. `assetManager` is available, but use it only when an exact skill/API names an asset-pipeline or generated-asset factory; do not use it as the default facade for existing asset refs or material authoring. Use lower-level `ctx`, `engine`, or `scene` only when existing code or an exact API requires them.',
44482
44506
  "- Authoring systems/presets: `waveEventBus`, `waveRig`, `waveValueCurve`, `waveValueCurvePresets`, `waveMotionSignal`, `waveParam`, `WaveParam`, `WaveChoice`, `prefabModels`, `effectPrefabs`, and prefab helpers such as `rocketPrefab`.",
44483
44507
  "- Lowercase constructors: `prop`, `marker`, `sphere`, `point`, `cube`, `box`, `cylinder`, `capsule`, `cone`, `torus`, `plane`, `ground`, `line`, `arc`, `path`, and related shape helpers.",
@@ -44510,7 +44534,7 @@ var WAVE_MCP_CODING_GUARDRAIL = [
44510
44534
  `4i. ${WAVE_AUTHORING_CORE_RULES.syncUserCode}`,
44511
44535
  `4j. ${WAVE_AUTHORING_CORE_RULES.terrainGroundRouting}`,
44512
44536
  "5. Use `get_wave_session` and `list_wave_files` to know active file context. `/main.ts` and scene `*/main.ts` get convenience globals: write against `myScene`, not a hand-created scene.",
44513
- `6. ${WAVE_AUTHORING_CORE_RULES.assetResolution} In main files, use bare asset maps like \`models.Foo\`, \`gaussianSplats.Room\`, \`textures.Grass\`, \`materials.Wood\`, \`animations.Idle\`, \`audios.Click\`, \`hdr.daylight\`, \`cubeMaps.studio\`, \`fonts.Poppins_Regular\`, \`serializedData.SomeJson\`. Do not add \`ASSET_WAREHOUSE.\` unless raw warehouse access is explicitly needed.`,
44537
+ `6. ${WAVE_AUTHORING_CORE_RULES.assetResolution} In main files, use bare asset maps like \`models.Foo\`, \`gaussianSplats.Room\`, \`textures.Grass\`, \`materials.Wood\`, \`animations.Idle\`, \`vertexAnimations.HorseGallopVat\`, \`audios.Click\`, \`hdr.daylight\`, \`cubeMaps.studio\`, \`fonts.Poppins_Regular\`, \`serializedData.SomeJson\`. Do not add \`ASSET_WAREHOUSE.\` unless raw warehouse access is explicitly needed.`,
44514
44538
  `6a. For authoring constants, prefer bare runtime globals declared by the Studio authoring environment. Common examples: ${WAVE_AUTHORING_COMMON_GLOBAL_EXAMPLES_TEXT}. The generated globals surface has many more values; use local-SDK \`query_wave_api\` before inventing namespace-qualified paths such as \`TransformVerbMode.Animate\` in authored code. HTTP Gateway hides/retires skill/API lookup.`,
44515
44539
  "6b. Asset facade split: passing an existing asset uses typed refs such as `models.X`, `textures.X`, `materials.X`, or `audios.X`; material authoring starts from `waveMaterial`; `assetManager` is reserved for exact low-level generated-asset/asset-pipeline APIs named by a local SDK skill or API lookup. Do not use `assetManager` as the default material, asset-ref, or path-surface authoring root.",
44516
44540
  '7. Use category-first asset discovery before referencing uploaded user assets, project aliases, or ambiguous built-in assets. Preferred tool: `find_wave_assets_by_category`; aliases: `list_wave_assets_by_category`, `search_wave_assets_by_category`, tolerant `list_wave_assets`. Choose category from user intent first, then query: `list_wave_assets_by_category({ category:"materials", query:"grass", limit:50 })`. For sound use `audios`, for 3D objects use `models`, for sky/HDR use `hdr`, for cube-map sky/environment use `cubeMaps`, for images use `textures`. Category-filtered responses can still be capped/truncated; absence there is not absence in the library. Only use asset refs returned by this tool. If the current code already contains the exact ref and the user asks for a small pattern-preserving edit, you may reuse that visible ref.',
@@ -44522,7 +44546,7 @@ var WAVE_MCP_CODING_GUARDRAIL = [
44522
44546
  '11. After the final code edit, default to hot reload. Prefer `awaitHotReload: true` on the edit tool when you want one MCP response with edit result + hot reload + bounded diagnostics. `requestHotReload:true` also waits for diagnostics by default unless paired with `awaitHotReload:false`. Standalone `run_wave_preview` / `hot_reload_wave_preview` also wait by default and return `runtimeVerification`; set `awaitRuntimeResult:false` only for intentional fire-and-poll. Caveat: standalone `hot_reload_wave_preview` mirrors the toolbar button and reloads the currently open/active TypeScript file in multi-file projects; it does not take a target path or reload every user-created .ts module. That hot-reload request may internally upgrade from `patchMain` to `preserveScene` or hard `rebuildScene` when assets/bootstrap/scene surface changed; do not manually force bootstrap sync. Runtime-authored UI that changes bootstrap/world-provider/runtime-envelope state must call `waveStudio.reloadPreview({ reason, invalidates: ["world-provider"] })` because Studio cannot infer that from static file diffs. Use `run_wave_preview` only when diagnostics show no successful preview yet (`hasRunSucceeded: false`), the user explicitly asks for a full run/restart, or hot reload/polling fails and a full restart is the last resort. `pause_wave_preview` and `resume_wave_preview` are direct preview controls mirroring the toolbar pause/resume buttons.',
44523
44547
  '12. After run/hot reload, use returned `runtimeVerification` when present; otherwise call `get_wave_runtime_diagnostics` repeatedly until `runtimeBusy` is false and `lastRuntimeOutcome` is `success` or `error`. Use `previewExecutionPhase` to distinguish Studio preparing work from the preview iframe executing it. `isRunning` is only the Studio scheduling flag, not the final runtime completion gate. If the edit/runtime tool returned `requestedAt` or `runtimeActionRequestedAt`, ignore diagnostics whose `lastRuntimeOutcomeAt` is older than that timestamp. Treat `latestError` as current only when `lastRuntimeOutcome` is `error`; older error lines can remain in returned logs as history. Treat `editorErrorCount > 0` as blocking Monaco/linter/compile failure and read `editorDiagnostics` before claiming success, even when `lastRuntimeOutcome` is `success`. If `lastRuntimeOutcome` is `success` and `editorErrorCount` is 0, verify visually with `capture_wave_screenshot({ resolution: "L" })` or structurally with `get_wave_runtime_entity_snapshot({ variableName, filePath? })` instead of declaring failure from old log text. Same-machine screenshots usually return `localPath`; hosted screenshots return exact field `dataBase64`, not `imageBase64`.',
44524
44548
  "12a. Marker waypoint workflow: when the human asks to use preview marking tools such as `clickToMark()`, route with `marker.use` plus the code/project intent, then call `list_wave_runtime_markers`. Treat marker records as runtime observation data, write marker `worldPosition`/`worldRotation` into the appropriate path/waypoint/placement code, hot reload, and verify with screenshot or marker re-read. One marker read may happen before routing only when needed to decide the code intent. Use path/placement skills only when current code does not reveal the marker-to-code pattern.",
44525
- `13. To use locally generated assets in code, prefer \`create_wave_asset_upload\` for local files when available: pass assetKind from the Wave Studio upload policy SSOT (${getWaveStudioBridgeAssetUploadKindHelpText()}), stat the file first, pass exact byte size, get \`uploadUrl\` + \`uploadHeaders\`, upload raw bytes with plain HTTP PUT using every returned header exactly, keep \`clientToken\`/\`uploadHeaders.Authorization\` out of logs, call \`get_wave_asset_upload_status\` until \`receivedBytes === sizeBytes\`, then call \`commit_wave_asset_upload\` with the same uploadId, assetKind, filename, contentType, and sizeBytes from the create response. No local Python, Vercel package, or Blob SDK is required. On hosted MCP this uses hosted temporary object storage; on same-machine local MCP it uses the local SDK as temporary staging. If direct upload is unavailable, use \`stage_wave_asset_upload_chunk\` plus \`commit_wave_asset_chunk_upload\`: omit uploadId only for chunkIndex 0, then reuse the returned uploadId for every later chunk and for commit. Use \`upload_wave_asset\` with exactly one of \`dataBase64\` or \`dataUrl\` only for small inline assets. If staging is abandoned before commit, call \`abort_wave_asset_upload\` with the uploadId. Do not abort after commit returns pending or completed; the Studio browser cleans committed staging after execution. Hosted MCP cannot read local filesystem paths through inline upload. All flows stage bytes outside the command queue, cap staged bytes at 300 MB, and return \`asset.bareRef\`, for example \`textures.Albedo\`, \`cubeMaps.Studio\`, \`materials.Wood\`, \`audios.Click\`, \`models.Robot\`, \`animations.Run\`, \`fonts.Title\`, or \`serializedData.Config\`. If the name already exists, the upload result returns \`skipped: true\` and the existing asset. Staged chunks/blobs are also swept lazily after about 24 hours. Model-family uploads enable meshoptimizer by default unless \`useMeshoptimizer\` is false.`,
44549
+ `13. To use locally generated assets in code, prefer \`create_wave_asset_upload\` for local files when available: pass assetKind from the Wave Studio upload policy SSOT (${getWaveStudioBridgeAssetUploadKindHelpText()}), stat the file first, pass exact byte size, get \`uploadUrl\` + \`uploadHeaders\`, upload raw bytes with plain HTTP PUT using every returned header exactly, keep \`clientToken\`/\`uploadHeaders.Authorization\` out of logs, call \`get_wave_asset_upload_status\` until \`receivedBytes === sizeBytes\`, then call \`commit_wave_asset_upload\` with the same uploadId, assetKind, filename, contentType, and sizeBytes from the create response. No local Python, Vercel package, or Blob SDK is required. On hosted MCP this uses hosted temporary object storage; on same-machine local MCP it uses the local SDK as temporary staging. If direct upload is unavailable, use \`stage_wave_asset_upload_chunk\` plus \`commit_wave_asset_chunk_upload\`: omit uploadId only for chunkIndex 0, then reuse the returned uploadId for every later chunk and for commit. Use \`upload_wave_asset\` with exactly one of \`dataBase64\` or \`dataUrl\` only for small inline assets. If staging is abandoned before commit, call \`abort_wave_asset_upload\` with the uploadId. Do not abort after commit returns pending or completed; the Studio browser cleans committed staging after execution. Hosted MCP cannot read local filesystem paths through inline upload. All flows stage bytes outside the command queue, cap staged bytes at 300 MB, and return \`asset.bareRef\`, for example \`textures.Albedo\`, \`cubeMaps.Studio\`, \`materials.Wood\`, \`audios.Click\`, \`models.Robot\`, \`animations.Run\`, \`vertexAnimations.HorseGallopVat\`, \`fonts.Title\`, or \`serializedData.Config\`. If the name already exists, the upload result returns \`skipped: true\` and the existing asset. Staged chunks/blobs are also swept lazily after about 24 hours. Model-family uploads enable meshoptimizer by default unless \`useMeshoptimizer\` is false.`,
44526
44550
  "14. Project tools: use `list_wave_project_templates` before `new_wave_project({ templateId? })`; use `list_wave_projects` before `rename_wave_project({ projectId, name })` or `open_wave_project({ projectId })`. Use `read_wave_project` to learn from example packs without replacing the current editor workspace: pass exact `projectId` from `list_wave_projects` for listed/saved projects, or pass a shared/published project `url` directly for silent URL reads. Shared URL reads support old `/waveStudio?share=<uuid>` links and new shared alias links; published URL reads support old/new `/p/<user>/<id>` links. Rename updates saved project display metadata only. New/open replace the live workspace. If there are unsaved changes they return `unsaved_changes` unless you save first or explicitly pass `discardUnsavedChanges: true`; only use exact ids returned by list tools for open/rename.",
44527
44551
  "15. `save_wave_project` quick-saves an existing saved project and ignores name/description. For a new unsaved project, pass name/description when available; otherwise Studio generates defaults and returns whether each field was defaulted.",
44528
44552
  "16. `share_wave_project` runs the same Share Project menu pipeline, returns a 30-day share `url`, and requires write pairing plus the signed-in share policy. Always send the returned `url` back to the user.",
@@ -44541,7 +44565,7 @@ var WAVE_MCP_CODING_GUARDRAIL_SUMMARY = [
44541
44565
  "4. Prefer natural, intent-preserving public APIs and aliases such as Actor, Prop, Cube, Sphere, waveMaterial, semantic directions, Animate/Seconds/DegreesPerSecond, and fluent WaveEngine DSLs.",
44542
44566
  `4a. ${WAVE_AUTHORING_CORE_RULES.hotReloadCallbackSafety} edit_wave_file/apply_wave_patch/create_wave_file preflight this rule and runtime bundling blocks violations before authored code runs.`,
44543
44567
  "4b. For model voxelization, route to `wave.model.voxelization`; use `model.voxelizing().relativeSize(0.05).surfaceOnly().onWorkerThread().usingCubes().asThinBatch().applyAsync()` only when async/worker authored code is explicitly acceptable, or `onMainThread().apply()` only for tiny local synchronous voxel work. Never use `.apply().then(...)` or Promise callback chains. Use `detectNear`, `getColorAt`, and `whenIndexClicked(index => voxelBatch.at(index).enlargeBy(1.2).done().commitBuffers())` for thin-batch voxel interaction. Do not use `voxelizeHeavy`, `voxelize({ voxelSize })`, `withVoxelSize`, `sharedBox`, or stale `atIndex`.",
44544
- "5. Resolve assets explicitly. Use category-first bare refs returned by `find_wave_assets_by_category` or alias `list_wave_assets_by_category`: models.X, textures.X, materials.X, audios.X, hdr.X, animations.X. Do not guess refs, raw paths, or infer absence from a capped list.",
44568
+ "5. Resolve assets explicitly. Use category-first bare refs returned by `find_wave_assets_by_category` or alias `list_wave_assets_by_category`: models.X, textures.X, materials.X, audios.X, hdr.X, animations.X, vertexAnimations.X. Do not guess refs, raw paths, or infer absence from a capped list.",
44545
44569
  "6. `project.assetrefs.ts`, `project.scene.ts`, `project.scenes.ts`, and `project.execution.ts` are Studio-managed read-only. `bootstrap.ts` is normally managed too; edit it only with a confident `managedFileEditReason` for world streaming/terrain provider, render/runtime backend, media consent/capture, external AI/TTS backend, scene/template baseline, pre-main baseline setup, or stale bootstrap API update to latest. Never edit bootstrap for asset loaders/manifests, instruments, scene registry, or execution manifest.",
44546
44570
  "7. Use safest VFS tool: read first, default to edit_wave_file for small one-file edits, let it auto-resolve baseHash, use apply_wave_patch({ operations }) with explicit baseHash for grouped/advanced work, and inspect skips/partials.",
44547
44571
  "8. For obby/platform tasks, controlled players use kinematic-controller authoring; moving platforms and hazards use rigid-body kinematic geometry/props plus transform/animate/current-code motion.",
@@ -44612,12 +44636,12 @@ var WAVE_MCP_FILE_CONTEXT_GUIDE = [
44612
44636
  "Wave Studio file-context rules:",
44613
44637
  "- Normal scene edit path: use the active `*/main.ts`, write against `myScene`, use bare asset refs, and do not edit Studio-managed generated files.",
44614
44638
  "- Studio-managed generated files are MCP read-only for agents: `project.assetrefs.ts`, `project.scene.ts`, `project.scenes.ts`, and `project.execution.ts`. `bootstrap.ts` / scene `*/bootstrap.ts` is normally Studio-managed too; edit it only when you can confidently pass `managedFileEditReason` as one of: `world_streaming_or_terrain_provider`, `render_profile_or_runtime_backend`, `media_consent_or_capture_plan`, `external_ai_or_tts_backend`, `scene_envelope_or_template_baseline`, `baseline_setup_before_user_main`, `stale_api_update_to_latest`. Never edit bootstrap for assets/loaders, instruments, scene registry, or execution manifest; Studio owns those.",
44615
- "- `main.ts` / scene `*/main.ts`: user scene authoring code. Prefer `myScene` for the active scene. Also available: `assetManager`, `waveEngine`, `waveStudio`, `models`, `gaussianSplats`, `fbxModels`, `animations`, `materials`, `guis`, `textures`, `audios`, `instruments`, `videos`, `hdr`, `cubeMaps`, `generatedModels`, `fonts`, `serializedData`, `terrains`, `fx`, `particles`.",
44639
+ "- `main.ts` / scene `*/main.ts`: user scene authoring code. Prefer `myScene` for the active scene. Also available: `assetManager`, `waveEngine`, `waveStudio`, `models`, `gaussianSplats`, `fbxModels`, `animations`, `vertexAnimations`, `materials`, `guis`, `textures`, `audios`, `instruments`, `videos`, `hdr`, `cubeMaps`, `generatedModels`, `fonts`, `serializedData`, `terrains`, `fx`, `particles`.",
44616
44640
  `- Studio also injects many authoring constants/functions as bare globals. ${WAVE_AUTHORING_BARE_GLOBALS_RULE} Do not add imports or guessed namespaces. Treat listed globals as examples and use local-SDK \`query_wave_api\` for the complete generated authoring surface. HTTP Gateway hides/retires skill/API lookup.`,
44617
44641
  "- Project code does not need to live in one monolith. Use `create_wave_file`, `rename_wave_file`, and `delete_wave_file` for VFS `.ts` modules and folders, for example `/actors/player.ts` or `/systems/movingPlatforms.ts`. New `.ts/.tsx` files are runnable source roots by default, so top-level scene code in them runs on Run/hot reload; pass `runnable:false` only for pure helper modules that should not execute on their own.",
44618
44642
  '- Studio VFS also supports non-code `.html` sketch files. Use `create_wave_file({ path: "/docs/diagram.html", content })` for Mermaid diagrams, charts, and lightweight docs; put Mermaid source inside `<div class="mermaid">...</div>`, not markdown fences. When that `.html` file is active, the editor/coding pane renders it as an HTML sketch and auto-loads Mermaid if `class="mermaid"` is present. HTML sketches must never replace the live engine preview iframe.',
44619
44643
  "- `list_wave_files` and `read_wave_file` return a full-file `contentHash`. Primary simple edit tool `edit_wave_file` can omit `baseHash`; Studio resolves the latest mirror hash and still performs browser-side stale-write checks. It accepts `edits:[{startLine,endLine,text}]` or `edits:[{rangeOffset,rangeLength,text}]` for one-file multi-edits. Use explicit `baseHash` for advanced `apply_wave_patch({ operations: [...] })` compact operations (`lineEdits`, `textEdits`, strict `searchReplace`, or `unifiedDiff`) and existing-file `writeFile` unless `forceOverwrite: true`. This hash is a cooperative stale-write check, not a security boundary. Prefer forgiving `edit_wave_file` over full-file `writeFile` when only a small span changes. Stale hashes and ambiguous replacements are skipped instead of guessed. `textEdits` offsets are zero-based UTF-16 offsets in the full file, even if you used a scoped line read; line edits use 1-based full-file line numbers. Scoped reads and line edits clamp oversized endLine to lineCount but still reject startLine beyond the file. If you need multiple compact changes in one file, use `edit_wave_file({ path, edits:[...] })` or combine them into one advanced patch operation; re-read between separate operations.",
44620
- "- Main-file asset refs should be bare: `models.Car`, `gaussianSplats.Room`, `textures.Grass`, `materials.Metal`, `animations.Idle`. Omit `ASSET_WAREHOUSE.` for authored code.",
44644
+ "- Main-file asset refs should be bare: `models.Car`, `gaussianSplats.Room`, `textures.Grass`, `materials.Metal`, `animations.Idle`, `vertexAnimations.HorseGallopVat`. Omit `ASSET_WAREHOUSE.` for authored code.",
44621
44645
  "- Asset facade split: existing asset inputs use typed refs; material authoring starts from `waveMaterial`; `assetManager` is for explicit generated-asset/asset-pipeline APIs named by a local SDK skill or API lookup, not the default place to invent new material or asset code.",
44622
44646
  "- Use `find_wave_assets_by_category({ category, query?, limit? })` to inspect the current live asset surface. Fill category from user intent first. It includes public assets, uploaded user assets, and project aliases, with `source` telling where each ref came from.",
44623
44647
  "- Wave Studio auto-maintains bootstrap/project asset/scene wiring for normal `main.ts` edits. Do not write or modify bootstrap or generated project registry files just to make an asset ref or scene entry available; use the returned bare refs in main code and let Studio sync boot/assets/scenes. Hot reload is the default preview path after code edits and can automatically upgrade its internal tier when new assets or bootstrap-surface changes require it.",
@@ -44672,13 +44696,13 @@ var WAVE_MCP_STUDIO_TOOL_GUIDE = [
44672
44696
  '- Local parity rule: WaveEngine Agent SDK `tools/list` mirrors the HTTP Gateway Studio-operation catalog, then adds local SDK lookup/session-discovery extras. Direct local live-session tools include VFS edits, assets/uploads, project save/open/new/read/share, preview run/hot reload/pause/resume, diagnostics, performance, screenshots, entity snapshots, and runtime markers. Local browser-backed calls wait directly for browser results. Only call `get_wave_command_result` if a tool returns `status:"pending"` plus `requestId`; on local it normally exists only for catalog parity and reports no local pending-command queue.',
44673
44697
  `- MCP topology: same-machine agents should run \`${WAVE3D_AGENT_SDK_START_COMMAND}\`, check /health, and update/restart SDK if version is stale, tools are missing, or SDK prints secrets; after restart recheck /health and tools/list. If restart happens after Copy-to-Agent, preserve MCP_TOKEN with \`${WAVE3D_AGENT_SDK_TOKEN_PRESERVING_START_COMMAND}\` using the current MCP_TOKEN value without printing it, otherwise the SDK may be healthy but unable to authorize the copied local pairing secret. Current SDK start reuses a current same-token peer, replaces verified stale/different-token SDK peers on localhost, and refuses to kill non-SDK port owners. Same-machine stale/missing tools/401-with-Gateway-success means repair local SDK first, not HTTP Gateway first. Same-machine HTTP Gateway is degraded fallback only after SDK cannot run, update, or adopt. Different-machine/cloud/sandbox agents should use \`MCP_HTTP_GATEWAY_FALLBACK_URL\` directly; starting WaveEngine Agent SDK there binds localhost beside the agent, not beside the user browser. Stale SDK/cache are not reasons to use HTTP Gateway fallback; wrong-machine localhost is a topology mismatch.`,
44674
44698
  `- MCP cache priorities: Priority 1 is active role/context cache: keep role, guardrails, authoring workflow, asset/VFS/hot-reload rules, prompt version, and prompt hash in active LLM context. Priority 2 is WaveEngine SDK Cache: production HTTP Gateway/local-SDK onboarding must not claim normal cached success until SDK cache is prepared or accept proof passes. If accept returns \`wave_engine_sdk_cache_not_ready\`, update/restart \`wave3d-agent-sdk\` or run \`${WAVE_MCP_CORPUS_CACHE_COMMAND_TEMPLATE}\`, then retry accept. If local cache cannot work because sandbox/no filesystem/no Node/npm/user refusal/refresh failure, explicitly accept degraded fallback with \`waveEngineSdkFallbackMode:"studio_ops_only"\`, a valid \`waveEngineSdkUnavailableReason\`, and \`studioOpsOnlyFallbackAccepted:true\`; warn user HTTP Gateway is Studio-ops-only and skill/API lookup requires local SDK/cache repair. This cache command reinstalls the package-bundled SDK locally; it is not WaveEngine Agent SDK transport. Never run it to switch MCP transport; never treat cache failure as local transport failure.`,
44675
- "- Main-file globals include `myScene`, `assetManager`, `waveEngine`, `waveStudio`, and direct asset maps such as `models`, `gaussianSplats`, `textures`, `materials`, `animations`, `audios`, `hdr`, `cubeMaps`, `fonts`, `serializedData`, `terrains`, `fx`, `particles`. Availability is not endorsement: choose the intent-owned facade/skill before using a global.",
44699
+ "- Main-file globals include `myScene`, `assetManager`, `waveEngine`, `waveStudio`, and direct asset maps such as `models`, `gaussianSplats`, `textures`, `materials`, `animations`, `vertexAnimations`, `audios`, `hdr`, `cubeMaps`, `fonts`, `serializedData`, `terrains`, `fx`, `particles`. Availability is not endorsement: choose the intent-owned facade/skill before using a global.",
44676
44700
  `- Bare authoring globals: common examples include ${WAVE_AUTHORING_COMMON_GLOBAL_EXAMPLES_TEXT}, \`waveMaterial\`, \`waveFx\`, \`waveValueCurve\`, and \`waveParam\`. Use them directly in \`main.ts\`; do not import or namespace-qualify them. If the task is unfamiliar, retrieve the relevant local-SDK \`wave.*\` skill first; for the full generated exact-global surface, call local-SDK \`query_wave_api\`.`,
44677
44701
  "- MCP VFS edit tools: primary tool is forgiving `edit_wave_file({ path, ... })`; it infers replaceLines from startLine/endLine, multi-edit from edits:[...], replaceText from oldText, and whole-file replace from text/content. It handles line-based edits, exact string changes, one-file edit arrays, whole-file replacement, omitted baseHash, common aliases, and oversized endLine clamping. Use `apply_wave_patch({ operations: [...] })` only for grouped/multi-file edits and advanced operations; a single operation is tolerated but operations:[...] is preferred. Simple edit tools can omit baseHash; advanced patch operations should pass it.",
44678
44702
  `- MCP hot-reload safety rule: ${WAVE_AUTHORING_CORE_RULES.hotReloadCallbackSafety} edit_wave_file, create_wave_file, and apply_wave_patch preflight this rule before writing.`,
44679
44703
  "- MCP generated-file rule: `project.assetrefs.ts`, `project.scene.ts`, `project.scenes.ts`, and `project.execution.ts` are Studio-managed read-only. `bootstrap.ts` / scene `*/bootstrap.ts` is normally managed too; edit it only when you can confidently pass `managedFileEditReason` as `world_streaming_or_terrain_provider` (Google Maps 3D tiles included), `render_profile_or_runtime_backend`, `media_consent_or_capture_plan`, `external_ai_or_tts_backend`, `scene_envelope_or_template_baseline`, `baseline_setup_before_user_main`, or `stale_api_update_to_latest`. Never edit bootstrap for asset manifests/loaders, instruments, scene registry, or execution manifest; Studio owns those.",
44680
44704
  '- MCP VFS file tools: `create_wave_file({ path, content, requestHotReload? })`, `rename_wave_file({ fromPath, toPath, requestHotReload? })`, and `delete_wave_file({ path, requestHotReload? })` are direct Operate tools for exact live VFS paths, including project `.ts` modules and `.html` sketch files. New `.ts/.tsx` files are standalone runnable source roots by default, so top-level scene code runs on Run/hot reload; pass `runnable:false` only for pure helper modules that should not execute on their own. For Mermaid/diagram/chart requests, create a `.html` file containing a block like `<div class="mermaid">flowchart TD\\n A --> B</div>`; active `.html` files render in the editor/coding pane as HTML sketches and auto-load Mermaid when `class="mermaid"` is present. HTML sketches must never replace the live engine preview iframe. On the final code edit, `awaitHotReload:true` implies hot reload and returns bounded runtime diagnostics in the same response. These tools manage VFS files, not uploaded asset-library blobs. If the user gives a vague target, call `list_wave_files`/`read_wave_file` or ask before mutating. Scene-root modules and owned helpers run in the Studio authoring context; keep shared helpers pure or parameterized, and keep live object ownership in feature files. Rename/move rewrites relative imports; delete is permanent unless you rewrite the file and refuses files still imported by other VFS code.',
44681
- "- MCP asset resolution rule: if code needs an existing asset argument, reuse an exact visible typed ref or call category-first asset discovery before editing: `find_wave_assets_by_category({ category, query?, sources?, limit? })`, alias `list_wave_assets_by_category`, alias `search_wave_assets_by_category`, or tolerant `list_wave_assets`. Fill `category` from user intent first: sound/audio -> `audios`, 3D object/character/prop -> `models`, material -> `materials`, image/texture -> `textures`, sky/HDR -> `hdr`, cube-map sky/environment -> `cubeMaps`, animation -> `animations`, JSON/CSV/YAML/XML/ROS data -> `serializedData`. It returns refs safe to use in authored code, ordered projectAlias -> user -> public. In `main.ts`, pass typed bare refs such as `models.X`, `textures.X`, `materials.X`, `audios.X`, `hdr.X`, `cubeMaps.X`, `animations.X`, `videos.X`, `fonts.X`, `serializedData.X`, or `terrains.X`; do not paste raw paths.",
44705
+ "- MCP asset resolution rule: if code needs an existing asset argument, reuse an exact visible typed ref or call category-first asset discovery before editing: `find_wave_assets_by_category({ category, query?, sources?, limit? })`, alias `list_wave_assets_by_category`, alias `search_wave_assets_by_category`, or tolerant `list_wave_assets`. Fill `category` from user intent first: sound/audio -> `audios`, 3D object/character/prop -> `models`, material -> `materials`, image/texture -> `textures`, sky/HDR -> `hdr`, cube-map sky/environment -> `cubeMaps`, skeletal animation -> `animations`, baked vertex/VAT animation -> `vertexAnimations`, JSON/CSV/YAML/XML/ROS data -> `serializedData`. It returns refs safe to use in authored code, ordered projectAlias -> user -> public. In `main.ts`, pass typed bare refs such as `models.X`, `textures.X`, `materials.X`, `audios.X`, `hdr.X`, `cubeMaps.X`, `animations.X`, `vertexAnimations.X`, `videos.X`, `fonts.X`, `serializedData.X`, or `terrains.X`; do not paste raw paths.",
44682
44706
  "- MCP asset-authoring facade rule: quoting or passing existing assets uses typed refs such as `models.X`, `textures.X`, `materials.X`, and `audios.X`; creating/configuring/forking/editing material handles starts from `waveMaterial` and `wave.material.authoring`; path-bound surfaces start from `wavePathSurface`; `assetManager` is reserved for exact low-level asset-pipeline/generated-asset APIs named by a local SDK skill or API lookup.",
44683
44707
  "- MCP material authoring rule: quoting an existing material uses `materials.X`; authoring a material handle uses `waveMaterial`. Do not call internal procedural material lifecycle methods such as `_sync(...)`, `_bindAutoSync(...)`, or `_release(...)` in authored code; apply procedural handles through `entity.useMaterial(...)`, geometry, terrain paint, or shader `.useTexture(...)`. `WaveMaterialAssetEditHandle.sync(assetManager)` remains the explicit commit step for intentional loaded-material asset edits.",
44684
44708
  `- MCP explorer asset tools: \`list_wave_explorer_assets({ query?, assetTypes?, limit? })\`, \`create_wave_asset_upload({ assetKind, filename, contentType?, sizeBytes })\`, \`commit_wave_asset_upload({ assetKind, uploadId, filename, contentType?, sizeBytes, useMeshoptimizer?, useKtx2Compression? })\`, \`get_wave_asset_upload_status({ uploadId, totalChunks? })\`, \`upload_wave_asset({ assetKind, filename, dataBase64? | dataUrl?, contentType?, useMeshoptimizer?, useKtx2Compression? })\`, \`stage_wave_asset_upload_chunk({ uploadId?, chunkIndex, totalChunks, dataBase64 })\`, \`commit_wave_asset_chunk_upload({ assetKind, uploadId, filename, contentType?, totalChunks, totalSizeBytes, useMeshoptimizer?, useKtx2Compression? })\`, \`abort_wave_asset_upload({ uploadId })\`, \`rename_wave_asset({ path, name })\`, and \`delete_wave_asset({ path })\` operate on uploaded user assets shown by the asset explorer. \`assetKind\` values come from the Wave Studio upload policy SSOT: ${getWaveStudioBridgeAssetUploadKindHelpText()}. Status and abort require the same write token, but can still run after the active Studio write grant expires. Read \`resultSummary\` first: asset tools report ASSET ... OK, ASSET ... FAILED, or ASSET ... PENDING in the same response.`,
@@ -44756,8 +44780,8 @@ var WAVE_MCP_TOOL_CATALOG_GUIDE = [
44756
44780
  "- Marker waypoint recipe: direct marker position/rotation questions are Observe and call `list_wave_runtime_markers` directly. When the user asks to use clicked/marked points in code, start a route with `marker.use` plus `code.author` or the fitting fast-lane code kind, then call `list_wave_runtime_markers`. Read the target file, use marker `worldPosition`/`worldRotation` as waypoint/path/placement inputs, and verify with screenshot or marker re-read. Retrieve a path/placement skill only when current code does not reveal the pattern.",
44757
44781
  "",
44758
44782
  "studio.assets - refs, uploads, and asset library:",
44759
- "- Asset resolution rule: before writing an asset-map input, reuse an exact ref already visible in code or call category-first asset discovery (`find_wave_assets_by_category`, alias `list_wave_assets_by_category`) with required `category`, semantic `query`, and small `limit`; do not invent `models.<asset>`, `textures.<asset>`, `materials.<asset>`, `audios.<asset>`, `hdr.<asset>`, `cubeMaps.<asset>`, or `animations.<asset>` keys from memory. Do not infer absence from a capped category list.",
44760
- "- Existing asset arguments should be typed bare refs in main files, such as `models.Robot`, `textures.Grass`, `materials.Wood`, `audios.Click`, `hdr.StudioSky`, `cubeMaps.studio`, `animations.Run`, `videos.Intro`, `fonts.Title`, `serializedData.UnitreePose`, or `terrains.Alpine`.",
44783
+ "- Asset resolution rule: before writing an asset-map input, reuse an exact ref already visible in code or call category-first asset discovery (`find_wave_assets_by_category`, alias `list_wave_assets_by_category`) with required `category`, semantic `query`, and small `limit`; do not invent `models.<asset>`, `textures.<asset>`, `materials.<asset>`, `audios.<asset>`, `hdr.<asset>`, `cubeMaps.<asset>`, `animations.<asset>`, or `vertexAnimations.<asset>` keys from memory. Do not infer absence from a capped category list.",
44784
+ "- Existing asset arguments should be typed bare refs in main files, such as `models.Robot`, `textures.Grass`, `materials.Wood`, `audios.Click`, `hdr.StudioSky`, `cubeMaps.studio`, `animations.Run`, `vertexAnimations.HorseGallopVat`, `videos.Intro`, `fonts.Title`, `serializedData.UnitreePose`, or `terrains.Alpine`.",
44761
44785
  "- Asset authoring facade split: quote existing assets as typed refs (`models.X`, `textures.X`, `materials.X`, `audios.X`, etc.); author material handles through `waveMaterial` and `wave.material.authoring`; use `assetManager` only for exact generated-asset/asset-pipeline APIs named by a local SDK Wave skill or API lookup.",
44762
44786
  '- `find_wave_assets_by_category`: category-first refs safe to write in code, such as `models.Robot`, `textures.Grass`, `materials.Wood`, `audios.Click`. Aliases: `list_wave_assets_by_category`, `search_wave_assets_by_category`, tolerant `list_wave_assets`. Examples: `{ category:"models", query:"sparrow", limit:50 }`, `{ category:"audios", query:"footstep", limit:50 }`, `{ category:"materials", query:"grass", limit:50 }`.',
44763
44787
  "- `list_wave_explorer_assets`: uploaded asset library metadata for management: path, URL, display/ref name, type, size. May be empty; not a fallback for built-in/public refs.",
@@ -44794,8 +44818,8 @@ function optionalStringArray(value) {
44794
44818
  const strings = rawItems.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
44795
44819
  return strings.length > 0 ? strings : void 0;
44796
44820
  }
44797
- function normalizeWaveFilePath(path5) {
44798
- const trimmed = path5.trim();
44821
+ function normalizeWaveFilePath(path6) {
44822
+ const trimmed = path6.trim();
44799
44823
  if (!trimmed) return trimmed;
44800
44824
  return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
44801
44825
  }
@@ -44931,9 +44955,9 @@ function readPathArray(value) {
44931
44955
  return value.map((item) => {
44932
44956
  if (typeof item === "string") return item;
44933
44957
  const record = readRecord(item);
44934
- const path5 = record?.path;
44935
- return typeof path5 === "string" ? path5 : null;
44936
- }).filter((path5) => typeof path5 === "string" && path5.length > 0);
44958
+ const path6 = record?.path;
44959
+ return typeof path6 === "string" ? path6 : null;
44960
+ }).filter((path6) => typeof path6 === "string" && path6.length > 0);
44937
44961
  }
44938
44962
  function readRecord(value) {
44939
44963
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -45910,6 +45934,401 @@ async function runLocalSdkLookupTool(input) {
45910
45934
  );
45911
45935
  }
45912
45936
 
45937
+ // ../../scripts/wavegenie-sdk/modeling/modelingJob.ts
45938
+ import { randomUUID } from "node:crypto";
45939
+ import { mkdir, writeFile } from "node:fs/promises";
45940
+ import { tmpdir } from "node:os";
45941
+ import path2 from "node:path";
45942
+ function sanitizeAssetName(rawName) {
45943
+ const sanitized = rawName.trim().toLowerCase().replace(/[^a-z0-9]+/gu, "_").replace(/^_+|_+$/gu, "");
45944
+ return sanitized || "generated_model";
45945
+ }
45946
+ function jsonValueOrNull(value) {
45947
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
45948
+ return typeof value === "number" && Number.isNaN(value) ? null : value;
45949
+ }
45950
+ if (Array.isArray(value)) {
45951
+ const items = value.map((item) => jsonValueOrNull(item));
45952
+ return items.every((item) => item !== null) ? items : null;
45953
+ }
45954
+ if (isRecord2(value)) {
45955
+ const entries = Object.entries(value);
45956
+ const result = {};
45957
+ for (const [key, item] of entries) {
45958
+ const normalized = jsonValueOrNull(item);
45959
+ if (normalized === null) return null;
45960
+ result[key] = normalized;
45961
+ }
45962
+ return result;
45963
+ }
45964
+ return null;
45965
+ }
45966
+ function normalizeKnownDimensionsMm(value) {
45967
+ if (!isRecord2(value)) return null;
45968
+ const result = {};
45969
+ for (const [key, item] of Object.entries(value)) {
45970
+ if (!key.trim()) continue;
45971
+ const normalized = jsonValueOrNull(item);
45972
+ if (normalized === null) return null;
45973
+ result[key.trim()] = normalized;
45974
+ }
45975
+ return Object.keys(result).length > 0 ? result : null;
45976
+ }
45977
+ function duplicateStrings(items) {
45978
+ const seen = /* @__PURE__ */ new Set();
45979
+ const duplicates = /* @__PURE__ */ new Set();
45980
+ for (const item of items) {
45981
+ if (seen.has(item)) {
45982
+ duplicates.add(item);
45983
+ } else {
45984
+ seen.add(item);
45985
+ }
45986
+ }
45987
+ return [...duplicates];
45988
+ }
45989
+ function parseModelingJobArgs(args) {
45990
+ const sourcePrompt = normalizeOptionalString(args?.sourcePrompt) ?? normalizeOptionalString(args?.prompt) ?? normalizeOptionalString(args?.description);
45991
+ if (!sourcePrompt) {
45992
+ return errorToolResult(
45993
+ "invalid_arguments",
45994
+ "`create_wave_3d_modeling_job` needs sourcePrompt, prompt, or description."
45995
+ );
45996
+ }
45997
+ const rawAssetName = normalizeOptionalString(args?.assetName) ?? normalizeOptionalString(args?.name) ?? sourcePrompt.split(/\s+/u).slice(0, 5).join("_");
45998
+ const knownDimensionsMm = typeof args?.knownDimensionsMm === "undefined" ? null : normalizeKnownDimensionsMm(args.knownDimensionsMm);
45999
+ if (typeof args?.knownDimensionsMm !== "undefined" && !knownDimensionsMm) {
46000
+ return errorToolResult(
46001
+ "invalid_arguments",
46002
+ "`knownDimensionsMm` must be a JSON object with only JSON-safe values."
46003
+ );
46004
+ }
46005
+ const requiredParts = optionalStringArray(args?.requiredParts) ?? [];
46006
+ const duplicateRequiredParts = duplicateStrings(requiredParts);
46007
+ if (duplicateRequiredParts.length > 0) {
46008
+ return errorToolResult(
46009
+ "invalid_arguments",
46010
+ `\`requiredParts\` names must be unique because Wave getPart(name) requires unambiguous public part names. Duplicates: ${duplicateRequiredParts.join(", ")}.`
46011
+ );
46012
+ }
46013
+ return {
46014
+ assetName: sanitizeAssetName(rawAssetName),
46015
+ sourcePrompt,
46016
+ screenshotPaths: optionalStringArray(args?.screenshotPaths) ?? [],
46017
+ referenceImagePaths: optionalStringArray(args?.referenceImagePaths) ?? [],
46018
+ targetStyle: normalizeOptionalString(args?.targetStyle) ?? null,
46019
+ intendedUse: normalizeOptionalString(args?.intendedUse) ?? null,
46020
+ requiredParts,
46021
+ knownDimensionsMm
46022
+ };
46023
+ }
46024
+ function markdownList(items, emptyText) {
46025
+ if (items.length === 0) return `- ${emptyText}`;
46026
+ return items.map((item) => `- ${item}`).join("\n");
46027
+ }
46028
+ var WAVE_OBJECT_MODEL_PARTS_CONTRACT = `## WaveEngine objectModel SSOT contract
46029
+
46030
+ When requiredParts is non-empty, or the user asks for named/selectable/collidable/material-addressable parts, the GLB must carry Wave objectModel metadata. Mesh names, ordered mesh arrays, and legacy partIds metadata are not source of truth.
46031
+
46032
+ Required GLB shape for part-aware output:
46033
+
46034
+ - Add an empty/transform node named \`__wave_model_object_model\`; it must have no children.
46035
+ - Put \`waveModelObjectModel\` in that node's metadata/extras.
46036
+ - The value must be a valid WaveModelObjectModel JSON object:
46037
+ - \`version: 1\`
46038
+ - \`modelId\`: stable asset id/name
46039
+ - \`revision\`: stable revision string for this generated artifact
46040
+ - \`meshes\`: one record per render mesh with stable \`id\`, \`displayName\`, and \`order\`
46041
+ - \`parts\`: one record per user-facing part with stable \`id\`, public \`name\`, \`order\`, optional \`parentPartId\`, optional \`rotationPivot\`, optional \`visualFrame\`
46042
+ - \`meshPartBindings\`: explicit bindings from mesh id to part id with role \`visual\` or \`collision\`
46043
+ - \`parentPartId\` on child part records when parts have authored parent/child relationships
46044
+ - \`topology\`: include \`rootPartIds\` and \`joints: []\` only when a rig/joint topology is needed
46045
+ - \`materialSlots\` and \`materialBindings\`: canonical material targets when materials are meant to be changed by mesh or part
46046
+ - Mirror only projection hints on each part-owned mesh metadata:
46047
+ - \`waveModelPartId\`: canonical part id
46048
+ - \`waveModelPartName\`: public part name users pass to \`getPart()\`
46049
+ - \`waveModelPartMetadata\`: serializable part metadata when needed
46050
+ - \`waveModelPartParentId\`: only when part has parent part
46051
+
46052
+ Rules:
46053
+
46054
+ - If user did not ask for parts, do not invent parts for every mesh. Output mesh-only GLB.
46055
+ - If a visual part contains multiple meshes, bind all meshes to the same part id.
46056
+ - Preserve authored pivots/origins as objectModel \`rotationPivot\` and \`visualFrame\`, not by baking them away.
46057
+ - Do not write \`metadata.partIds\`, \`extras.partIds\`, mesh-name-derived part ids, or positional part arrays as source of truth.
46058
+ - Validate before upload: every required part name appears in \`objectModel.parts[].name\`, and every part-owned mesh has a matching \`meshPartBindings[]\` record.
46059
+ `;
46060
+ function buildModelingSupportFiles(input) {
46061
+ const requestedPartsText = input.requiredParts.length > 0 ? input.requiredParts.join(", ") : "none explicitly requested; keep output mesh-only unless the source request itself asks for named/selectable/collidable/material-addressable parts";
46062
+ const visiblePartsInstruction = input.requiredParts.length > 0 ? "- Named visible parts matching requiredParts exactly." : "- Mesh/object breakdown only; do not invent Wave parts unless the source request itself asks for named/selectable/collidable/material-addressable parts.";
46063
+ const briefJson = JSON.stringify({
46064
+ assetName: input.assetName,
46065
+ sourcePrompt: input.sourcePrompt,
46066
+ screenshotPaths: input.screenshotPaths,
46067
+ referenceImagePaths: input.referenceImagePaths,
46068
+ targetStyle: input.targetStyle,
46069
+ intendedUse: input.intendedUse,
46070
+ requiredParts: input.requiredParts,
46071
+ knownDimensionsMm: input.knownDimensionsMm,
46072
+ outputContract: {
46073
+ preferredModelFile: `${input.assetName}.glb`,
46074
+ optionalCadFile: `${input.assetName}.step`,
46075
+ waveStudioImport: "Use create_wave_asset_upload/upload_wave_asset after GLB exists, then author code with models.<assetName>.",
46076
+ objectModelSSOT: input.requiredParts.length > 0 ? {
46077
+ required: true,
46078
+ envelopeNodeName: "__wave_model_object_model",
46079
+ envelopeMetadataKey: "waveModelObjectModel",
46080
+ perMeshProjectionKeys: [
46081
+ "waveModelPartId",
46082
+ "waveModelPartName",
46083
+ "waveModelPartMetadata",
46084
+ "waveModelPartParentId"
46085
+ ],
46086
+ forbiddenLegacyTruth: [
46087
+ "metadata.partIds",
46088
+ "extras.partIds",
46089
+ "mesh-name-derived part ids",
46090
+ "ordered mesh arrays as part truth"
46091
+ ]
46092
+ } : {
46093
+ required: false,
46094
+ conditionalRequirement: "Embed objectModel SSOT only if the source request itself asks for named/selectable/collidable/material-addressable parts.",
46095
+ rule: "Do not invent objectModel parts for raw multi-mesh GLB unless user explicitly requests parts."
46096
+ }
46097
+ }
46098
+ }, null, 2);
46099
+ const parameterTemplate = JSON.stringify({
46100
+ assetName: input.assetName,
46101
+ units: "millimeter",
46102
+ parameters: {
46103
+ overall_width: input.knownDimensionsMm?.overall_width ?? 1e3,
46104
+ overall_depth: input.knownDimensionsMm?.overall_depth ?? 600,
46105
+ overall_height: input.knownDimensionsMm?.overall_height ?? 500,
46106
+ bevel_radius: 12
46107
+ },
46108
+ parts: input.requiredParts.length > 0 ? input.requiredParts.map((partName) => ({ name: partName, materialHint: "default" })) : []
46109
+ }, null, 2);
46110
+ return [
46111
+ {
46112
+ fileName: "README.md",
46113
+ role: "workflow",
46114
+ content: `# WaveEngine 3D Modeling Job: ${input.assetName}
46115
+
46116
+ This folder is a local SDK modeling scaffold. It does not mean a GLB has been generated yet.
46117
+
46118
+ ## Source request
46119
+
46120
+ ${input.sourcePrompt}
46121
+
46122
+ ## Screenshot inputs
46123
+
46124
+ ${markdownList(input.screenshotPaths, "No screenshot paths provided. Use capture_wave_screenshot first when visual matching matters.")}
46125
+
46126
+ ## Reference image inputs
46127
+
46128
+ ${markdownList(input.referenceImagePaths, "No reference image paths provided.")}
46129
+
46130
+ ## Workflow
46131
+
46132
+ 1. Use \`cad-sheet-prompt.md\` to derive a CAD sheet or parameter spec from screenshots/reference images.
46133
+ 2. Fill \`parameters.template.json\`.
46134
+ 3. Run \`python build_step.py parameters.template.json out\` after installing CadQuery/OCP locally.
46135
+ 4. Convert the STEP/mesh result to GLB with your approved modeling pipeline.
46136
+ 5. If parts are requested, embed and validate the Wave objectModel SSOT contract before upload.
46137
+ 6. Upload the GLB through Wave Studio MCP asset upload tools.
46138
+ 7. Author scene code with \`models.${input.assetName}\`.
46139
+
46140
+ ## Safety
46141
+
46142
+ Keep API keys and private screenshots out of commits. Generated assets must be reviewed before upload.
46143
+ `
46144
+ },
46145
+ {
46146
+ fileName: "asset-brief.json",
46147
+ role: "brief",
46148
+ content: `${briefJson}
46149
+ `
46150
+ },
46151
+ {
46152
+ fileName: "cad-sheet-prompt.md",
46153
+ role: "prompt",
46154
+ content: `Create a CAD-ready model specification for this WaveEngine asset.
46155
+
46156
+ Asset name: ${input.assetName}
46157
+ Request: ${input.sourcePrompt}
46158
+ Target style: ${input.targetStyle ?? "match source/reference"}
46159
+ Intended use: ${input.intendedUse ?? "WaveEngine scene asset"}
46160
+ Required parts: ${requestedPartsText}
46161
+
46162
+ ${WAVE_OBJECT_MODEL_PARTS_CONTRACT}
46163
+
46164
+ Use the screenshots/reference images as visual evidence. Return:
46165
+
46166
+ - Orthographic front/side/top proportions.
46167
+ ${visiblePartsInstruction}
46168
+ - Dimensions in millimeters.
46169
+ - Bevels, thickness, symmetry, repeating details.
46170
+ - Material/color hints. Do not invent engine internals beyond the Wave objectModel SSOT contract.
46171
+ - A JSON parameter block compatible with parameters.template.json.
46172
+ `
46173
+ },
46174
+ {
46175
+ fileName: "parameter-spec.schema.json",
46176
+ role: "schema",
46177
+ content: `${JSON.stringify({
46178
+ $schema: "https://json-schema.org/draft/2020-12/schema",
46179
+ title: "WaveEngine 3D Modeling Parameters",
46180
+ type: "object",
46181
+ required: ["assetName", "units", "parameters", "parts"],
46182
+ additionalProperties: false,
46183
+ properties: {
46184
+ assetName: { type: "string", minLength: 1 },
46185
+ units: { type: "string", enum: ["millimeter"] },
46186
+ parameters: {
46187
+ type: "object",
46188
+ additionalProperties: { type: ["number", "string", "boolean"] }
46189
+ },
46190
+ parts: {
46191
+ type: "array",
46192
+ items: {
46193
+ type: "object",
46194
+ required: ["name"],
46195
+ additionalProperties: false,
46196
+ properties: {
46197
+ name: { type: "string", minLength: 1 },
46198
+ materialHint: { type: "string" }
46199
+ }
46200
+ }
46201
+ }
46202
+ }
46203
+ }, null, 2)}
46204
+ `
46205
+ },
46206
+ {
46207
+ fileName: "parameters.template.json",
46208
+ role: "parameters",
46209
+ content: `${parameterTemplate}
46210
+ `
46211
+ },
46212
+ {
46213
+ fileName: "build_step.py",
46214
+ role: "python-cad-scaffold",
46215
+ content: `#!/usr/bin/env python3
46216
+ """WaveEngine local CAD scaffold.
46217
+
46218
+ This is a starting point, not a magic model generator. Replace build_model()
46219
+ with the CAD recipe derived from cad-sheet-prompt.md.
46220
+ """
46221
+
46222
+ from __future__ import annotations
46223
+
46224
+ import json
46225
+ import pathlib
46226
+ import sys
46227
+
46228
+ try:
46229
+ import cadquery as cq
46230
+ except ImportError as exc:
46231
+ raise SystemExit(
46232
+ "CadQuery is required. Install it in your local modeling environment before running this scaffold."
46233
+ ) from exc
46234
+
46235
+
46236
+ def read_params(path: pathlib.Path) -> dict:
46237
+ with path.open("r", encoding="utf-8") as handle:
46238
+ return json.load(handle)
46239
+
46240
+
46241
+ def number_param(params: dict, name: str, fallback: float) -> float:
46242
+ value = params.get("parameters", {}).get(name, fallback)
46243
+ if isinstance(value, (int, float)):
46244
+ return float(value)
46245
+ return fallback
46246
+
46247
+
46248
+ def build_model(params: dict) -> cq.Workplane:
46249
+ width = number_param(params, "overall_width", 1000)
46250
+ depth = number_param(params, "overall_depth", 600)
46251
+ height = number_param(params, "overall_height", 500)
46252
+ bevel_radius = number_param(params, "bevel_radius", 12)
46253
+ model = cq.Workplane("XY").box(width, depth, height)
46254
+ if bevel_radius > 0:
46255
+ model = model.edges().fillet(bevel_radius)
46256
+ return model
46257
+
46258
+
46259
+ def main() -> None:
46260
+ if len(sys.argv) != 3:
46261
+ raise SystemExit("usage: python build_step.py parameters.template.json out")
46262
+ params_path = pathlib.Path(sys.argv[1])
46263
+ out_dir = pathlib.Path(sys.argv[2])
46264
+ out_dir.mkdir(parents=True, exist_ok=True)
46265
+ params = read_params(params_path)
46266
+ asset_name = str(params.get("assetName") or "${input.assetName}")
46267
+ step_path = out_dir / f"{asset_name}.step"
46268
+ cq.exporters.export(build_model(params), str(step_path))
46269
+ print(f"Wrote {step_path}")
46270
+
46271
+
46272
+ if __name__ == "__main__":
46273
+ main()
46274
+ `
46275
+ },
46276
+ {
46277
+ fileName: "validation-checklist.md",
46278
+ role: "validation",
46279
+ content: `# Validation checklist
46280
+
46281
+ - Silhouette matches source screenshots from front/side/top.
46282
+ - Scale is correct for WaveEngine world use.
46283
+ - If parts were requested, GLB contains \`waveModelObjectModel\` on the \`__wave_model_object_model\` node metadata/extras.
46284
+ - If parts were requested, every required part is present in \`objectModel.parts[].name\`.
46285
+ - If parts were requested, every part mesh has explicit \`meshPartBindings[]\`; no \`metadata.partIds\` or mesh-name fallback is used.
46286
+ - If no parts were requested, raw multi-mesh GLB remains mesh-only and does not eagerly invent parts.
46287
+ - Part pivots/origins, if authored, survive as \`rotationPivot\` and \`visualFrame\`.
46288
+ - Material slots are clean and simple.
46289
+ - GLB loads in Wave Studio before code authoring.
46290
+ - Uploaded asset appears as \`models.${input.assetName}\`.
46291
+ `
46292
+ }
46293
+ ];
46294
+ }
46295
+ async function createWave3dModelingJobTool(args) {
46296
+ const parsed = parseModelingJobArgs(args);
46297
+ if ("content" in parsed) return parsed;
46298
+ const jobId = `wave_model_${parsed.assetName}_${randomUUID().slice(0, 8)}`;
46299
+ const jobDir = path2.join(tmpdir(), "wave3d-agent-sdk", "modeling-jobs", jobId);
46300
+ const supportFiles = buildModelingSupportFiles(parsed);
46301
+ await mkdir(jobDir, { recursive: true });
46302
+ await Promise.all(supportFiles.map((file) => writeFile(path2.join(jobDir, file.fileName), file.content, "utf8")));
46303
+ return successToolResult({
46304
+ ok: true,
46305
+ tool: "create_wave_3d_modeling_job",
46306
+ status: "scaffolded",
46307
+ localSdk: true,
46308
+ generatedAssetReady: false,
46309
+ jobId,
46310
+ jobDir,
46311
+ assetName: parsed.assetName,
46312
+ supportFiles: supportFiles.map((file) => ({
46313
+ fileName: file.fileName,
46314
+ role: file.role,
46315
+ path: path2.join(jobDir, file.fileName)
46316
+ })),
46317
+ nextActions: [
46318
+ "Use cad-sheet-prompt.md plus screenshots/reference images to produce a CAD parameter spec.",
46319
+ "Fill parameters.template.json and run the Python scaffold only in a local modeling environment with CadQuery installed.",
46320
+ "Convert generated model output to GLB.",
46321
+ "If parts are requested, embed and validate the Wave objectModel SSOT contract before upload.",
46322
+ "Upload with create_wave_asset_upload/upload_wave_asset.",
46323
+ `After upload, author WaveEngine scene code with models.${parsed.assetName}.`
46324
+ ],
46325
+ caveats: [
46326
+ "This tool creates a modeling job scaffold only; it does not call a cloud model generator.",
46327
+ "Do not commit private screenshots, prompts, or generated assets without review."
46328
+ ]
46329
+ });
46330
+ }
46331
+
45913
46332
  // ../../src/lib/waveStudio/aiAssist/bridge/assetListGuidance.ts
45914
46333
  function buildWaveAssetListGuidance(input) {
45915
46334
  const hasQuery = typeof input.query === "string" && input.query.trim().length > 0;
@@ -45918,7 +46337,7 @@ function buildWaveAssetListGuidance(input) {
45918
46337
  const hasFilters = hasQuery || hasBuckets || hasSources;
45919
46338
  const hasMore = input.summary.total > input.summary.returned;
45920
46339
  const guidance = [
45921
- "Use category-first asset discovery for code-safe refs such as models.X, textures.X, materials.X, audios.X, hdr.X, cubeMaps.X, and animations.X. Callable names: `find_wave_assets_by_category`, `list_wave_assets_by_category`, `search_wave_assets_by_category`, or tolerant `list_wave_assets`.",
46340
+ "Use category-first asset discovery for code-safe refs such as models.X, textures.X, materials.X, audios.X, hdr.X, cubeMaps.X, animations.X, and vertexAnimations.X. Callable names: `find_wave_assets_by_category`, `list_wave_assets_by_category`, `search_wave_assets_by_category`, or tolerant `list_wave_assets`.",
45922
46341
  "Choose category from user intent first, then search semantically with `query`; do not browse a broad slice and infer absence."
45923
46342
  ];
45924
46343
  if (hasMore && !hasQuery) {
@@ -45955,7 +46374,7 @@ function buildWaveExplorerAssetListGuidance(input) {
45955
46374
  const hasMore = input.summary.total > input.summary.returned;
45956
46375
  const guidance = [
45957
46376
  "`list_wave_explorer_assets` is for uploaded user-asset management metadata: path, URL, size, display/ref name, rename, and delete.",
45958
- "It is not the fallback for built-in/public code refs. Use category-first asset discovery (`find_wave_assets_by_category` or alias `list_wave_assets_by_category`) when code needs models.X, textures.X, materials.X, audios.X, hdr.X, cubeMaps.X, or animations.X."
46377
+ "It is not the fallback for built-in/public code refs. Use category-first asset discovery (`find_wave_assets_by_category` or alias `list_wave_assets_by_category`) when code needs models.X, textures.X, materials.X, audios.X, hdr.X, cubeMaps.X, animations.X, or vertexAnimations.X."
45959
46378
  ];
45960
46379
  if (input.summary.total === 0 && !hasQuery && !hasAssetTypes) {
45961
46380
  guidance.push("Empty explorer does not mean no code assets exist; it only means this uploaded-user library view has no returned items.");
@@ -46401,7 +46820,7 @@ function reportHotReloadUnsafeUsage(params) {
46401
46820
  })
46402
46821
  });
46403
46822
  }
46404
- function collectRuntimeHotReloadSafetyDiagnostics(path5, code, limit = HOT_RELOAD_UNSAFE_ERROR_LIMIT) {
46823
+ function collectRuntimeHotReloadSafetyDiagnostics(path6, code, limit = HOT_RELOAD_UNSAFE_ERROR_LIMIT) {
46405
46824
  const diagnostics = [];
46406
46825
  if (limit <= 0) return diagnostics;
46407
46826
  const reported = /* @__PURE__ */ new Set();
@@ -46446,34 +46865,34 @@ function collectRuntimeHotReloadSafetyDiagnostics(path5, code, limit = HOT_RELOA
46446
46865
  const receiverPath = previousChar === "." ? readPropertyReceiverPath(code, tokenStart) : "";
46447
46866
  const globalGuidance = HOT_RELOAD_UNSAFE_GLOBAL_CALLBACKS.get(token);
46448
46867
  if (globalGuidance) {
46449
- reportHotReloadUnsafeUsage({ code, path: path5, token, offset: tokenStart, guidance: globalGuidance, diagnostics, reported });
46868
+ reportHotReloadUnsafeUsage({ code, path: path6, token, offset: tokenStart, guidance: globalGuidance, diagnostics, reported });
46450
46869
  continue;
46451
46870
  }
46452
46871
  const listenerGuidance = HOT_RELOAD_UNSAFE_LISTENER_CALLBACKS.get(token);
46453
46872
  if (listenerGuidance && isCall) {
46454
- reportHotReloadUnsafeUsage({ code, path: path5, token, offset: tokenStart, guidance: listenerGuidance, diagnostics, reported });
46873
+ reportHotReloadUnsafeUsage({ code, path: path6, token, offset: tokenStart, guidance: listenerGuidance, diagnostics, reported });
46455
46874
  continue;
46456
46875
  }
46457
46876
  const promiseGuidance = HOT_RELOAD_UNSAFE_PROMISE_CALLBACKS.get(token);
46458
46877
  if (promiseGuidance && previousChar === "." && isCall) {
46459
- reportHotReloadUnsafeUsage({ code, path: path5, token, offset: tokenStart, guidance: promiseGuidance, diagnostics, reported });
46878
+ reportHotReloadUnsafeUsage({ code, path: path6, token, offset: tokenStart, guidance: promiseGuidance, diagnostics, reported });
46460
46879
  continue;
46461
46880
  }
46462
46881
  const eventBusGuidance = HOT_RELOAD_UNSAFE_EVENTBUS_CALLBACKS.get(token);
46463
46882
  if (eventBusGuidance && previousChar === "." && isCall && isWaveEventBusReceiverPath(receiverPath)) {
46464
- reportHotReloadUnsafeUsage({ code, path: path5, token, offset: tokenStart, guidance: eventBusGuidance, diagnostics, reported });
46883
+ reportHotReloadUnsafeUsage({ code, path: path6, token, offset: tokenStart, guidance: eventBusGuidance, diagnostics, reported });
46465
46884
  continue;
46466
46885
  }
46467
46886
  const sceneEventGuidance = HOT_RELOAD_UNSAFE_SCENE_EVENT_CALLBACKS.get(token);
46468
46887
  if (sceneEventGuidance && previousChar === "." && isCall && isWaveSceneEventReceiverPath(receiverPath)) {
46469
- reportHotReloadUnsafeUsage({ code, path: path5, token, offset: tokenStart, guidance: sceneEventGuidance, diagnostics, reported });
46888
+ reportHotReloadUnsafeUsage({ code, path: path6, token, offset: tokenStart, guidance: sceneEventGuidance, diagnostics, reported });
46470
46889
  continue;
46471
46890
  }
46472
46891
  const forbiddenStudioSceneMethodGuidance = HOT_RELOAD_FORBIDDEN_STUDIO_SCENE_METHODS.get(token);
46473
46892
  if (forbiddenStudioSceneMethodGuidance && previousChar === "." && isCall && isWaveSceneEventReceiverPath(receiverPath)) {
46474
46893
  reportHotReloadUnsafeUsage({
46475
46894
  code,
46476
- path: path5,
46895
+ path: path6,
46477
46896
  token,
46478
46897
  offset: tokenStart,
46479
46898
  severity: "error",
@@ -46485,12 +46904,12 @@ function collectRuntimeHotReloadSafetyDiagnostics(path5, code, limit = HOT_RELOA
46485
46904
  }
46486
46905
  const constructorGuidance = HOT_RELOAD_UNSAFE_CONSTRUCTORS.get(token);
46487
46906
  if (constructorGuidance && (previousIdentifier === "new" || previousChar === "." && isCall)) {
46488
- reportHotReloadUnsafeUsage({ code, path: path5, token, offset: tokenStart, guidance: constructorGuidance, diagnostics, reported });
46907
+ reportHotReloadUnsafeUsage({ code, path: path6, token, offset: tokenStart, guidance: constructorGuidance, diagnostics, reported });
46489
46908
  continue;
46490
46909
  }
46491
46910
  const eventHandlerGuidance = HOT_RELOAD_UNSAFE_EVENT_HANDLER_ASSIGNMENTS.get(token);
46492
46911
  if (eventHandlerGuidance && previousChar === "." && nextNonWhitespace === "=") {
46493
- reportHotReloadUnsafeUsage({ code, path: path5, token, offset: tokenStart, guidance: eventHandlerGuidance, diagnostics, reported });
46912
+ reportHotReloadUnsafeUsage({ code, path: path6, token, offset: tokenStart, guidance: eventHandlerGuidance, diagnostics, reported });
46494
46913
  }
46495
46914
  }
46496
46915
  return diagnostics;
@@ -46883,16 +47302,16 @@ function parseMutationOperations(value) {
46883
47302
  }
46884
47303
  return operations;
46885
47304
  }
46886
- function isAuthoredCodePath(path5) {
46887
- const normalized = path5.trim().toLowerCase();
47305
+ function isAuthoredCodePath(path6) {
47306
+ const normalized = path6.trim().toLowerCase();
46888
47307
  return normalized.endsWith(".ts") || normalized.endsWith(".js");
46889
47308
  }
46890
- function warnUnsafeCallbackUsage(path5, code) {
46891
- if (!isAuthoredCodePath(path5) || !code.trim()) return;
46892
- const diagnostics = collectRuntimeHotReloadSafetyDiagnostics(path5, code);
47309
+ function warnUnsafeCallbackUsage(path6, code) {
47310
+ if (!isAuthoredCodePath(path6) || !code.trim()) return;
47311
+ const diagnostics = collectRuntimeHotReloadSafetyDiagnostics(path6, code);
46893
47312
  if (diagnostics.length === 0) return;
46894
47313
  console.warn([
46895
- `[WaveStudio][HotReloadSafety] Strong warning for ${path5}: code edits introduced unmanaged callback roots. The write is allowed, but preview will show a memory leak warning.`,
47314
+ `[WaveStudio][HotReloadSafety] Strong warning for ${path6}: code edits introduced unmanaged callback roots. The write is allowed, but preview will show a memory leak warning.`,
46896
47315
  "Use Wave-owned callbacks, animation DSL, entity lifecycle/tick/state APIs, or Director/input APIs instead.",
46897
47316
  ...diagnostics.map((diagnostic) => diagnostic.message)
46898
47317
  ].join("\n"));
@@ -47055,8 +47474,8 @@ async function handleVfsTool(input) {
47055
47474
  return errorToolResult("session_not_found", LOCAL_SESSION_NOT_FOUND_MESSAGE);
47056
47475
  }
47057
47476
  const pathInput = typeof args?.path === "string" ? args.path : typeof args?.filePath === "string" ? args.filePath : "";
47058
- const path5 = normalizeWaveFilePath(pathInput);
47059
- if (!path5) {
47477
+ const path6 = normalizeWaveFilePath(pathInput);
47478
+ if (!path6) {
47060
47479
  return errorToolResult("invalid_arguments", "`read_wave_file` requires a non-empty string `path` (alias: `filePath`).");
47061
47480
  }
47062
47481
  const startLine = normalizeOptionalPositiveInteger(args?.startLine);
@@ -47075,7 +47494,7 @@ async function handleVfsTool(input) {
47075
47494
  try {
47076
47495
  const result = await context.commandBroker.enqueue(session.sessionId, {
47077
47496
  type: "readFile",
47078
- path: path5,
47497
+ path: path6,
47079
47498
  ...ifMatchHash ? { ifMatchHash } : {},
47080
47499
  ...typeof effectiveStartLine === "number" ? { startLine: effectiveStartLine } : {},
47081
47500
  ...typeof endLine === "number" ? { endLine } : {}
@@ -47097,7 +47516,7 @@ async function handleVfsTool(input) {
47097
47516
  ...result.payload.range ? { editHint: "This was a scoped line read. For edits in this slice, prefer edit_wave_file with the same full-file line numbers. baseHash is optional for simple edits." } : {}
47098
47517
  });
47099
47518
  } catch (error) {
47100
- return errorToolResult("command_timeout", error instanceof Error ? error.message : "WaveEngine Agent SDK file read timed out.", { path: path5 });
47519
+ return errorToolResult("command_timeout", error instanceof Error ? error.message : "WaveEngine Agent SDK file read timed out.", { path: path6 });
47101
47520
  }
47102
47521
  }
47103
47522
  if (name === "edit_wave_file") {
@@ -47109,21 +47528,21 @@ async function handleVfsTool(input) {
47109
47528
  ]);
47110
47529
  if ("result" in contentResult) return contentResult.result;
47111
47530
  if (typeof args?.blockContaining !== "undefined" || typeof args?.replaceBlockContaining !== "undefined" || typeof args?.deleteStatementContaining !== "undefined" || typeof args?.replaceBetween !== "undefined") {
47112
- const path5 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47113
- if (!path5) return errorToolResult("invalid_arguments", "`edit_wave_file` requires a non-empty string `path`.");
47531
+ const path6 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47532
+ if (!path6) return errorToolResult("invalid_arguments", "`edit_wave_file` requires a non-empty string `path`.");
47114
47533
  const deleteNeedle = normalizeOptionalString(args?.deleteStatementContaining);
47115
47534
  const replacementResult = deleteNeedle ? { value: "" } : resolveReplacementAlias("text", args?.text, args?.replacement);
47116
47535
  if ("result" in replacementResult) return replacementResult.result;
47117
47536
  if (typeof replacementResult.value !== "string") {
47118
47537
  return errorToolResult("invalid_arguments", "Block edit requires `text`/`replacement`, or `deleteStatementContaining` for deletion.");
47119
47538
  }
47120
- warnUnsafeCallbackUsage(path5, replacementResult.value);
47539
+ warnUnsafeCallbackUsage(path6, replacementResult.value);
47121
47540
  const sessionResult = resolveWritableSession(args, context);
47122
47541
  if ("result" in sessionResult) return sessionResult.result;
47123
47542
  const resolvedHash = await resolveEditBaseHash({
47124
47543
  context,
47125
47544
  session: sessionResult.session,
47126
- path: path5,
47545
+ path: path6,
47127
47546
  baseHash: args?.baseHash
47128
47547
  });
47129
47548
  if ("result" in resolvedHash) return resolvedHash.result;
@@ -47145,7 +47564,7 @@ async function handleVfsTool(input) {
47145
47564
  session: sessionResult.session,
47146
47565
  operations: [{
47147
47566
  type: "lineEdits",
47148
- path: path5,
47567
+ path: path6,
47149
47568
  baseHash: resolvedHash.baseHash,
47150
47569
  edits: [{
47151
47570
  startLine: range.startLine,
@@ -47159,7 +47578,7 @@ async function handleVfsTool(input) {
47159
47578
  return withEditBaseHashMetadata(result, resolvedHash, {
47160
47579
  editMode: deleteNeedle ? "deleteStatementContaining" : "replaceBlockContaining",
47161
47580
  editTarget: {
47162
- path: path5,
47581
+ path: path6,
47163
47582
  startLine: range.startLine,
47164
47583
  endLine: range.endLine,
47165
47584
  targetReason: range.reason,
@@ -47172,28 +47591,28 @@ async function handleVfsTool(input) {
47172
47591
  const editArray = parseEditWaveFileEditsArray(args?.edits);
47173
47592
  if (editArray) {
47174
47593
  if ("result" in editArray) return editArray.result;
47175
- const path5 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47176
- if (!path5) return errorToolResult("invalid_arguments", "`edit_wave_file` requires a non-empty string `path`.");
47594
+ const path6 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47595
+ if (!path6) return errorToolResult("invalid_arguments", "`edit_wave_file` requires a non-empty string `path`.");
47177
47596
  for (const edit of editArray.edits) {
47178
- warnUnsafeCallbackUsage(path5, edit.text);
47597
+ warnUnsafeCallbackUsage(path6, edit.text);
47179
47598
  }
47180
47599
  const sessionResult = resolveWritableSession(args, context);
47181
47600
  if ("result" in sessionResult) return sessionResult.result;
47182
47601
  const resolvedHash = await resolveEditBaseHash({
47183
47602
  context,
47184
47603
  session: sessionResult.session,
47185
- path: path5,
47604
+ path: path6,
47186
47605
  baseHash: args?.baseHash
47187
47606
  });
47188
47607
  if ("result" in resolvedHash) return resolvedHash.result;
47189
47608
  const operations = editArray.type === "lineEdits" ? [{
47190
47609
  type: "lineEdits",
47191
- path: path5,
47610
+ path: path6,
47192
47611
  baseHash: resolvedHash.baseHash,
47193
47612
  edits: editArray.edits
47194
47613
  }] : [{
47195
47614
  type: "textEdits",
47196
- path: path5,
47615
+ path: path6,
47197
47616
  baseHash: resolvedHash.baseHash,
47198
47617
  edits: editArray.edits
47199
47618
  }];
@@ -47207,17 +47626,17 @@ async function handleVfsTool(input) {
47207
47626
  return withEditBaseHashMetadata(result, resolvedHash, {
47208
47627
  editMode: editArray.type === "lineEdits" ? "multiLineEdits" : "multiTextEdits",
47209
47628
  editTarget: {
47210
- path: path5,
47629
+ path: path6,
47211
47630
  editCount: editArray.edits.length
47212
47631
  }
47213
47632
  });
47214
47633
  }
47215
47634
  const inferredMode = normalizeEditWaveFileMode(args?.mode) ?? (requireNonEmptyString(args?.oldText) ? "replaceText" : typeof args?.startLine !== "undefined" || typeof args?.endLine !== "undefined" ? "replaceLines" : typeof contentResult.value === "string" ? "replaceWholeFile" : "");
47216
47635
  if (inferredMode === "replaceLines") {
47217
- const path5 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47636
+ const path6 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47218
47637
  const startLine = normalizeOptionalPositiveInteger(args?.startLine);
47219
47638
  const endLine = normalizeOptionalPositiveInteger(args?.endLine);
47220
- if (!path5) return errorToolResult("invalid_arguments", "`edit_wave_file` requires a non-empty string `path`.");
47639
+ if (!path6) return errorToolResult("invalid_arguments", "`edit_wave_file` requires a non-empty string `path`.");
47221
47640
  if (startLine === null || typeof startLine === "undefined") {
47222
47641
  return errorToolResult("invalid_arguments", "`startLine` must be a positive integer.");
47223
47642
  }
@@ -47232,13 +47651,13 @@ async function handleVfsTool(input) {
47232
47651
  if (typeof replacementResult.value !== "string") {
47233
47652
  return errorToolResult("invalid_arguments", "`text` or `replacement` must be a string. Use an empty string to delete the line range.");
47234
47653
  }
47235
- warnUnsafeCallbackUsage(path5, replacementResult.value);
47654
+ warnUnsafeCallbackUsage(path6, replacementResult.value);
47236
47655
  const sessionResult = resolveWritableSession(args, context);
47237
47656
  if ("result" in sessionResult) return sessionResult.result;
47238
47657
  const resolvedHash = await resolveEditBaseHash({
47239
47658
  context,
47240
47659
  session: sessionResult.session,
47241
- path: path5,
47660
+ path: path6,
47242
47661
  baseHash: args?.baseHash
47243
47662
  });
47244
47663
  if ("result" in resolvedHash) return resolvedHash.result;
@@ -47262,7 +47681,7 @@ async function handleVfsTool(input) {
47262
47681
  session: sessionResult.session,
47263
47682
  operations: [{
47264
47683
  type: "lineEdits",
47265
- path: path5,
47684
+ path: path6,
47266
47685
  baseHash: resolvedHash.baseHash,
47267
47686
  edits: [{
47268
47687
  startLine,
@@ -47276,7 +47695,7 @@ async function handleVfsTool(input) {
47276
47695
  return withEditBaseHashMetadata(result, resolvedHash, {
47277
47696
  editMode: "replaceLines",
47278
47697
  editTarget: {
47279
- path: path5,
47698
+ path: path6,
47280
47699
  startLine,
47281
47700
  endLine: finalEndLine,
47282
47701
  requestedEndLine,
@@ -47289,18 +47708,18 @@ async function handleVfsTool(input) {
47289
47708
  });
47290
47709
  }
47291
47710
  if (inferredMode === "replaceText") {
47292
- const path5 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47711
+ const path6 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47293
47712
  const oldText = requireNonEmptyString(args?.oldText);
47294
- if (!path5) return errorToolResult("invalid_arguments", "`edit_wave_file` requires a non-empty string `path`.");
47713
+ if (!path6) return errorToolResult("invalid_arguments", "`edit_wave_file` requires a non-empty string `path`.");
47295
47714
  if (!oldText) {
47296
47715
  if (typeof contentResult.value === "string") {
47297
- warnUnsafeCallbackUsage(path5, contentResult.value);
47716
+ warnUnsafeCallbackUsage(path6, contentResult.value);
47298
47717
  const sessionResult2 = resolveWritableSession(args, context);
47299
47718
  if ("result" in sessionResult2) return sessionResult2.result;
47300
47719
  const resolvedHash2 = await resolveEditBaseHash({
47301
47720
  context,
47302
47721
  session: sessionResult2.session,
47303
- path: path5,
47722
+ path: path6,
47304
47723
  baseHash: args?.baseHash
47305
47724
  });
47306
47725
  if ("result" in resolvedHash2) return resolvedHash2.result;
@@ -47309,7 +47728,7 @@ async function handleVfsTool(input) {
47309
47728
  session: sessionResult2.session,
47310
47729
  operations: [{
47311
47730
  type: "writeFile",
47312
- path: path5,
47731
+ path: path6,
47313
47732
  baseHash: resolvedHash2.baseHash,
47314
47733
  content: contentResult.value
47315
47734
  }],
@@ -47325,7 +47744,7 @@ async function handleVfsTool(input) {
47325
47744
  if (typeof replacementResult.value !== "string") {
47326
47745
  return errorToolResult("invalid_arguments", "`newText` or `replacement` must be a string. Use an empty string to delete the matched text.");
47327
47746
  }
47328
- warnUnsafeCallbackUsage(path5, replacementResult.value);
47747
+ warnUnsafeCallbackUsage(path6, replacementResult.value);
47329
47748
  const sessionResult = resolveWritableSession(args, context);
47330
47749
  if ("result" in sessionResult) return sessionResult.result;
47331
47750
  const hasOccurrenceIndex = typeof args?.occurrenceIndex !== "undefined";
@@ -47338,7 +47757,7 @@ async function handleVfsTool(input) {
47338
47757
  const resolvedHash = await resolveEditBaseHash({
47339
47758
  context,
47340
47759
  session: sessionResult.session,
47341
- path: path5,
47760
+ path: path6,
47342
47761
  baseHash: args?.baseHash
47343
47762
  });
47344
47763
  if ("result" in resolvedHash) return resolvedHash.result;
@@ -47347,7 +47766,7 @@ async function handleVfsTool(input) {
47347
47766
  session: sessionResult.session,
47348
47767
  operations: [{
47349
47768
  type: "searchReplace",
47350
- path: path5,
47769
+ path: path6,
47351
47770
  baseHash: resolvedHash.baseHash,
47352
47771
  oldText,
47353
47772
  newText: replacementResult.value,
@@ -47360,15 +47779,15 @@ async function handleVfsTool(input) {
47360
47779
  return withEditBaseHashMetadata(result, resolvedHash);
47361
47780
  }
47362
47781
  if (inferredMode === "replaceWholeFile" && typeof contentResult.value === "string") {
47363
- const path5 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47364
- if (!path5) return errorToolResult("invalid_arguments", "`edit_wave_file` requires a non-empty string `path`.");
47365
- warnUnsafeCallbackUsage(path5, contentResult.value);
47782
+ const path6 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47783
+ if (!path6) return errorToolResult("invalid_arguments", "`edit_wave_file` requires a non-empty string `path`.");
47784
+ warnUnsafeCallbackUsage(path6, contentResult.value);
47366
47785
  const sessionResult = resolveWritableSession(args, context);
47367
47786
  if ("result" in sessionResult) return sessionResult.result;
47368
47787
  const resolvedHash = await resolveEditBaseHash({
47369
47788
  context,
47370
47789
  session: sessionResult.session,
47371
- path: path5,
47790
+ path: path6,
47372
47791
  baseHash: args?.baseHash
47373
47792
  });
47374
47793
  if ("result" in resolvedHash) return resolvedHash.result;
@@ -47377,7 +47796,7 @@ async function handleVfsTool(input) {
47377
47796
  session: sessionResult.session,
47378
47797
  operations: [{
47379
47798
  type: "writeFile",
47380
- path: path5,
47799
+ path: path6,
47381
47800
  baseHash: resolvedHash.baseHash,
47382
47801
  content: contentResult.value
47383
47802
  }],
@@ -47389,12 +47808,12 @@ async function handleVfsTool(input) {
47389
47808
  return errorToolResult("invalid_arguments", "`edit_wave_file` could not infer the edit shape. Use oldText+newText, startLine+endLine+text, or path+text/content for whole-file replacement.");
47390
47809
  }
47391
47810
  if (name === "create_wave_file") {
47392
- const path5 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47393
- if (!path5) return errorToolResult("invalid_arguments", "`create_wave_file` requires a non-empty string `path`.");
47811
+ const path6 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47812
+ if (!path6) return errorToolResult("invalid_arguments", "`create_wave_file` requires a non-empty string `path`.");
47394
47813
  if (typeof args?.content !== "string") {
47395
47814
  return errorToolResult("invalid_arguments", "`content` must be a string.");
47396
47815
  }
47397
- warnUnsafeCallbackUsage(path5, args.content);
47816
+ warnUnsafeCallbackUsage(path6, args.content);
47398
47817
  const overwrite = args?.overwrite === true || args?.forceOverwrite === true || args?.replaceExisting === true;
47399
47818
  const sessionResult = resolveWritableSession(args, context);
47400
47819
  if ("result" in sessionResult) return sessionResult.result;
@@ -47403,7 +47822,7 @@ async function handleVfsTool(input) {
47403
47822
  session: sessionResult.session,
47404
47823
  operations: [{
47405
47824
  type: "createFile",
47406
- path: path5,
47825
+ path: path6,
47407
47826
  content: args.content,
47408
47827
  runnable: typeof args?.runnable === "boolean" ? args.runnable : true,
47409
47828
  ...overwrite ? { overwrite: true } : {}
@@ -47434,14 +47853,14 @@ async function handleVfsTool(input) {
47434
47853
  if (name === "delete_wave_file") {
47435
47854
  const sessionResult = resolveWritableSession(args, context);
47436
47855
  if ("result" in sessionResult) return sessionResult.result;
47437
- const path5 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47438
- if (!path5) return errorToolResult("invalid_arguments", "`delete_wave_file` requires a non-empty string `path`.");
47856
+ const path6 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47857
+ if (!path6) return errorToolResult("invalid_arguments", "`delete_wave_file` requires a non-empty string `path`.");
47439
47858
  return await applyMutationOperations({
47440
47859
  context,
47441
47860
  session: sessionResult.session,
47442
47861
  operations: [{
47443
47862
  type: "deleteFile",
47444
- path: path5
47863
+ path: path6
47445
47864
  }],
47446
47865
  ...runtimeVerificationArgs(args)
47447
47866
  });
@@ -47659,7 +48078,7 @@ var ASSET_CODE_REF_DISCOVERY_INPUT_SCHEMA = {
47659
48078
  properties: {
47660
48079
  category: {
47661
48080
  type: "string",
47662
- description: `Preferred asset category or alias. Use user intent to fill this first. Supported categories/aliases from Wave Studio asset SSOT: ${ASSET_CATEGORY_HELP}. Examples: sound/audio -> audios, 3D model/character -> models, material -> materials, texture/image -> textures, HDR sky -> hdr, cube-map sky/environment -> cubeMaps, animation -> animations.`
48081
+ description: `Preferred asset category or alias. Use user intent to fill this first. Supported categories/aliases from Wave Studio asset SSOT: ${ASSET_CATEGORY_HELP}. Examples: sound/audio -> audios, 3D model/character -> models, material -> materials, texture/image -> textures, HDR sky -> hdr, cube-map sky/environment -> cubeMaps, skeletal animation -> animations, baked vertex/VAT animation -> vertexAnimations.`
47663
48082
  },
47664
48083
  bucket: {
47665
48084
  type: "string",
@@ -48389,7 +48808,7 @@ function buildHostedWaveStudioMcpToolDefinitions() {
48389
48808
  },
48390
48809
  {
48391
48810
  name: "find_wave_assets_by_category",
48392
- description: `[studio.assets] Canonical category-first discovery for code refs. Use when code needs an existing asset: choose an asset category from the user intent first (model/sound/material/texture/sky/animation/etc.), then search the paired Wave Studio asset surface: public assets, uploaded user assets, and project aliases. Preferred shape: { category:"materials", query:"grass", limit:50 }. Friendly aliases \`list_wave_assets_by_category\`, \`search_wave_assets_by_category\`, and \`list_wave_assets\` route here. Returned bare refs are safe in code, e.g. models.Car, textures.Grass, materials.Wood, audios.Click, hdr.Sky, animations.Run. The category-filtered response can still be capped; do not infer absence without semantic query refinement.${BROWSER_COMMAND_PENDING_NOTE}`,
48811
+ description: `[studio.assets] Canonical category-first discovery for code refs. Use when code needs an existing asset: choose an asset category from the user intent first (model/sound/material/texture/sky/animation/VAT/etc.), then search the paired Wave Studio asset surface: public assets, uploaded user assets, and project aliases. Preferred shape: { category:"materials", query:"grass", limit:50 }. Friendly aliases \`list_wave_assets_by_category\`, \`search_wave_assets_by_category\`, and \`list_wave_assets\` route here. Returned bare refs are safe in code, e.g. models.Car, textures.Grass, materials.Wood, audios.Click, hdr.Sky, animations.Run, vertexAnimations.HorseGallopVat. The category-filtered response can still be capped; do not infer absence without semantic query refinement.${BROWSER_COMMAND_PENDING_NOTE}`,
48393
48812
  inputSchema: ASSET_CODE_REF_DISCOVERY_INPUT_SCHEMA
48394
48813
  },
48395
48814
  {
@@ -48436,7 +48855,7 @@ function buildHostedWaveStudioMcpToolDefinitions() {
48436
48855
  },
48437
48856
  {
48438
48857
  name: "upload_wave_asset",
48439
- description: `[studio.assets] Small inline upload only: upload bytes already loaded by the agent as base64/data URL. This tool does not read local paths. Upload decision tree: tiny bytes already in memory -> upload_wave_asset; local file path/normal size -> create_wave_asset_upload + raw PUT + commit_wave_asset_upload; direct_upload_unavailable or chunk-only environment -> stage_wave_asset_upload_chunk + commit_wave_asset_chunk_upload. assetKind values come from the Wave Studio upload policy SSOT: ${BRIDGE_ASSET_UPLOAD_KIND_HELP}. Meshoptimizer defaults on for model-family uploads only; useMeshoptimizer is ignored for non-model assets unless explicitly supported by the browser pipeline. Pass exactly one of dataBase64 or dataUrl. Returns typed refs such as textures.Albedo, cubeMaps.Studio, materials.Wood, audios.Click, models.Robot, animations.Run, fonts.Title, or serializedData.Config.${WRITE_PAIRING_REQUIRED_NOTE}${BROWSER_COMMAND_PENDING_NOTE}`,
48858
+ description: `[studio.assets] Small inline upload only: upload bytes already loaded by the agent as base64/data URL. This tool does not read local paths. Upload decision tree: tiny bytes already in memory -> upload_wave_asset; local file path/normal size -> create_wave_asset_upload + raw PUT + commit_wave_asset_upload; direct_upload_unavailable or chunk-only environment -> stage_wave_asset_upload_chunk + commit_wave_asset_chunk_upload. assetKind values come from the Wave Studio upload policy SSOT: ${BRIDGE_ASSET_UPLOAD_KIND_HELP}. Meshoptimizer defaults on for model-family uploads only; useMeshoptimizer is ignored for non-model assets unless explicitly supported by the browser pipeline. Pass exactly one of dataBase64 or dataUrl. Returns typed refs such as textures.Albedo, cubeMaps.Studio, materials.Wood, audios.Click, models.Robot, animations.Run, vertexAnimations.HorseGallopVat, fonts.Title, or serializedData.Config.${WRITE_PAIRING_REQUIRED_NOTE}${BROWSER_COMMAND_PENDING_NOTE}`,
48440
48859
  inputSchema: {
48441
48860
  type: "object",
48442
48861
  properties: {
@@ -49497,7 +49916,11 @@ var LOCAL_TOOL_DEFINITION_OVERRIDE_NAMES = /* @__PURE__ */ new Set([
49497
49916
  var LOCAL_EXTRA_TOOL_DEFINITION_NAMES = /* @__PURE__ */ new Set([
49498
49917
  "self_check_wave_mcp",
49499
49918
  "list_wave_sessions",
49500
- "get_active_wave_session"
49919
+ "get_active_wave_session",
49920
+ "create_wave_3d_modeling_job"
49921
+ ]);
49922
+ var LOCAL_SDK_MODELING_TOOL_NAMES = /* @__PURE__ */ new Set([
49923
+ "create_wave_3d_modeling_job"
49501
49924
  ]);
49502
49925
  var LOCAL_OPTIONAL_SESSION_TOOL_NAMES = /* @__PURE__ */ new Set([
49503
49926
  "get_wave_session",
@@ -49566,6 +49989,7 @@ var LOCAL_SDK_CONTROL_TOOL_NAMES = /* @__PURE__ */ new Set([
49566
49989
  var LOCAL_SDK_IMPLEMENTED_CANONICAL_TOOL_NAMES = /* @__PURE__ */ new Set([
49567
49990
  ...LOCAL_SDK_CONTROL_TOOL_NAMES,
49568
49991
  ...LOCAL_SDK_LOOKUP_TOOL_NAMES,
49992
+ ...LOCAL_SDK_MODELING_TOOL_NAMES,
49569
49993
  ...[...LOCAL_OPTIONAL_SESSION_TOOL_NAMES].map((name) => canonicalizeWaveMcpToolName(name))
49570
49994
  ]);
49571
49995
  function isLocalMcpToolImplemented(name) {
@@ -49688,6 +50112,60 @@ function getReadToolDefinitions() {
49688
50112
  additionalProperties: false
49689
50113
  }
49690
50114
  },
50115
+ {
50116
+ name: "create_wave_3d_modeling_job",
50117
+ description: "[wave.modeling] Create a local SDK 3D-modeling job scaffold from a prompt and optional screenshots/reference image paths. Writes README, CAD prompt, JSON parameter template/schema, Python CadQuery STEP scaffold, and validation checklist into a temp job folder. Does not generate/upload a GLB by itself; use asset upload tools after a real model file exists. If requiredParts is supplied, the resulting GLB must embed Wave objectModel SSOT metadata via waveModelObjectModel on a __wave_model_object_model node plus per-mesh projection keys; raw mesh names, metadata.partIds, extras.partIds, or positional mesh arrays are not part truth. Aliases: generate_wave_3d_model, create_wave_3d_model, create_wave_3d_model_from_screenshots.",
50118
+ inputSchema: {
50119
+ type: "object",
50120
+ properties: {
50121
+ sourcePrompt: {
50122
+ type: "string",
50123
+ description: 'Required modeling goal, e.g. "make a low-poly sea lion matching the screenshot". Aliases accepted by handler: prompt, description.'
50124
+ },
50125
+ prompt: {
50126
+ type: "string",
50127
+ description: "Alias for sourcePrompt."
50128
+ },
50129
+ description: {
50130
+ type: "string",
50131
+ description: "Alias for sourcePrompt."
50132
+ },
50133
+ assetName: {
50134
+ type: "string",
50135
+ description: "Optional public Wave asset/model name stem. Handler sanitizes to lowercase snake_case."
50136
+ },
50137
+ screenshotPaths: {
50138
+ type: "array",
50139
+ items: { type: "string" },
50140
+ description: "Optional local screenshot paths, usually returned from capture_wave_screenshot or supplied by user."
50141
+ },
50142
+ referenceImagePaths: {
50143
+ type: "array",
50144
+ items: { type: "string" },
50145
+ description: "Optional local reference image paths."
50146
+ },
50147
+ targetStyle: {
50148
+ type: "string",
50149
+ description: "Optional visual style constraint."
50150
+ },
50151
+ intendedUse: {
50152
+ type: "string",
50153
+ description: "Optional Wave scene usage target such as prop, actor, background, UI model, collision mesh."
50154
+ },
50155
+ requiredParts: {
50156
+ type: "array",
50157
+ items: { type: "string" },
50158
+ description: "Optional public part names the generated GLB must preserve in objectModel SSOT. When non-empty, model output must include waveModelObjectModel on a __wave_model_object_model node with parts, meshPartBindings, parentPartId/topology/material data as needed, and per-mesh waveModelPartId/waveModelPartName projection keys."
50159
+ },
50160
+ knownDimensionsMm: {
50161
+ type: "object",
50162
+ description: 'Optional JSON-safe dimension hints in millimeters, e.g. {"overall_width": 1200, "overall_height": 500}.',
50163
+ additionalProperties: true
50164
+ }
50165
+ },
50166
+ additionalProperties: false
50167
+ }
50168
+ },
49691
50169
  {
49692
50170
  name: "get_wave_session",
49693
50171
  description: "Return the Wave Studio browser session paired through the WaveEngine Agent SDK. Full Studio tools are available when this local endpoint is called with the adopted HTTP Gateway token from Wave Studio.",
@@ -49792,6 +50270,7 @@ function getLocalToolImplementationSummary() {
49792
50270
  implementedCanonicalToolCount: implementedCanonicalToolNames.length,
49793
50271
  localExtraToolNames: [...LOCAL_EXTRA_TOOL_DEFINITION_NAMES].sort(),
49794
50272
  localLookupToolNames: [...LOCAL_SDK_LOOKUP_TOOL_NAMES].sort(),
50273
+ localModelingToolNames: [...LOCAL_SDK_MODELING_TOOL_NAMES].sort(),
49795
50274
  omittedCanonicalToolNames: canonicalToolNames.filter((name) => !isLocalMcpToolImplemented(name))
49796
50275
  };
49797
50276
  }
@@ -49815,7 +50294,8 @@ function localSdkLookupStatus(waveEngineSdkCache) {
49815
50294
  corpusCacheReady,
49816
50295
  apiAndSkillQueryAvailable: corpusCacheReady,
49817
50296
  cacheMissAffectsLiveStudioTransport: false,
49818
- lookupToolNames: [...LOCAL_SDK_LOOKUP_TOOL_NAMES].sort()
50297
+ lookupToolNames: [...LOCAL_SDK_LOOKUP_TOOL_NAMES].sort(),
50298
+ localModelingToolNames: [...LOCAL_SDK_MODELING_TOOL_NAMES].sort()
49819
50299
  };
49820
50300
  }
49821
50301
  function jsonRpcToolIdempotencyKey(toolName, requestId) {
@@ -50280,10 +50760,10 @@ function screenshotBasePayload(payload, suggestedFilename) {
50280
50760
  };
50281
50761
  }
50282
50762
  async function saveScreenshotPayloadToLocalFile(payload, suggestedFilename) {
50283
- const directory = path2.join(tmpdir(), "wave3d-agent-sdk", "screenshots");
50284
- await mkdir(directory, { recursive: true });
50285
- const localPath = path2.join(directory, suggestedFilename);
50286
- await writeFile(localPath, Buffer.from(payload.dataBase64, "base64"));
50763
+ const directory = path3.join(tmpdir2(), "wave3d-agent-sdk", "screenshots");
50764
+ await mkdir2(directory, { recursive: true });
50765
+ const localPath = path3.join(directory, suggestedFilename);
50766
+ await writeFile2(localPath, Buffer.from(payload.dataBase64, "base64"));
50287
50767
  return localPath;
50288
50768
  }
50289
50769
  function normalizeRuntimeMarkerSource(value) {
@@ -50476,7 +50956,7 @@ async function callReadTool(name, args, context) {
50476
50956
  if (name === "start_wave_task") {
50477
50957
  const routeResult = startWaveMcpTaskRoute({
50478
50958
  args,
50479
- taskRouteId: `wgtask_${randomUUID()}`,
50959
+ taskRouteId: `wgtask_${randomUUID2()}`,
50480
50960
  now: nowIso(),
50481
50961
  seedEvidence: mergeWaveMcpTaskEvidence(
50482
50962
  getLocalPreTaskEvidence(context) ?? createWaveMcpEmptyTaskEvidence(),
@@ -50665,7 +51145,8 @@ async function callReadTool(name, args, context) {
50665
51145
  "studio.project: list_wave_project_templates, new_wave_project, list_wave_projects, read_wave_project, rename_wave_project, open_wave_project, save_wave_project, share_wave_project",
50666
51146
  "studio.preview: hot_reload_wave_preview (alias: hotreload_wave_preview), run_wave_preview (alias: run_project), pause_wave_preview (alias: pause_wave_engine), resume_wave_preview (alias: resume_wave_engine), get_wave_runtime_diagnostics (alias: get_wave_errors), get_wave_runtime_performance_snapshot (alias: get_wave_runtime_performance)",
50667
51147
  "studio.capture: capture_wave_screenshot (alias: get_wave_screenshot), get_wave_runtime_entity_snapshot (alias: get_wave_entity_snapshot), list_wave_runtime_markers (alias: list_wave_markers)",
50668
- "wave.authoring: list_wave_skill_families, query_wave_skills, get_wave_skill, query_wave_api"
51148
+ "wave.authoring: list_wave_skill_families, query_wave_skills, get_wave_skill, query_wave_api",
51149
+ "wave.modeling: create_wave_3d_modeling_job (aliases: generate_wave_3d_model, create_wave_3d_model, create_wave_3d_model_from_screenshots) creates local SDK support files; if parts are requested the generated GLB must embed waveModelObjectModel on a __wave_model_object_model node before upload"
50669
51150
  ],
50670
51151
  rules: [
50671
51152
  "studio.* labels are categories, not callable tools.",
@@ -50686,6 +51167,9 @@ async function callReadTool(name, args, context) {
50686
51167
  args
50687
51168
  });
50688
51169
  }
51170
+ if (name === "create_wave_3d_modeling_job") {
51171
+ return await createWave3dModelingJobTool(args);
51172
+ }
50689
51173
  if (name === "get_wave_command_result") {
50690
51174
  const requestId = requireNonEmptyString(args?.requestId);
50691
51175
  if (!requestId) {
@@ -50938,15 +51422,15 @@ async function callReadTool(name, args, context) {
50938
51422
  if (name === "rename_wave_asset") {
50939
51423
  const sessionResult = resolveWritableSession(args, context);
50940
51424
  if ("result" in sessionResult) return sessionResult.result;
50941
- const path5 = requireNonEmptyString(args?.path);
51425
+ const path6 = requireNonEmptyString(args?.path);
50942
51426
  const nextName = requireNonEmptyString(stringAliasArg(args, ["name", "newName", "displayName"]));
50943
- if (!path5 || !nextName) return errorToolResult("invalid_arguments", "`rename_wave_asset` requires non-empty `path` and `name`.");
51427
+ if (!path6 || !nextName) return errorToolResult("invalid_arguments", "`rename_wave_asset` requires non-empty `path` and `name`.");
50944
51428
  return await enqueueBrowserCommand({
50945
51429
  context,
50946
51430
  session: sessionResult.session,
50947
51431
  command: {
50948
51432
  type: "renameAsset",
50949
- path: path5,
51433
+ path: path6,
50950
51434
  name: nextName
50951
51435
  }
50952
51436
  });
@@ -50954,14 +51438,14 @@ async function callReadTool(name, args, context) {
50954
51438
  if (name === "delete_wave_asset") {
50955
51439
  const sessionResult = resolveWritableSession(args, context);
50956
51440
  if ("result" in sessionResult) return sessionResult.result;
50957
- const path5 = requireNonEmptyString(args?.path);
50958
- if (!path5) return errorToolResult("invalid_arguments", "`delete_wave_asset` requires a non-empty `path`.");
51441
+ const path6 = requireNonEmptyString(args?.path);
51442
+ if (!path6) return errorToolResult("invalid_arguments", "`delete_wave_asset` requires a non-empty `path`.");
50959
51443
  return await enqueueBrowserCommand({
50960
51444
  context,
50961
51445
  session: sessionResult.session,
50962
51446
  command: {
50963
51447
  type: "deleteAsset",
50964
- path: path5
51448
+ path: path6
50965
51449
  }
50966
51450
  });
50967
51451
  }
@@ -51415,9 +51899,9 @@ async function handleWaveGenieMcpRequest(request, context) {
51415
51899
  }
51416
51900
 
51417
51901
  // ../../scripts/wavegenie-sdk/sessionRegistry.ts
51418
- import { createHash as createHash4, randomUUID as randomUUID2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
51902
+ import { createHash as createHash4, randomUUID as randomUUID3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
51419
51903
  function createSessionId() {
51420
- return `wgb_${randomUUID2()}`;
51904
+ return `wgb_${randomUUID3()}`;
51421
51905
  }
51422
51906
  function nowIso2() {
51423
51907
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -51557,18 +52041,18 @@ var WaveGenieBridgeSessionRegistry = class {
51557
52041
  };
51558
52042
 
51559
52043
  // ../../scripts/wavegenie-sdk/assetStaging.ts
51560
- import { createHash as createHash5, randomBytes as randomBytes2, randomUUID as randomUUID3 } from "node:crypto";
52044
+ import { createHash as createHash5, randomBytes as randomBytes2, randomUUID as randomUUID4 } from "node:crypto";
51561
52045
  import { createWriteStream } from "node:fs";
51562
- import { mkdir as mkdir2, rm, writeFile as writeFile2 } from "node:fs/promises";
51563
- import { tmpdir as tmpdir2 } from "node:os";
51564
- import path3 from "node:path";
52046
+ import { mkdir as mkdir3, rm, writeFile as writeFile3 } from "node:fs/promises";
52047
+ import { tmpdir as tmpdir3 } from "node:os";
52048
+ import path4 from "node:path";
51565
52049
  var MAX_LOCAL_ASSET_UPLOAD_BYTES = 300 * 1024 * 1024;
51566
52050
  var MAX_LOCAL_ASSET_UPLOAD_CHUNKS = 256;
51567
- var STAGING_ROOT = path3.join(tmpdir2(), "wave3d-agent-sdk-asset-staging");
52051
+ var STAGING_ROOT = path4.join(tmpdir3(), "wave3d-agent-sdk-asset-staging");
51568
52052
  var LOCAL_DIRECT_UPLOAD_TTL_MS = 60 * 60 * 1e3;
51569
52053
  var DEFAULT_LOCAL_ASSET_UPLOAD_TTL_MS = 24 * 60 * 60 * 1e3;
51570
52054
  function createUploadId() {
51571
- return `wgbupload_${randomUUID3()}`;
52055
+ return `wgbupload_${randomUUID4()}`;
51572
52056
  }
51573
52057
  function createSecret() {
51574
52058
  return randomBytes2(24).toString("base64url");
@@ -51628,8 +52112,8 @@ var LocalAssetUploadStagingStore = class {
51628
52112
  this.uploads = /* @__PURE__ */ new Map();
51629
52113
  }
51630
52114
  async ensureUploadDir(uploadId) {
51631
- const uploadDir = path3.join(STAGING_ROOT, hashPathPart(uploadId));
51632
- await mkdir2(uploadDir, { recursive: true });
52115
+ const uploadDir = path4.join(STAGING_ROOT, hashPathPart(uploadId));
52116
+ await mkdir3(uploadDir, { recursive: true });
51633
52117
  return uploadDir;
51634
52118
  }
51635
52119
  createPartUrl(uploadId, chunkIndex, readToken) {
@@ -51668,8 +52152,8 @@ var LocalAssetUploadStagingStore = class {
51668
52152
  const currentBytes = [...upload.parts.values()].filter((part2) => part2.chunkIndex !== chunkIndex).reduce((total, part2) => total + part2.sizeBytes, 0);
51669
52153
  assertUploadSize(currentBytes + bytes.byteLength);
51670
52154
  const uploadDir = await this.ensureUploadDir(upload.uploadId);
51671
- const filePath = path3.join(uploadDir, `part-${chunkIndex}.bin`);
51672
- await writeFile2(filePath, bytes);
52155
+ const filePath = path4.join(uploadDir, `part-${chunkIndex}.bin`);
52156
+ await writeFile3(filePath, bytes);
51673
52157
  const part = {
51674
52158
  chunkIndex,
51675
52159
  sizeBytes: bytes.byteLength,
@@ -51682,7 +52166,7 @@ var LocalAssetUploadStagingStore = class {
51682
52166
  async putStreamPart(input) {
51683
52167
  const currentBytes = [...input.upload.parts.values()].filter((part2) => part2.chunkIndex !== input.chunkIndex).reduce((total, part2) => total + part2.sizeBytes, 0);
51684
52168
  const uploadDir = await this.ensureUploadDir(input.upload.uploadId);
51685
- const filePath = path3.join(uploadDir, `part-${input.chunkIndex}.bin`);
52169
+ const filePath = path4.join(uploadDir, `part-${input.chunkIndex}.bin`);
51686
52170
  const output = createWriteStream(filePath, { flags: "w" });
51687
52171
  let writtenBytes = 0;
51688
52172
  try {
@@ -51836,7 +52320,7 @@ var LocalAssetUploadStagingStore = class {
51836
52320
  const upload = this.uploads.get(uploadId);
51837
52321
  if (!upload) return 0;
51838
52322
  this.uploads.delete(uploadId);
51839
- const uploadDir = path3.join(STAGING_ROOT, hashPathPart(uploadId));
52323
+ const uploadDir = path4.join(STAGING_ROOT, hashPathPart(uploadId));
51840
52324
  await rm(uploadDir, { recursive: true, force: true });
51841
52325
  return upload.parts.size;
51842
52326
  }
@@ -51861,7 +52345,7 @@ var MAX_DIRECT_UPLOAD_BODY_BYTES = 300 * 1024 * 1024;
51861
52345
  var LOCAL_ORIGIN_RE = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/i;
51862
52346
  var TRUSTED_WAVE_STUDIO_ORIGIN_RE = /^https:\/\/(www\.)?wave3d\.ai$/i;
51863
52347
  function createBridgeId() {
51864
- return `wgbridge_${randomUUID4()}`;
52348
+ return `wgbridge_${randomUUID5()}`;
51865
52349
  }
51866
52350
  function isAllowedStudioOrigin(origin) {
51867
52351
  if (!origin) return true;
@@ -52364,10 +52848,10 @@ function readBooleanFlag(options, name) {
52364
52848
  return options.flags.get(name) === true;
52365
52849
  }
52366
52850
  function getDefaultCacheRoot() {
52367
- return path4.join(homedir2(), ".wave3d", "agent-cache");
52851
+ return path5.join(homedir2(), ".wave3d", "agent-cache");
52368
52852
  }
52369
52853
  function getCacheRoot(options) {
52370
- return path4.resolve(readStringFlag(options, "cache-dir") ?? process.env[CACHE_DIR_ENV] ?? getDefaultCacheRoot());
52854
+ return path5.resolve(readStringFlag(options, "cache-dir") ?? process.env[CACHE_DIR_ENV] ?? getDefaultCacheRoot());
52371
52855
  }
52372
52856
  function getDefaultMcpUrl(options) {
52373
52857
  const port = readNumberFlag(options, "port") ?? DEFAULT_PORT2;
@@ -52386,29 +52870,29 @@ function assertSafeListenHost(host, allowNetwork) {
52386
52870
  throw new Error(`Refusing to listen on non-loopback host ${host}. Use --allow-network only on a trusted network.`);
52387
52871
  }
52388
52872
  function assertSafeCacheRoot(cacheRoot) {
52389
- const resolved = path4.resolve(cacheRoot);
52390
- const root = path4.parse(resolved).root;
52873
+ const resolved = path5.resolve(cacheRoot);
52874
+ const root = path5.parse(resolved).root;
52391
52875
  const dangerous = /* @__PURE__ */ new Set([
52392
52876
  root,
52393
- path4.resolve(homedir2()),
52394
- path4.resolve(process.cwd()),
52395
- path4.resolve(path4.dirname(homedir2()))
52877
+ path5.resolve(homedir2()),
52878
+ path5.resolve(process.cwd()),
52879
+ path5.resolve(path5.dirname(homedir2()))
52396
52880
  ]);
52397
52881
  if (dangerous.has(resolved)) {
52398
52882
  throw new Error(`Refusing to use dangerous cache directory: ${resolved}`);
52399
52883
  }
52400
- const depth = path4.relative(root, resolved).split(path4.sep).filter(Boolean).length;
52884
+ const depth = path5.relative(root, resolved).split(path5.sep).filter(Boolean).length;
52401
52885
  if (depth < 2) {
52402
52886
  throw new Error(`Refusing to use shallow cache directory: ${resolved}`);
52403
52887
  }
52404
52888
  }
52405
52889
  function isDefaultCacheRoot(cacheRoot) {
52406
- return path4.resolve(cacheRoot) === path4.resolve(getDefaultCacheRoot());
52890
+ return path5.resolve(cacheRoot) === path5.resolve(getDefaultCacheRoot());
52407
52891
  }
52408
52892
  async function markCacheRoot(cacheRoot) {
52409
52893
  assertSafeCacheRoot(cacheRoot);
52410
- await mkdir3(cacheRoot, { recursive: true });
52411
- await writeFile3(path4.join(cacheRoot, CACHE_MARKER_FILE), "wave3d-agent-cache\n");
52894
+ await mkdir4(cacheRoot, { recursive: true });
52895
+ await writeFile4(path5.join(cacheRoot, CACHE_MARKER_FILE), "wave3d-agent-cache\n");
52412
52896
  }
52413
52897
  function isSafePathSegment(value) {
52414
52898
  return /^[A-Za-z0-9._-]+$/.test(value);
@@ -52737,11 +53221,11 @@ function assertCorpusManifestMatches(actual, expected) {
52737
53221
  }
52738
53222
  }
52739
53223
  function getBundledSdkDirectory() {
52740
- return path4.join(path4.dirname(fileURLToPath(import.meta.url)), BUNDLED_SDK_DIR_NAME);
53224
+ return path5.join(path5.dirname(fileURLToPath(import.meta.url)), BUNDLED_SDK_DIR_NAME);
52741
53225
  }
52742
53226
  async function readBundledSdkManifest() {
52743
53227
  try {
52744
- const parsed = JSON.parse(await readFile2(path4.join(getBundledSdkDirectory(), "manifest.json"), "utf8"));
53228
+ const parsed = JSON.parse(await readFile2(path5.join(getBundledSdkDirectory(), "manifest.json"), "utf8"));
52745
53229
  return isCorpusManifest(parsed) ? parsed : null;
52746
53230
  } catch {
52747
53231
  return null;
@@ -52749,7 +53233,7 @@ async function readBundledSdkManifest() {
52749
53233
  }
52750
53234
  async function readBundledSdkZip(manifest) {
52751
53235
  const bundleFileName = requireSafePathSegment(manifest.bundleFileName, "Bundled SDK zip file name");
52752
- const bytes = await readFile2(path4.join(getBundledSdkDirectory(), bundleFileName));
53236
+ const bytes = await readFile2(path5.join(getBundledSdkDirectory(), bundleFileName));
52753
53237
  if (bytes.length > MAX_CORPUS_ZIP_BYTES2) {
52754
53238
  throw new Error(`Bundled SDK zip is too large: ${bytes.length} bytes.`);
52755
53239
  }
@@ -52760,11 +53244,11 @@ async function writeCorpusCacheFromZip(input) {
52760
53244
  await markCacheRoot(cacheRoot);
52761
53245
  const cacheKey = requireSafePathSegment(input.manifest.cacheKey, "Corpus cacheKey");
52762
53246
  const bundleFileName = requireSafePathSegment(input.manifest.bundleFileName, "Corpus bundle file name");
52763
- const versionDir = path4.join(cacheRoot, cacheKey);
52764
- const corpusDir = path4.join(versionDir, "corpus");
52765
- await mkdir3(versionDir, { recursive: true });
52766
- await writeFile3(path4.join(versionDir, "corpus-handshake.json"), JSON.stringify(input.handshake, null, 2));
52767
- await writeFile3(path4.join(versionDir, bundleFileName), input.zipBytes);
53247
+ const versionDir = path5.join(cacheRoot, cacheKey);
53248
+ const corpusDir = path5.join(versionDir, "corpus");
53249
+ await mkdir4(versionDir, { recursive: true });
53250
+ await writeFile4(path5.join(versionDir, "corpus-handshake.json"), JSON.stringify(input.handshake, null, 2));
53251
+ await writeFile4(path5.join(versionDir, bundleFileName), input.zipBytes);
52768
53252
  await extractZipToDirectory(input.zipBytes, corpusDir, input.manifest);
52769
53253
  await buildSearchIndex(corpusDir);
52770
53254
  const current = {
@@ -52776,7 +53260,7 @@ async function writeCorpusCacheFromZip(input) {
52776
53260
  sourceVersions: input.manifest.sourceVersions,
52777
53261
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
52778
53262
  };
52779
- await writeFile3(path4.join(cacheRoot, "current.json"), JSON.stringify(current, null, 2));
53263
+ await writeFile4(path5.join(cacheRoot, "current.json"), JSON.stringify(current, null, 2));
52780
53264
  return current;
52781
53265
  }
52782
53266
  async function ensureBundledSdkCache(options, input) {
@@ -52915,16 +53399,16 @@ async function commandDoctor(options) {
52915
53399
  await commandCacheStatus(options);
52916
53400
  }
52917
53401
  function normalizeZipEntryPath(entryName) {
52918
- const normalized = path4.posix.normalize(entryName.replace(/\\/g, "/"));
52919
- if (normalized === "." || normalized === ".." || normalized.startsWith("../") || normalized.includes("\0") || path4.posix.isAbsolute(normalized)) {
53402
+ const normalized = path5.posix.normalize(entryName.replace(/\\/g, "/"));
53403
+ if (normalized === "." || normalized === ".." || normalized.startsWith("../") || normalized.includes("\0") || path5.posix.isAbsolute(normalized)) {
52920
53404
  throw new Error(`Unsafe corpus zip entry: ${entryName}`);
52921
53405
  }
52922
53406
  return normalized;
52923
53407
  }
52924
53408
  function resolveZipOutputPath(targetDir, normalizedEntryName) {
52925
- const outputPath = path4.resolve(targetDir, ...normalizedEntryName.split("/"));
52926
- const relative = path4.relative(targetDir, outputPath);
52927
- if (relative.startsWith("..") || path4.isAbsolute(relative)) {
53409
+ const outputPath = path5.resolve(targetDir, ...normalizedEntryName.split("/"));
53410
+ const relative = path5.relative(targetDir, outputPath);
53411
+ if (relative.startsWith("..") || path5.isAbsolute(relative)) {
52928
53412
  throw new Error(`Unsafe corpus zip output path: ${normalizedEntryName}`);
52929
53413
  }
52930
53414
  return outputPath;
@@ -52938,8 +53422,8 @@ function isCorpusCurrentRecord(value) {
52938
53422
  return isRecord4(value) && typeof value.cacheKey === "string" && typeof value.version === "string" && (typeof value.formatVersion === "undefined" || typeof value.formatVersion === "string") && typeof value.bundleHash === "string" && typeof value.corpusDir === "string" && (typeof value.sourceVersions === "undefined" || isRecord4(value.sourceVersions)) && (typeof value.updatedAt === "undefined" || typeof value.updatedAt === "string");
52939
53423
  }
52940
53424
  function isPathInsideDirectory2(parentDir, childPath) {
52941
- const relative = path4.relative(path4.resolve(parentDir), path4.resolve(childPath));
52942
- return relative === "" || !!relative && !relative.startsWith("..") && !path4.isAbsolute(relative);
53425
+ const relative = path5.relative(path5.resolve(parentDir), path5.resolve(childPath));
53426
+ return relative === "" || !!relative && !relative.startsWith("..") && !path5.isAbsolute(relative);
52943
53427
  }
52944
53428
  async function readCorpusManifestFromDirectory(corpusDir) {
52945
53429
  try {
@@ -52984,7 +53468,7 @@ async function extractZipToDirectory(zipBytes, targetDir, manifest) {
52984
53468
  const allowedPaths = /* @__PURE__ */ new Set(["manifest.json", ...expectedFiles.keys()]);
52985
53469
  const extractedFilePaths = /* @__PURE__ */ new Set();
52986
53470
  await rm2(targetDir, { recursive: true, force: true });
52987
- await mkdir3(targetDir, { recursive: true });
53471
+ await mkdir4(targetDir, { recursive: true });
52988
53472
  const entries = Object.values(zip.files);
52989
53473
  if (entries.length > MAX_CORPUS_FILE_COUNT) {
52990
53474
  throw new Error(`Corpus zip has too many entries: ${entries.length}.`);
@@ -53001,7 +53485,7 @@ async function extractZipToDirectory(zipBytes, targetDir, manifest) {
53001
53485
  throw new Error(`Corpus extracted size is too large: ${extractedBytes + expectedSize} bytes.`);
53002
53486
  }
53003
53487
  const outputPath = resolveZipOutputPath(targetDir, normalized);
53004
- await mkdir3(path4.dirname(outputPath), { recursive: true });
53488
+ await mkdir4(path5.dirname(outputPath), { recursive: true });
53005
53489
  const content = await entry.async("nodebuffer");
53006
53490
  extractedBytes += content.length;
53007
53491
  if (extractedBytes > MAX_CORPUS_EXTRACTED_BYTES2) {
@@ -53019,7 +53503,7 @@ async function extractZipToDirectory(zipBytes, targetDir, manifest) {
53019
53503
  }
53020
53504
  extractedFilePaths.add(normalized);
53021
53505
  }
53022
- await writeFile3(outputPath, content);
53506
+ await writeFile4(outputPath, content);
53023
53507
  }
53024
53508
  for (const expectedPath of expectedFiles.keys()) {
53025
53509
  if (!extractedFilePaths.has(expectedPath)) {
@@ -53031,7 +53515,7 @@ function parseJsonLines2(content) {
53031
53515
  return content.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line));
53032
53516
  }
53033
53517
  async function buildSearchIndex(corpusDir) {
53034
- const searchPath = path4.join(corpusDir, "search", "search-documents.jsonl");
53518
+ const searchPath = path5.join(corpusDir, "search", "search-documents.jsonl");
53035
53519
  const content = await readFile2(searchPath, "utf8");
53036
53520
  const documents = parseJsonLines2(content);
53037
53521
  const uniqueDocuments = [];
@@ -53058,7 +53542,7 @@ async function buildSearchIndex(corpusDir) {
53058
53542
  }
53059
53543
  });
53060
53544
  miniSearch.addAll(uniqueDocuments);
53061
- await writeFile3(path4.join(corpusDir, "search", "minisearch-index.json"), JSON.stringify(miniSearch.toJSON()));
53545
+ await writeFile4(path5.join(corpusDir, "search", "minisearch-index.json"), JSON.stringify(miniSearch.toJSON()));
53062
53546
  }
53063
53547
  async function commandCacheRefresh(options) {
53064
53548
  const mcpUrl = readStringFlag(options, "mcp-url");
@@ -53076,12 +53560,12 @@ async function commandCacheRefresh(options) {
53076
53560
  console.log(`Cache root: ${getCacheRoot(options)}`);
53077
53561
  }
53078
53562
  async function readCurrentCache(cacheRoot) {
53079
- const currentPath = path4.join(cacheRoot, "current.json");
53563
+ const currentPath = path5.join(cacheRoot, "current.json");
53080
53564
  if (!existsSync2(currentPath)) return null;
53081
53565
  try {
53082
53566
  const parsed = JSON.parse(await readFile2(currentPath, "utf8"));
53083
53567
  if (!isCorpusCurrentRecord(parsed)) return null;
53084
- const corpusDir = path4.resolve(parsed.corpusDir);
53568
+ const corpusDir = path5.resolve(parsed.corpusDir);
53085
53569
  if (!isPathInsideDirectory2(cacheRoot, corpusDir)) return null;
53086
53570
  const manifest = await readCorpusManifestFromDirectory(corpusDir);
53087
53571
  if (!manifest || !corpusCurrentMatchesManifest(parsed, manifest)) return null;
@@ -53110,7 +53594,7 @@ async function commandCacheStatus(options) {
53110
53594
  async function commandCacheClear(options) {
53111
53595
  const cacheRoot = getCacheRoot(options);
53112
53596
  assertSafeCacheRoot(cacheRoot);
53113
- if (!isDefaultCacheRoot(cacheRoot) && !existsSync2(path4.join(cacheRoot, CACHE_MARKER_FILE))) {
53597
+ if (!isDefaultCacheRoot(cacheRoot) && !existsSync2(path5.join(cacheRoot, CACHE_MARKER_FILE))) {
53114
53598
  throw new Error(`Refusing to clear unmarked custom cache directory: ${cacheRoot}`);
53115
53599
  }
53116
53600
  await rm2(cacheRoot, { recursive: true, force: true });
@@ -53124,7 +53608,7 @@ async function commandCacheSearch(options) {
53124
53608
  const current = await readCurrentCache(cacheRoot);
53125
53609
  const corpusDir = typeof current?.corpusDir === "string" ? current.corpusDir : null;
53126
53610
  if (!corpusDir) throw new Error("No bundled SDK cache is available. Reinstall/update wave3d-agent-sdk.");
53127
- const indexPath = path4.join(corpusDir, "search", "minisearch-index.json");
53611
+ const indexPath = path5.join(corpusDir, "search", "minisearch-index.json");
53128
53612
  const rawIndex = await readFile2(indexPath, "utf8");
53129
53613
  const miniSearch = MiniSearch2.loadJSON(rawIndex, {
53130
53614
  fields: ["title", "text"],