wave3d-agent-sdk 0.2.13 → 0.2.14

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";
@@ -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).
@@ -42520,8 +42520,8 @@ function normalizeWaveGenieAssetUploadRequest(request) {
42520
42520
 
42521
42521
  // ../../src/lib/waveStudio/aiAssist/bridge/localAgentSdkContract.ts
42522
42522
  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";
42523
+ var WAVE3D_AGENT_SDK_REQUIRED_VERSION = "0.2.14";
42524
+ var WAVE3D_AGENT_SDK_REQUIRED_BUILD = "agent-sdk-20260625.2";
42525
42525
  var WAVE3D_AGENT_SDK_PACKAGE_SPEC = `${WAVE3D_AGENT_SDK_PACKAGE_NAME}@${WAVE3D_AGENT_SDK_REQUIRED_VERSION}`;
42526
42526
  var WAVE3D_AGENT_SDK_START_COMMAND = `npx -y ${WAVE3D_AGENT_SDK_PACKAGE_SPEC} start`;
42527
42527
  var WAVE3D_AGENT_SDK_TOKEN_ENV_VAR = "WAVE3D_MCP_TOKEN";
@@ -42660,7 +42660,12 @@ var WAVE_MCP_TOOL_ALIAS_CANONICAL_NAMES = {
42660
42660
  mcp_status: "get_wave_mcp_health",
42661
42661
  get_mcp_health: "get_wave_mcp_health",
42662
42662
  get_mcp_status: "get_wave_mcp_health",
42663
- get_wave_mcp_status: "get_wave_mcp_health"
42663
+ get_wave_mcp_status: "get_wave_mcp_health",
42664
+ generate_wave_3d_model: "create_wave_3d_modeling_job",
42665
+ create_wave_3d_model: "create_wave_3d_modeling_job",
42666
+ create_wave_3d_model_from_screenshots: "create_wave_3d_modeling_job",
42667
+ make_wave_3d_model_from_screenshot: "create_wave_3d_modeling_job",
42668
+ scaffold_wave_3d_modeling_job: "create_wave_3d_modeling_job"
42664
42669
  };
42665
42670
  function canonicalizeWaveMcpToolName(toolName) {
42666
42671
  return WAVE_MCP_TOOL_ALIAS_CANONICAL_NAMES[toolName] ?? toolName;
@@ -42802,6 +42807,7 @@ var WAVE_MCP_TOOL_CONTRACTS = {
42802
42807
  get_wave_runtime_entity_snapshot: { workMode: "observe", domain: "runtime", safety: "readOnly", targetPolicy: "none", requiresTaskRoute: false, supportsTaskRoute: true },
42803
42808
  list_wave_runtime_markers: { workMode: "observe", domain: "marker", safety: "readOnly", targetPolicy: "none", requiresTaskRoute: false, supportsTaskRoute: true },
42804
42809
  capture_wave_screenshot: { workMode: "observe", domain: "visual", safety: "readOnly", targetPolicy: "none", requiresTaskRoute: false, supportsTaskRoute: true },
42810
+ create_wave_3d_modeling_job: { workMode: "operate", domain: "modeling", safety: "workspaceOperation", targetPolicy: "none", requiresTaskRoute: false, supportsTaskRoute: true, directIntentKind: "asset.manage", directEditIntent: "modifyAssets" },
42805
42811
  query_wave_api: { workMode: "author", domain: "code", safety: "authoringMutation", targetPolicy: "none", requiresTaskRoute: true, supportsTaskRoute: true },
42806
42812
  edit_wave_file: { workMode: "author", domain: "code", safety: "authoringMutation", targetPolicy: "exactPathRequired", requiresTaskRoute: true, supportsTaskRoute: true },
42807
42813
  apply_wave_patch: { workMode: "author", domain: "code", safety: "authoringMutation", targetPolicy: "exactPathRequired", requiresTaskRoute: true, supportsTaskRoute: true },
@@ -42944,6 +42950,9 @@ var WAVE_MCP_ASSET_STAGING_STORE_TOOL_NAMES = /* @__PURE__ */ new Set([
42944
42950
  "get_wave_asset_upload_status",
42945
42951
  "abort_wave_asset_upload"
42946
42952
  ]);
42953
+ var WAVE_MCP_LOCAL_SDK_WORKSPACE_TOOL_NAMES = /* @__PURE__ */ new Set([
42954
+ "create_wave_3d_modeling_job"
42955
+ ]);
42947
42956
  function getWaveMcpToolExecutionScope(toolName) {
42948
42957
  const canonicalToolName = canonicalizeWaveMcpToolName(toolName);
42949
42958
  if (WAVE_MCP_SESSION_DISCOVERY_TOOL_NAMES.has(canonicalToolName)) return "sessionDiscovery";
@@ -42952,6 +42961,7 @@ function getWaveMcpToolExecutionScope(toolName) {
42952
42961
  if (WAVE_MCP_REFERENCE_LOOKUP_TOOL_NAMES.has(canonicalToolName)) return "referenceLookup";
42953
42962
  if (canonicalToolName === "get_wave_command_result") return "commandStatus";
42954
42963
  if (WAVE_MCP_ASSET_STAGING_STORE_TOOL_NAMES.has(canonicalToolName)) return "assetStagingStore";
42964
+ if (WAVE_MCP_LOCAL_SDK_WORKSPACE_TOOL_NAMES.has(canonicalToolName)) return "localSdkWorkspace";
42955
42965
  return "pairedStudioSession";
42956
42966
  }
42957
42967
  function waveMcpToolRequiresStudioSession(toolName) {
@@ -42973,6 +42983,7 @@ function waveMcpToolFamily(toolName) {
42973
42983
  const contract = getWaveMcpToolContract(toolName);
42974
42984
  if (contract?.domain === "vfs") return "studio.vfs";
42975
42985
  if (contract?.domain === "asset") return "studio.assets";
42986
+ if (contract?.domain === "modeling") return "wave.modeling";
42976
42987
  if (contract?.domain === "project") return "studio.project";
42977
42988
  if (contract?.domain === "preview") return "studio.preview";
42978
42989
  if (contract?.domain === "runtime") return "studio.preview";
@@ -43720,8 +43731,8 @@ function hasStringProperty(args, names) {
43720
43731
  return names.some((name) => typeof args[name] === "string");
43721
43732
  }
43722
43733
  function editWaveFileArgsAreExact(args) {
43723
- const path5 = trimString(args?.path, 240);
43724
- if (!path5) return false;
43734
+ const path6 = trimString(args?.path, 240);
43735
+ if (!path6) return false;
43725
43736
  const hasExactLineRange = isPositiveInteger(args?.startLine) && isPositiveInteger(args?.endLine) && hasStringProperty(args, ["text", "replacement"]);
43726
43737
  const hasExactTextReplace = typeof args?.oldText === "string" && args.oldText.length > 0 && hasStringProperty(args, ["newText", "replacement"]);
43727
43738
  return hasExactLineRange || hasExactTextReplace;
@@ -43755,7 +43766,7 @@ function waveMcpToolRoutingNote(toolName) {
43755
43766
  return " Direct Observe tool: call directly without start_wave_task. Pass taskRouteId only to attach this observation to an active Author/Diagnose route.";
43756
43767
  }
43757
43768
  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.";
43769
+ 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
43770
  }
43760
43771
  if (contract.workMode === "author" && contract.safety === "readOnly") {
43761
43772
  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 +44101,14 @@ function validateWaveMcpTaskToolCall(input) {
44090
44101
  function recordWaveMcpTaskEvidenceUse(input) {
44091
44102
  let evidence = input.evidence;
44092
44103
  const canonicalToolName = canonicalizeWaveMcpToolName(input.toolName);
44093
- const path5 = trimString(input.args?.path, 240);
44104
+ const path6 = trimString(input.args?.path, 240);
44094
44105
  const skillId = trimString(input.args?.skillId, 120) || trimString(input.args?.id, 120);
44095
44106
  if (canonicalToolName === "list_wave_files") evidence = { ...evidence, filesListed: true };
44096
44107
  if (canonicalToolName === "read_wave_file") {
44097
44108
  evidence = {
44098
44109
  ...evidence,
44099
44110
  filesListed: true,
44100
- filesRead: uniqueStrings([...evidence.filesRead, path5].filter(Boolean), 64)
44111
+ filesRead: uniqueStrings([...evidence.filesRead, path6].filter(Boolean), 64)
44101
44112
  };
44102
44113
  }
44103
44114
  if (canonicalToolName === "find_wave_assets_by_category") evidence = { ...evidence, assetsListed: true };
@@ -44794,8 +44805,8 @@ function optionalStringArray(value) {
44794
44805
  const strings = rawItems.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
44795
44806
  return strings.length > 0 ? strings : void 0;
44796
44807
  }
44797
- function normalizeWaveFilePath(path5) {
44798
- const trimmed = path5.trim();
44808
+ function normalizeWaveFilePath(path6) {
44809
+ const trimmed = path6.trim();
44799
44810
  if (!trimmed) return trimmed;
44800
44811
  return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
44801
44812
  }
@@ -44931,9 +44942,9 @@ function readPathArray(value) {
44931
44942
  return value.map((item) => {
44932
44943
  if (typeof item === "string") return item;
44933
44944
  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);
44945
+ const path6 = record?.path;
44946
+ return typeof path6 === "string" ? path6 : null;
44947
+ }).filter((path6) => typeof path6 === "string" && path6.length > 0);
44937
44948
  }
44938
44949
  function readRecord(value) {
44939
44950
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -45910,6 +45921,318 @@ async function runLocalSdkLookupTool(input) {
45910
45921
  );
45911
45922
  }
45912
45923
 
45924
+ // ../../scripts/wavegenie-sdk/modeling/modelingJob.ts
45925
+ import { randomUUID } from "node:crypto";
45926
+ import { mkdir, writeFile } from "node:fs/promises";
45927
+ import { tmpdir } from "node:os";
45928
+ import path2 from "node:path";
45929
+ function sanitizeAssetName(rawName) {
45930
+ const sanitized = rawName.trim().toLowerCase().replace(/[^a-z0-9]+/gu, "_").replace(/^_+|_+$/gu, "");
45931
+ return sanitized || "generated_model";
45932
+ }
45933
+ function jsonValueOrNull(value) {
45934
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
45935
+ return typeof value === "number" && Number.isNaN(value) ? null : value;
45936
+ }
45937
+ if (Array.isArray(value)) {
45938
+ const items = value.map((item) => jsonValueOrNull(item));
45939
+ return items.every((item) => item !== null) ? items : null;
45940
+ }
45941
+ if (isRecord2(value)) {
45942
+ const entries = Object.entries(value);
45943
+ const result = {};
45944
+ for (const [key, item] of entries) {
45945
+ const normalized = jsonValueOrNull(item);
45946
+ if (normalized === null) return null;
45947
+ result[key] = normalized;
45948
+ }
45949
+ return result;
45950
+ }
45951
+ return null;
45952
+ }
45953
+ function normalizeKnownDimensionsMm(value) {
45954
+ if (!isRecord2(value)) return null;
45955
+ const result = {};
45956
+ for (const [key, item] of Object.entries(value)) {
45957
+ if (!key.trim()) continue;
45958
+ const normalized = jsonValueOrNull(item);
45959
+ if (normalized === null) return null;
45960
+ result[key.trim()] = normalized;
45961
+ }
45962
+ return Object.keys(result).length > 0 ? result : null;
45963
+ }
45964
+ function parseModelingJobArgs(args) {
45965
+ const sourcePrompt = normalizeOptionalString(args?.sourcePrompt) ?? normalizeOptionalString(args?.prompt) ?? normalizeOptionalString(args?.description);
45966
+ if (!sourcePrompt) {
45967
+ return errorToolResult(
45968
+ "invalid_arguments",
45969
+ "`create_wave_3d_modeling_job` needs sourcePrompt, prompt, or description."
45970
+ );
45971
+ }
45972
+ const rawAssetName = normalizeOptionalString(args?.assetName) ?? normalizeOptionalString(args?.name) ?? sourcePrompt.split(/\s+/u).slice(0, 5).join("_");
45973
+ const knownDimensionsMm = typeof args?.knownDimensionsMm === "undefined" ? null : normalizeKnownDimensionsMm(args.knownDimensionsMm);
45974
+ if (typeof args?.knownDimensionsMm !== "undefined" && !knownDimensionsMm) {
45975
+ return errorToolResult(
45976
+ "invalid_arguments",
45977
+ "`knownDimensionsMm` must be a JSON object with only JSON-safe values."
45978
+ );
45979
+ }
45980
+ return {
45981
+ assetName: sanitizeAssetName(rawAssetName),
45982
+ sourcePrompt,
45983
+ screenshotPaths: optionalStringArray(args?.screenshotPaths) ?? [],
45984
+ referenceImagePaths: optionalStringArray(args?.referenceImagePaths) ?? [],
45985
+ targetStyle: normalizeOptionalString(args?.targetStyle) ?? null,
45986
+ intendedUse: normalizeOptionalString(args?.intendedUse) ?? null,
45987
+ requiredParts: optionalStringArray(args?.requiredParts) ?? [],
45988
+ knownDimensionsMm
45989
+ };
45990
+ }
45991
+ function markdownList(items, emptyText) {
45992
+ if (items.length === 0) return `- ${emptyText}`;
45993
+ return items.map((item) => `- ${item}`).join("\n");
45994
+ }
45995
+ function buildModelingSupportFiles(input) {
45996
+ const briefJson = JSON.stringify({
45997
+ assetName: input.assetName,
45998
+ sourcePrompt: input.sourcePrompt,
45999
+ screenshotPaths: input.screenshotPaths,
46000
+ referenceImagePaths: input.referenceImagePaths,
46001
+ targetStyle: input.targetStyle,
46002
+ intendedUse: input.intendedUse,
46003
+ requiredParts: input.requiredParts,
46004
+ knownDimensionsMm: input.knownDimensionsMm,
46005
+ outputContract: {
46006
+ preferredModelFile: `${input.assetName}.glb`,
46007
+ optionalCadFile: `${input.assetName}.step`,
46008
+ waveStudioImport: "Use create_wave_asset_upload/upload_wave_asset after GLB exists, then author code with models.<assetName>."
46009
+ }
46010
+ }, null, 2);
46011
+ const parameterTemplate = JSON.stringify({
46012
+ assetName: input.assetName,
46013
+ units: "millimeter",
46014
+ parameters: {
46015
+ overall_width: input.knownDimensionsMm?.overall_width ?? 1e3,
46016
+ overall_depth: input.knownDimensionsMm?.overall_depth ?? 600,
46017
+ overall_height: input.knownDimensionsMm?.overall_height ?? 500,
46018
+ bevel_radius: 12
46019
+ },
46020
+ parts: input.requiredParts.length > 0 ? input.requiredParts.map((partName) => ({ name: partName, materialHint: "default" })) : [{ name: "body", materialHint: "default" }]
46021
+ }, null, 2);
46022
+ return [
46023
+ {
46024
+ fileName: "README.md",
46025
+ role: "workflow",
46026
+ content: `# WaveEngine 3D Modeling Job: ${input.assetName}
46027
+
46028
+ This folder is a local SDK modeling scaffold. It does not mean a GLB has been generated yet.
46029
+
46030
+ ## Source request
46031
+
46032
+ ${input.sourcePrompt}
46033
+
46034
+ ## Screenshot inputs
46035
+
46036
+ ${markdownList(input.screenshotPaths, "No screenshot paths provided. Use capture_wave_screenshot first when visual matching matters.")}
46037
+
46038
+ ## Reference image inputs
46039
+
46040
+ ${markdownList(input.referenceImagePaths, "No reference image paths provided.")}
46041
+
46042
+ ## Workflow
46043
+
46044
+ 1. Use \`cad-sheet-prompt.md\` to derive a CAD sheet or parameter spec from screenshots/reference images.
46045
+ 2. Fill \`parameters.template.json\`.
46046
+ 3. Run \`python build_step.py parameters.template.json out\` after installing CadQuery/OCP locally.
46047
+ 4. Convert the STEP/mesh result to GLB with your approved modeling pipeline.
46048
+ 5. Upload the GLB through Wave Studio MCP asset upload tools.
46049
+ 6. Author scene code with \`models.${input.assetName}\`.
46050
+
46051
+ ## Safety
46052
+
46053
+ Keep API keys and private screenshots out of commits. Generated assets must be reviewed before upload.
46054
+ `
46055
+ },
46056
+ {
46057
+ fileName: "asset-brief.json",
46058
+ role: "brief",
46059
+ content: `${briefJson}
46060
+ `
46061
+ },
46062
+ {
46063
+ fileName: "cad-sheet-prompt.md",
46064
+ role: "prompt",
46065
+ content: `Create a CAD-ready model specification for this WaveEngine asset.
46066
+
46067
+ Asset name: ${input.assetName}
46068
+ Request: ${input.sourcePrompt}
46069
+ Target style: ${input.targetStyle ?? "match source/reference"}
46070
+ Intended use: ${input.intendedUse ?? "WaveEngine scene asset"}
46071
+ Required parts: ${input.requiredParts.length > 0 ? input.requiredParts.join(", ") : "infer minimal public-facing parts"}
46072
+
46073
+ Use the screenshots/reference images as visual evidence. Return:
46074
+
46075
+ - Orthographic front/side/top proportions.
46076
+ - Named visible parts.
46077
+ - Dimensions in millimeters.
46078
+ - Bevels, thickness, symmetry, repeating details.
46079
+ - Material/color hints only. Do not invent private engine internals.
46080
+ - A JSON parameter block compatible with parameters.template.json.
46081
+ `
46082
+ },
46083
+ {
46084
+ fileName: "parameter-spec.schema.json",
46085
+ role: "schema",
46086
+ content: `${JSON.stringify({
46087
+ $schema: "https://json-schema.org/draft/2020-12/schema",
46088
+ title: "WaveEngine 3D Modeling Parameters",
46089
+ type: "object",
46090
+ required: ["assetName", "units", "parameters", "parts"],
46091
+ additionalProperties: false,
46092
+ properties: {
46093
+ assetName: { type: "string", minLength: 1 },
46094
+ units: { type: "string", enum: ["millimeter"] },
46095
+ parameters: {
46096
+ type: "object",
46097
+ additionalProperties: { type: ["number", "string", "boolean"] }
46098
+ },
46099
+ parts: {
46100
+ type: "array",
46101
+ minItems: 1,
46102
+ items: {
46103
+ type: "object",
46104
+ required: ["name"],
46105
+ additionalProperties: false,
46106
+ properties: {
46107
+ name: { type: "string", minLength: 1 },
46108
+ materialHint: { type: "string" }
46109
+ }
46110
+ }
46111
+ }
46112
+ }
46113
+ }, null, 2)}
46114
+ `
46115
+ },
46116
+ {
46117
+ fileName: "parameters.template.json",
46118
+ role: "parameters",
46119
+ content: `${parameterTemplate}
46120
+ `
46121
+ },
46122
+ {
46123
+ fileName: "build_step.py",
46124
+ role: "python-cad-scaffold",
46125
+ content: `#!/usr/bin/env python3
46126
+ """WaveEngine local CAD scaffold.
46127
+
46128
+ This is a starting point, not a magic model generator. Replace build_model()
46129
+ with the CAD recipe derived from cad-sheet-prompt.md.
46130
+ """
46131
+
46132
+ from __future__ import annotations
46133
+
46134
+ import json
46135
+ import pathlib
46136
+ import sys
46137
+
46138
+ try:
46139
+ import cadquery as cq
46140
+ except ImportError as exc:
46141
+ raise SystemExit(
46142
+ "CadQuery is required. Install it in your local modeling environment before running this scaffold."
46143
+ ) from exc
46144
+
46145
+
46146
+ def read_params(path: pathlib.Path) -> dict:
46147
+ with path.open("r", encoding="utf-8") as handle:
46148
+ return json.load(handle)
46149
+
46150
+
46151
+ def number_param(params: dict, name: str, fallback: float) -> float:
46152
+ value = params.get("parameters", {}).get(name, fallback)
46153
+ if isinstance(value, (int, float)):
46154
+ return float(value)
46155
+ return fallback
46156
+
46157
+
46158
+ def build_model(params: dict) -> cq.Workplane:
46159
+ width = number_param(params, "overall_width", 1000)
46160
+ depth = number_param(params, "overall_depth", 600)
46161
+ height = number_param(params, "overall_height", 500)
46162
+ bevel_radius = number_param(params, "bevel_radius", 12)
46163
+ model = cq.Workplane("XY").box(width, depth, height)
46164
+ if bevel_radius > 0:
46165
+ model = model.edges().fillet(bevel_radius)
46166
+ return model
46167
+
46168
+
46169
+ def main() -> None:
46170
+ if len(sys.argv) != 3:
46171
+ raise SystemExit("usage: python build_step.py parameters.template.json out")
46172
+ params_path = pathlib.Path(sys.argv[1])
46173
+ out_dir = pathlib.Path(sys.argv[2])
46174
+ out_dir.mkdir(parents=True, exist_ok=True)
46175
+ params = read_params(params_path)
46176
+ asset_name = str(params.get("assetName") or "${input.assetName}")
46177
+ step_path = out_dir / f"{asset_name}.step"
46178
+ cq.exporters.export(build_model(params), str(step_path))
46179
+ print(f"Wrote {step_path}")
46180
+
46181
+
46182
+ if __name__ == "__main__":
46183
+ main()
46184
+ `
46185
+ },
46186
+ {
46187
+ fileName: "validation-checklist.md",
46188
+ role: "validation",
46189
+ content: `# Validation checklist
46190
+
46191
+ - Silhouette matches source screenshots from front/side/top.
46192
+ - Scale is correct for WaveEngine world use.
46193
+ - Parts are named for future objectModel/part authoring where useful.
46194
+ - Material slots are clean and simple.
46195
+ - GLB loads in Wave Studio before code authoring.
46196
+ - Uploaded asset appears as \`models.${input.assetName}\`.
46197
+ `
46198
+ }
46199
+ ];
46200
+ }
46201
+ async function createWave3dModelingJobTool(args) {
46202
+ const parsed = parseModelingJobArgs(args);
46203
+ if ("content" in parsed) return parsed;
46204
+ const jobId = `wave_model_${parsed.assetName}_${randomUUID().slice(0, 8)}`;
46205
+ const jobDir = path2.join(tmpdir(), "wave3d-agent-sdk", "modeling-jobs", jobId);
46206
+ const supportFiles = buildModelingSupportFiles(parsed);
46207
+ await mkdir(jobDir, { recursive: true });
46208
+ await Promise.all(supportFiles.map((file) => writeFile(path2.join(jobDir, file.fileName), file.content, "utf8")));
46209
+ return successToolResult({
46210
+ ok: true,
46211
+ tool: "create_wave_3d_modeling_job",
46212
+ status: "scaffolded",
46213
+ localSdk: true,
46214
+ generatedAssetReady: false,
46215
+ jobId,
46216
+ jobDir,
46217
+ assetName: parsed.assetName,
46218
+ supportFiles: supportFiles.map((file) => ({
46219
+ fileName: file.fileName,
46220
+ role: file.role,
46221
+ path: path2.join(jobDir, file.fileName)
46222
+ })),
46223
+ nextActions: [
46224
+ "Use cad-sheet-prompt.md plus screenshots/reference images to produce a CAD parameter spec.",
46225
+ "Fill parameters.template.json and run the Python scaffold only in a local modeling environment with CadQuery installed.",
46226
+ "Convert generated model output to GLB, then upload with create_wave_asset_upload/upload_wave_asset.",
46227
+ `After upload, author WaveEngine scene code with models.${parsed.assetName}.`
46228
+ ],
46229
+ caveats: [
46230
+ "This tool creates a modeling job scaffold only; it does not call a cloud model generator.",
46231
+ "Do not commit private screenshots, prompts, or generated assets without review."
46232
+ ]
46233
+ });
46234
+ }
46235
+
45913
46236
  // ../../src/lib/waveStudio/aiAssist/bridge/assetListGuidance.ts
45914
46237
  function buildWaveAssetListGuidance(input) {
45915
46238
  const hasQuery = typeof input.query === "string" && input.query.trim().length > 0;
@@ -46401,7 +46724,7 @@ function reportHotReloadUnsafeUsage(params) {
46401
46724
  })
46402
46725
  });
46403
46726
  }
46404
- function collectRuntimeHotReloadSafetyDiagnostics(path5, code, limit = HOT_RELOAD_UNSAFE_ERROR_LIMIT) {
46727
+ function collectRuntimeHotReloadSafetyDiagnostics(path6, code, limit = HOT_RELOAD_UNSAFE_ERROR_LIMIT) {
46405
46728
  const diagnostics = [];
46406
46729
  if (limit <= 0) return diagnostics;
46407
46730
  const reported = /* @__PURE__ */ new Set();
@@ -46446,34 +46769,34 @@ function collectRuntimeHotReloadSafetyDiagnostics(path5, code, limit = HOT_RELOA
46446
46769
  const receiverPath = previousChar === "." ? readPropertyReceiverPath(code, tokenStart) : "";
46447
46770
  const globalGuidance = HOT_RELOAD_UNSAFE_GLOBAL_CALLBACKS.get(token);
46448
46771
  if (globalGuidance) {
46449
- reportHotReloadUnsafeUsage({ code, path: path5, token, offset: tokenStart, guidance: globalGuidance, diagnostics, reported });
46772
+ reportHotReloadUnsafeUsage({ code, path: path6, token, offset: tokenStart, guidance: globalGuidance, diagnostics, reported });
46450
46773
  continue;
46451
46774
  }
46452
46775
  const listenerGuidance = HOT_RELOAD_UNSAFE_LISTENER_CALLBACKS.get(token);
46453
46776
  if (listenerGuidance && isCall) {
46454
- reportHotReloadUnsafeUsage({ code, path: path5, token, offset: tokenStart, guidance: listenerGuidance, diagnostics, reported });
46777
+ reportHotReloadUnsafeUsage({ code, path: path6, token, offset: tokenStart, guidance: listenerGuidance, diagnostics, reported });
46455
46778
  continue;
46456
46779
  }
46457
46780
  const promiseGuidance = HOT_RELOAD_UNSAFE_PROMISE_CALLBACKS.get(token);
46458
46781
  if (promiseGuidance && previousChar === "." && isCall) {
46459
- reportHotReloadUnsafeUsage({ code, path: path5, token, offset: tokenStart, guidance: promiseGuidance, diagnostics, reported });
46782
+ reportHotReloadUnsafeUsage({ code, path: path6, token, offset: tokenStart, guidance: promiseGuidance, diagnostics, reported });
46460
46783
  continue;
46461
46784
  }
46462
46785
  const eventBusGuidance = HOT_RELOAD_UNSAFE_EVENTBUS_CALLBACKS.get(token);
46463
46786
  if (eventBusGuidance && previousChar === "." && isCall && isWaveEventBusReceiverPath(receiverPath)) {
46464
- reportHotReloadUnsafeUsage({ code, path: path5, token, offset: tokenStart, guidance: eventBusGuidance, diagnostics, reported });
46787
+ reportHotReloadUnsafeUsage({ code, path: path6, token, offset: tokenStart, guidance: eventBusGuidance, diagnostics, reported });
46465
46788
  continue;
46466
46789
  }
46467
46790
  const sceneEventGuidance = HOT_RELOAD_UNSAFE_SCENE_EVENT_CALLBACKS.get(token);
46468
46791
  if (sceneEventGuidance && previousChar === "." && isCall && isWaveSceneEventReceiverPath(receiverPath)) {
46469
- reportHotReloadUnsafeUsage({ code, path: path5, token, offset: tokenStart, guidance: sceneEventGuidance, diagnostics, reported });
46792
+ reportHotReloadUnsafeUsage({ code, path: path6, token, offset: tokenStart, guidance: sceneEventGuidance, diagnostics, reported });
46470
46793
  continue;
46471
46794
  }
46472
46795
  const forbiddenStudioSceneMethodGuidance = HOT_RELOAD_FORBIDDEN_STUDIO_SCENE_METHODS.get(token);
46473
46796
  if (forbiddenStudioSceneMethodGuidance && previousChar === "." && isCall && isWaveSceneEventReceiverPath(receiverPath)) {
46474
46797
  reportHotReloadUnsafeUsage({
46475
46798
  code,
46476
- path: path5,
46799
+ path: path6,
46477
46800
  token,
46478
46801
  offset: tokenStart,
46479
46802
  severity: "error",
@@ -46485,12 +46808,12 @@ function collectRuntimeHotReloadSafetyDiagnostics(path5, code, limit = HOT_RELOA
46485
46808
  }
46486
46809
  const constructorGuidance = HOT_RELOAD_UNSAFE_CONSTRUCTORS.get(token);
46487
46810
  if (constructorGuidance && (previousIdentifier === "new" || previousChar === "." && isCall)) {
46488
- reportHotReloadUnsafeUsage({ code, path: path5, token, offset: tokenStart, guidance: constructorGuidance, diagnostics, reported });
46811
+ reportHotReloadUnsafeUsage({ code, path: path6, token, offset: tokenStart, guidance: constructorGuidance, diagnostics, reported });
46489
46812
  continue;
46490
46813
  }
46491
46814
  const eventHandlerGuidance = HOT_RELOAD_UNSAFE_EVENT_HANDLER_ASSIGNMENTS.get(token);
46492
46815
  if (eventHandlerGuidance && previousChar === "." && nextNonWhitespace === "=") {
46493
- reportHotReloadUnsafeUsage({ code, path: path5, token, offset: tokenStart, guidance: eventHandlerGuidance, diagnostics, reported });
46816
+ reportHotReloadUnsafeUsage({ code, path: path6, token, offset: tokenStart, guidance: eventHandlerGuidance, diagnostics, reported });
46494
46817
  }
46495
46818
  }
46496
46819
  return diagnostics;
@@ -46883,16 +47206,16 @@ function parseMutationOperations(value) {
46883
47206
  }
46884
47207
  return operations;
46885
47208
  }
46886
- function isAuthoredCodePath(path5) {
46887
- const normalized = path5.trim().toLowerCase();
47209
+ function isAuthoredCodePath(path6) {
47210
+ const normalized = path6.trim().toLowerCase();
46888
47211
  return normalized.endsWith(".ts") || normalized.endsWith(".js");
46889
47212
  }
46890
- function warnUnsafeCallbackUsage(path5, code) {
46891
- if (!isAuthoredCodePath(path5) || !code.trim()) return;
46892
- const diagnostics = collectRuntimeHotReloadSafetyDiagnostics(path5, code);
47213
+ function warnUnsafeCallbackUsage(path6, code) {
47214
+ if (!isAuthoredCodePath(path6) || !code.trim()) return;
47215
+ const diagnostics = collectRuntimeHotReloadSafetyDiagnostics(path6, code);
46893
47216
  if (diagnostics.length === 0) return;
46894
47217
  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.`,
47218
+ `[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
47219
  "Use Wave-owned callbacks, animation DSL, entity lifecycle/tick/state APIs, or Director/input APIs instead.",
46897
47220
  ...diagnostics.map((diagnostic) => diagnostic.message)
46898
47221
  ].join("\n"));
@@ -47055,8 +47378,8 @@ async function handleVfsTool(input) {
47055
47378
  return errorToolResult("session_not_found", LOCAL_SESSION_NOT_FOUND_MESSAGE);
47056
47379
  }
47057
47380
  const pathInput = typeof args?.path === "string" ? args.path : typeof args?.filePath === "string" ? args.filePath : "";
47058
- const path5 = normalizeWaveFilePath(pathInput);
47059
- if (!path5) {
47381
+ const path6 = normalizeWaveFilePath(pathInput);
47382
+ if (!path6) {
47060
47383
  return errorToolResult("invalid_arguments", "`read_wave_file` requires a non-empty string `path` (alias: `filePath`).");
47061
47384
  }
47062
47385
  const startLine = normalizeOptionalPositiveInteger(args?.startLine);
@@ -47075,7 +47398,7 @@ async function handleVfsTool(input) {
47075
47398
  try {
47076
47399
  const result = await context.commandBroker.enqueue(session.sessionId, {
47077
47400
  type: "readFile",
47078
- path: path5,
47401
+ path: path6,
47079
47402
  ...ifMatchHash ? { ifMatchHash } : {},
47080
47403
  ...typeof effectiveStartLine === "number" ? { startLine: effectiveStartLine } : {},
47081
47404
  ...typeof endLine === "number" ? { endLine } : {}
@@ -47097,7 +47420,7 @@ async function handleVfsTool(input) {
47097
47420
  ...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
47421
  });
47099
47422
  } catch (error) {
47100
- return errorToolResult("command_timeout", error instanceof Error ? error.message : "WaveEngine Agent SDK file read timed out.", { path: path5 });
47423
+ return errorToolResult("command_timeout", error instanceof Error ? error.message : "WaveEngine Agent SDK file read timed out.", { path: path6 });
47101
47424
  }
47102
47425
  }
47103
47426
  if (name === "edit_wave_file") {
@@ -47109,21 +47432,21 @@ async function handleVfsTool(input) {
47109
47432
  ]);
47110
47433
  if ("result" in contentResult) return contentResult.result;
47111
47434
  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`.");
47435
+ const path6 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47436
+ if (!path6) return errorToolResult("invalid_arguments", "`edit_wave_file` requires a non-empty string `path`.");
47114
47437
  const deleteNeedle = normalizeOptionalString(args?.deleteStatementContaining);
47115
47438
  const replacementResult = deleteNeedle ? { value: "" } : resolveReplacementAlias("text", args?.text, args?.replacement);
47116
47439
  if ("result" in replacementResult) return replacementResult.result;
47117
47440
  if (typeof replacementResult.value !== "string") {
47118
47441
  return errorToolResult("invalid_arguments", "Block edit requires `text`/`replacement`, or `deleteStatementContaining` for deletion.");
47119
47442
  }
47120
- warnUnsafeCallbackUsage(path5, replacementResult.value);
47443
+ warnUnsafeCallbackUsage(path6, replacementResult.value);
47121
47444
  const sessionResult = resolveWritableSession(args, context);
47122
47445
  if ("result" in sessionResult) return sessionResult.result;
47123
47446
  const resolvedHash = await resolveEditBaseHash({
47124
47447
  context,
47125
47448
  session: sessionResult.session,
47126
- path: path5,
47449
+ path: path6,
47127
47450
  baseHash: args?.baseHash
47128
47451
  });
47129
47452
  if ("result" in resolvedHash) return resolvedHash.result;
@@ -47145,7 +47468,7 @@ async function handleVfsTool(input) {
47145
47468
  session: sessionResult.session,
47146
47469
  operations: [{
47147
47470
  type: "lineEdits",
47148
- path: path5,
47471
+ path: path6,
47149
47472
  baseHash: resolvedHash.baseHash,
47150
47473
  edits: [{
47151
47474
  startLine: range.startLine,
@@ -47159,7 +47482,7 @@ async function handleVfsTool(input) {
47159
47482
  return withEditBaseHashMetadata(result, resolvedHash, {
47160
47483
  editMode: deleteNeedle ? "deleteStatementContaining" : "replaceBlockContaining",
47161
47484
  editTarget: {
47162
- path: path5,
47485
+ path: path6,
47163
47486
  startLine: range.startLine,
47164
47487
  endLine: range.endLine,
47165
47488
  targetReason: range.reason,
@@ -47172,28 +47495,28 @@ async function handleVfsTool(input) {
47172
47495
  const editArray = parseEditWaveFileEditsArray(args?.edits);
47173
47496
  if (editArray) {
47174
47497
  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`.");
47498
+ const path6 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47499
+ if (!path6) return errorToolResult("invalid_arguments", "`edit_wave_file` requires a non-empty string `path`.");
47177
47500
  for (const edit of editArray.edits) {
47178
- warnUnsafeCallbackUsage(path5, edit.text);
47501
+ warnUnsafeCallbackUsage(path6, edit.text);
47179
47502
  }
47180
47503
  const sessionResult = resolveWritableSession(args, context);
47181
47504
  if ("result" in sessionResult) return sessionResult.result;
47182
47505
  const resolvedHash = await resolveEditBaseHash({
47183
47506
  context,
47184
47507
  session: sessionResult.session,
47185
- path: path5,
47508
+ path: path6,
47186
47509
  baseHash: args?.baseHash
47187
47510
  });
47188
47511
  if ("result" in resolvedHash) return resolvedHash.result;
47189
47512
  const operations = editArray.type === "lineEdits" ? [{
47190
47513
  type: "lineEdits",
47191
- path: path5,
47514
+ path: path6,
47192
47515
  baseHash: resolvedHash.baseHash,
47193
47516
  edits: editArray.edits
47194
47517
  }] : [{
47195
47518
  type: "textEdits",
47196
- path: path5,
47519
+ path: path6,
47197
47520
  baseHash: resolvedHash.baseHash,
47198
47521
  edits: editArray.edits
47199
47522
  }];
@@ -47207,17 +47530,17 @@ async function handleVfsTool(input) {
47207
47530
  return withEditBaseHashMetadata(result, resolvedHash, {
47208
47531
  editMode: editArray.type === "lineEdits" ? "multiLineEdits" : "multiTextEdits",
47209
47532
  editTarget: {
47210
- path: path5,
47533
+ path: path6,
47211
47534
  editCount: editArray.edits.length
47212
47535
  }
47213
47536
  });
47214
47537
  }
47215
47538
  const inferredMode = normalizeEditWaveFileMode(args?.mode) ?? (requireNonEmptyString(args?.oldText) ? "replaceText" : typeof args?.startLine !== "undefined" || typeof args?.endLine !== "undefined" ? "replaceLines" : typeof contentResult.value === "string" ? "replaceWholeFile" : "");
47216
47539
  if (inferredMode === "replaceLines") {
47217
- const path5 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47540
+ const path6 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47218
47541
  const startLine = normalizeOptionalPositiveInteger(args?.startLine);
47219
47542
  const endLine = normalizeOptionalPositiveInteger(args?.endLine);
47220
- if (!path5) return errorToolResult("invalid_arguments", "`edit_wave_file` requires a non-empty string `path`.");
47543
+ if (!path6) return errorToolResult("invalid_arguments", "`edit_wave_file` requires a non-empty string `path`.");
47221
47544
  if (startLine === null || typeof startLine === "undefined") {
47222
47545
  return errorToolResult("invalid_arguments", "`startLine` must be a positive integer.");
47223
47546
  }
@@ -47232,13 +47555,13 @@ async function handleVfsTool(input) {
47232
47555
  if (typeof replacementResult.value !== "string") {
47233
47556
  return errorToolResult("invalid_arguments", "`text` or `replacement` must be a string. Use an empty string to delete the line range.");
47234
47557
  }
47235
- warnUnsafeCallbackUsage(path5, replacementResult.value);
47558
+ warnUnsafeCallbackUsage(path6, replacementResult.value);
47236
47559
  const sessionResult = resolveWritableSession(args, context);
47237
47560
  if ("result" in sessionResult) return sessionResult.result;
47238
47561
  const resolvedHash = await resolveEditBaseHash({
47239
47562
  context,
47240
47563
  session: sessionResult.session,
47241
- path: path5,
47564
+ path: path6,
47242
47565
  baseHash: args?.baseHash
47243
47566
  });
47244
47567
  if ("result" in resolvedHash) return resolvedHash.result;
@@ -47262,7 +47585,7 @@ async function handleVfsTool(input) {
47262
47585
  session: sessionResult.session,
47263
47586
  operations: [{
47264
47587
  type: "lineEdits",
47265
- path: path5,
47588
+ path: path6,
47266
47589
  baseHash: resolvedHash.baseHash,
47267
47590
  edits: [{
47268
47591
  startLine,
@@ -47276,7 +47599,7 @@ async function handleVfsTool(input) {
47276
47599
  return withEditBaseHashMetadata(result, resolvedHash, {
47277
47600
  editMode: "replaceLines",
47278
47601
  editTarget: {
47279
- path: path5,
47602
+ path: path6,
47280
47603
  startLine,
47281
47604
  endLine: finalEndLine,
47282
47605
  requestedEndLine,
@@ -47289,18 +47612,18 @@ async function handleVfsTool(input) {
47289
47612
  });
47290
47613
  }
47291
47614
  if (inferredMode === "replaceText") {
47292
- const path5 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47615
+ const path6 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47293
47616
  const oldText = requireNonEmptyString(args?.oldText);
47294
- if (!path5) return errorToolResult("invalid_arguments", "`edit_wave_file` requires a non-empty string `path`.");
47617
+ if (!path6) return errorToolResult("invalid_arguments", "`edit_wave_file` requires a non-empty string `path`.");
47295
47618
  if (!oldText) {
47296
47619
  if (typeof contentResult.value === "string") {
47297
- warnUnsafeCallbackUsage(path5, contentResult.value);
47620
+ warnUnsafeCallbackUsage(path6, contentResult.value);
47298
47621
  const sessionResult2 = resolveWritableSession(args, context);
47299
47622
  if ("result" in sessionResult2) return sessionResult2.result;
47300
47623
  const resolvedHash2 = await resolveEditBaseHash({
47301
47624
  context,
47302
47625
  session: sessionResult2.session,
47303
- path: path5,
47626
+ path: path6,
47304
47627
  baseHash: args?.baseHash
47305
47628
  });
47306
47629
  if ("result" in resolvedHash2) return resolvedHash2.result;
@@ -47309,7 +47632,7 @@ async function handleVfsTool(input) {
47309
47632
  session: sessionResult2.session,
47310
47633
  operations: [{
47311
47634
  type: "writeFile",
47312
- path: path5,
47635
+ path: path6,
47313
47636
  baseHash: resolvedHash2.baseHash,
47314
47637
  content: contentResult.value
47315
47638
  }],
@@ -47325,7 +47648,7 @@ async function handleVfsTool(input) {
47325
47648
  if (typeof replacementResult.value !== "string") {
47326
47649
  return errorToolResult("invalid_arguments", "`newText` or `replacement` must be a string. Use an empty string to delete the matched text.");
47327
47650
  }
47328
- warnUnsafeCallbackUsage(path5, replacementResult.value);
47651
+ warnUnsafeCallbackUsage(path6, replacementResult.value);
47329
47652
  const sessionResult = resolveWritableSession(args, context);
47330
47653
  if ("result" in sessionResult) return sessionResult.result;
47331
47654
  const hasOccurrenceIndex = typeof args?.occurrenceIndex !== "undefined";
@@ -47338,7 +47661,7 @@ async function handleVfsTool(input) {
47338
47661
  const resolvedHash = await resolveEditBaseHash({
47339
47662
  context,
47340
47663
  session: sessionResult.session,
47341
- path: path5,
47664
+ path: path6,
47342
47665
  baseHash: args?.baseHash
47343
47666
  });
47344
47667
  if ("result" in resolvedHash) return resolvedHash.result;
@@ -47347,7 +47670,7 @@ async function handleVfsTool(input) {
47347
47670
  session: sessionResult.session,
47348
47671
  operations: [{
47349
47672
  type: "searchReplace",
47350
- path: path5,
47673
+ path: path6,
47351
47674
  baseHash: resolvedHash.baseHash,
47352
47675
  oldText,
47353
47676
  newText: replacementResult.value,
@@ -47360,15 +47683,15 @@ async function handleVfsTool(input) {
47360
47683
  return withEditBaseHashMetadata(result, resolvedHash);
47361
47684
  }
47362
47685
  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);
47686
+ const path6 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47687
+ if (!path6) return errorToolResult("invalid_arguments", "`edit_wave_file` requires a non-empty string `path`.");
47688
+ warnUnsafeCallbackUsage(path6, contentResult.value);
47366
47689
  const sessionResult = resolveWritableSession(args, context);
47367
47690
  if ("result" in sessionResult) return sessionResult.result;
47368
47691
  const resolvedHash = await resolveEditBaseHash({
47369
47692
  context,
47370
47693
  session: sessionResult.session,
47371
- path: path5,
47694
+ path: path6,
47372
47695
  baseHash: args?.baseHash
47373
47696
  });
47374
47697
  if ("result" in resolvedHash) return resolvedHash.result;
@@ -47377,7 +47700,7 @@ async function handleVfsTool(input) {
47377
47700
  session: sessionResult.session,
47378
47701
  operations: [{
47379
47702
  type: "writeFile",
47380
- path: path5,
47703
+ path: path6,
47381
47704
  baseHash: resolvedHash.baseHash,
47382
47705
  content: contentResult.value
47383
47706
  }],
@@ -47389,12 +47712,12 @@ async function handleVfsTool(input) {
47389
47712
  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
47713
  }
47391
47714
  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`.");
47715
+ const path6 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47716
+ if (!path6) return errorToolResult("invalid_arguments", "`create_wave_file` requires a non-empty string `path`.");
47394
47717
  if (typeof args?.content !== "string") {
47395
47718
  return errorToolResult("invalid_arguments", "`content` must be a string.");
47396
47719
  }
47397
- warnUnsafeCallbackUsage(path5, args.content);
47720
+ warnUnsafeCallbackUsage(path6, args.content);
47398
47721
  const overwrite = args?.overwrite === true || args?.forceOverwrite === true || args?.replaceExisting === true;
47399
47722
  const sessionResult = resolveWritableSession(args, context);
47400
47723
  if ("result" in sessionResult) return sessionResult.result;
@@ -47403,7 +47726,7 @@ async function handleVfsTool(input) {
47403
47726
  session: sessionResult.session,
47404
47727
  operations: [{
47405
47728
  type: "createFile",
47406
- path: path5,
47729
+ path: path6,
47407
47730
  content: args.content,
47408
47731
  runnable: typeof args?.runnable === "boolean" ? args.runnable : true,
47409
47732
  ...overwrite ? { overwrite: true } : {}
@@ -47434,14 +47757,14 @@ async function handleVfsTool(input) {
47434
47757
  if (name === "delete_wave_file") {
47435
47758
  const sessionResult = resolveWritableSession(args, context);
47436
47759
  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`.");
47760
+ const path6 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
47761
+ if (!path6) return errorToolResult("invalid_arguments", "`delete_wave_file` requires a non-empty string `path`.");
47439
47762
  return await applyMutationOperations({
47440
47763
  context,
47441
47764
  session: sessionResult.session,
47442
47765
  operations: [{
47443
47766
  type: "deleteFile",
47444
- path: path5
47767
+ path: path6
47445
47768
  }],
47446
47769
  ...runtimeVerificationArgs(args)
47447
47770
  });
@@ -49497,7 +49820,11 @@ var LOCAL_TOOL_DEFINITION_OVERRIDE_NAMES = /* @__PURE__ */ new Set([
49497
49820
  var LOCAL_EXTRA_TOOL_DEFINITION_NAMES = /* @__PURE__ */ new Set([
49498
49821
  "self_check_wave_mcp",
49499
49822
  "list_wave_sessions",
49500
- "get_active_wave_session"
49823
+ "get_active_wave_session",
49824
+ "create_wave_3d_modeling_job"
49825
+ ]);
49826
+ var LOCAL_SDK_MODELING_TOOL_NAMES = /* @__PURE__ */ new Set([
49827
+ "create_wave_3d_modeling_job"
49501
49828
  ]);
49502
49829
  var LOCAL_OPTIONAL_SESSION_TOOL_NAMES = /* @__PURE__ */ new Set([
49503
49830
  "get_wave_session",
@@ -49566,6 +49893,7 @@ var LOCAL_SDK_CONTROL_TOOL_NAMES = /* @__PURE__ */ new Set([
49566
49893
  var LOCAL_SDK_IMPLEMENTED_CANONICAL_TOOL_NAMES = /* @__PURE__ */ new Set([
49567
49894
  ...LOCAL_SDK_CONTROL_TOOL_NAMES,
49568
49895
  ...LOCAL_SDK_LOOKUP_TOOL_NAMES,
49896
+ ...LOCAL_SDK_MODELING_TOOL_NAMES,
49569
49897
  ...[...LOCAL_OPTIONAL_SESSION_TOOL_NAMES].map((name) => canonicalizeWaveMcpToolName(name))
49570
49898
  ]);
49571
49899
  function isLocalMcpToolImplemented(name) {
@@ -49688,6 +50016,60 @@ function getReadToolDefinitions() {
49688
50016
  additionalProperties: false
49689
50017
  }
49690
50018
  },
50019
+ {
50020
+ name: "create_wave_3d_modeling_job",
50021
+ 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. Aliases: generate_wave_3d_model, create_wave_3d_model, create_wave_3d_model_from_screenshots.",
50022
+ inputSchema: {
50023
+ type: "object",
50024
+ properties: {
50025
+ sourcePrompt: {
50026
+ type: "string",
50027
+ description: 'Required modeling goal, e.g. "make a low-poly sea lion matching the screenshot". Aliases accepted by handler: prompt, description.'
50028
+ },
50029
+ prompt: {
50030
+ type: "string",
50031
+ description: "Alias for sourcePrompt."
50032
+ },
50033
+ description: {
50034
+ type: "string",
50035
+ description: "Alias for sourcePrompt."
50036
+ },
50037
+ assetName: {
50038
+ type: "string",
50039
+ description: "Optional public Wave asset/model name stem. Handler sanitizes to lowercase snake_case."
50040
+ },
50041
+ screenshotPaths: {
50042
+ type: "array",
50043
+ items: { type: "string" },
50044
+ description: "Optional local screenshot paths, usually returned from capture_wave_screenshot or supplied by user."
50045
+ },
50046
+ referenceImagePaths: {
50047
+ type: "array",
50048
+ items: { type: "string" },
50049
+ description: "Optional local reference image paths."
50050
+ },
50051
+ targetStyle: {
50052
+ type: "string",
50053
+ description: "Optional visual style constraint."
50054
+ },
50055
+ intendedUse: {
50056
+ type: "string",
50057
+ description: "Optional Wave scene usage target such as prop, actor, background, UI model, collision mesh."
50058
+ },
50059
+ requiredParts: {
50060
+ type: "array",
50061
+ items: { type: "string" },
50062
+ description: "Optional named visible parts the generated model should preserve."
50063
+ },
50064
+ knownDimensionsMm: {
50065
+ type: "object",
50066
+ description: 'Optional JSON-safe dimension hints in millimeters, e.g. {"overall_width": 1200, "overall_height": 500}.',
50067
+ additionalProperties: true
50068
+ }
50069
+ },
50070
+ additionalProperties: false
50071
+ }
50072
+ },
49691
50073
  {
49692
50074
  name: "get_wave_session",
49693
50075
  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 +50174,7 @@ function getLocalToolImplementationSummary() {
49792
50174
  implementedCanonicalToolCount: implementedCanonicalToolNames.length,
49793
50175
  localExtraToolNames: [...LOCAL_EXTRA_TOOL_DEFINITION_NAMES].sort(),
49794
50176
  localLookupToolNames: [...LOCAL_SDK_LOOKUP_TOOL_NAMES].sort(),
50177
+ localModelingToolNames: [...LOCAL_SDK_MODELING_TOOL_NAMES].sort(),
49795
50178
  omittedCanonicalToolNames: canonicalToolNames.filter((name) => !isLocalMcpToolImplemented(name))
49796
50179
  };
49797
50180
  }
@@ -49815,7 +50198,8 @@ function localSdkLookupStatus(waveEngineSdkCache) {
49815
50198
  corpusCacheReady,
49816
50199
  apiAndSkillQueryAvailable: corpusCacheReady,
49817
50200
  cacheMissAffectsLiveStudioTransport: false,
49818
- lookupToolNames: [...LOCAL_SDK_LOOKUP_TOOL_NAMES].sort()
50201
+ lookupToolNames: [...LOCAL_SDK_LOOKUP_TOOL_NAMES].sort(),
50202
+ localModelingToolNames: [...LOCAL_SDK_MODELING_TOOL_NAMES].sort()
49819
50203
  };
49820
50204
  }
49821
50205
  function jsonRpcToolIdempotencyKey(toolName, requestId) {
@@ -50280,10 +50664,10 @@ function screenshotBasePayload(payload, suggestedFilename) {
50280
50664
  };
50281
50665
  }
50282
50666
  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"));
50667
+ const directory = path3.join(tmpdir2(), "wave3d-agent-sdk", "screenshots");
50668
+ await mkdir2(directory, { recursive: true });
50669
+ const localPath = path3.join(directory, suggestedFilename);
50670
+ await writeFile2(localPath, Buffer.from(payload.dataBase64, "base64"));
50287
50671
  return localPath;
50288
50672
  }
50289
50673
  function normalizeRuntimeMarkerSource(value) {
@@ -50476,7 +50860,7 @@ async function callReadTool(name, args, context) {
50476
50860
  if (name === "start_wave_task") {
50477
50861
  const routeResult = startWaveMcpTaskRoute({
50478
50862
  args,
50479
- taskRouteId: `wgtask_${randomUUID()}`,
50863
+ taskRouteId: `wgtask_${randomUUID2()}`,
50480
50864
  now: nowIso(),
50481
50865
  seedEvidence: mergeWaveMcpTaskEvidence(
50482
50866
  getLocalPreTaskEvidence(context) ?? createWaveMcpEmptyTaskEvidence(),
@@ -50665,7 +51049,8 @@ async function callReadTool(name, args, context) {
50665
51049
  "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
51050
  "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
51051
  "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"
51052
+ "wave.authoring: list_wave_skill_families, query_wave_skills, get_wave_skill, query_wave_api",
51053
+ "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; upload generated GLB later with asset tools"
50669
51054
  ],
50670
51055
  rules: [
50671
51056
  "studio.* labels are categories, not callable tools.",
@@ -50686,6 +51071,9 @@ async function callReadTool(name, args, context) {
50686
51071
  args
50687
51072
  });
50688
51073
  }
51074
+ if (name === "create_wave_3d_modeling_job") {
51075
+ return await createWave3dModelingJobTool(args);
51076
+ }
50689
51077
  if (name === "get_wave_command_result") {
50690
51078
  const requestId = requireNonEmptyString(args?.requestId);
50691
51079
  if (!requestId) {
@@ -50938,15 +51326,15 @@ async function callReadTool(name, args, context) {
50938
51326
  if (name === "rename_wave_asset") {
50939
51327
  const sessionResult = resolveWritableSession(args, context);
50940
51328
  if ("result" in sessionResult) return sessionResult.result;
50941
- const path5 = requireNonEmptyString(args?.path);
51329
+ const path6 = requireNonEmptyString(args?.path);
50942
51330
  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`.");
51331
+ if (!path6 || !nextName) return errorToolResult("invalid_arguments", "`rename_wave_asset` requires non-empty `path` and `name`.");
50944
51332
  return await enqueueBrowserCommand({
50945
51333
  context,
50946
51334
  session: sessionResult.session,
50947
51335
  command: {
50948
51336
  type: "renameAsset",
50949
- path: path5,
51337
+ path: path6,
50950
51338
  name: nextName
50951
51339
  }
50952
51340
  });
@@ -50954,14 +51342,14 @@ async function callReadTool(name, args, context) {
50954
51342
  if (name === "delete_wave_asset") {
50955
51343
  const sessionResult = resolveWritableSession(args, context);
50956
51344
  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`.");
51345
+ const path6 = requireNonEmptyString(args?.path);
51346
+ if (!path6) return errorToolResult("invalid_arguments", "`delete_wave_asset` requires a non-empty `path`.");
50959
51347
  return await enqueueBrowserCommand({
50960
51348
  context,
50961
51349
  session: sessionResult.session,
50962
51350
  command: {
50963
51351
  type: "deleteAsset",
50964
- path: path5
51352
+ path: path6
50965
51353
  }
50966
51354
  });
50967
51355
  }
@@ -51415,9 +51803,9 @@ async function handleWaveGenieMcpRequest(request, context) {
51415
51803
  }
51416
51804
 
51417
51805
  // ../../scripts/wavegenie-sdk/sessionRegistry.ts
51418
- import { createHash as createHash4, randomUUID as randomUUID2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
51806
+ import { createHash as createHash4, randomUUID as randomUUID3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
51419
51807
  function createSessionId() {
51420
- return `wgb_${randomUUID2()}`;
51808
+ return `wgb_${randomUUID3()}`;
51421
51809
  }
51422
51810
  function nowIso2() {
51423
51811
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -51557,18 +51945,18 @@ var WaveGenieBridgeSessionRegistry = class {
51557
51945
  };
51558
51946
 
51559
51947
  // ../../scripts/wavegenie-sdk/assetStaging.ts
51560
- import { createHash as createHash5, randomBytes as randomBytes2, randomUUID as randomUUID3 } from "node:crypto";
51948
+ import { createHash as createHash5, randomBytes as randomBytes2, randomUUID as randomUUID4 } from "node:crypto";
51561
51949
  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";
51950
+ import { mkdir as mkdir3, rm, writeFile as writeFile3 } from "node:fs/promises";
51951
+ import { tmpdir as tmpdir3 } from "node:os";
51952
+ import path4 from "node:path";
51565
51953
  var MAX_LOCAL_ASSET_UPLOAD_BYTES = 300 * 1024 * 1024;
51566
51954
  var MAX_LOCAL_ASSET_UPLOAD_CHUNKS = 256;
51567
- var STAGING_ROOT = path3.join(tmpdir2(), "wave3d-agent-sdk-asset-staging");
51955
+ var STAGING_ROOT = path4.join(tmpdir3(), "wave3d-agent-sdk-asset-staging");
51568
51956
  var LOCAL_DIRECT_UPLOAD_TTL_MS = 60 * 60 * 1e3;
51569
51957
  var DEFAULT_LOCAL_ASSET_UPLOAD_TTL_MS = 24 * 60 * 60 * 1e3;
51570
51958
  function createUploadId() {
51571
- return `wgbupload_${randomUUID3()}`;
51959
+ return `wgbupload_${randomUUID4()}`;
51572
51960
  }
51573
51961
  function createSecret() {
51574
51962
  return randomBytes2(24).toString("base64url");
@@ -51628,8 +52016,8 @@ var LocalAssetUploadStagingStore = class {
51628
52016
  this.uploads = /* @__PURE__ */ new Map();
51629
52017
  }
51630
52018
  async ensureUploadDir(uploadId) {
51631
- const uploadDir = path3.join(STAGING_ROOT, hashPathPart(uploadId));
51632
- await mkdir2(uploadDir, { recursive: true });
52019
+ const uploadDir = path4.join(STAGING_ROOT, hashPathPart(uploadId));
52020
+ await mkdir3(uploadDir, { recursive: true });
51633
52021
  return uploadDir;
51634
52022
  }
51635
52023
  createPartUrl(uploadId, chunkIndex, readToken) {
@@ -51668,8 +52056,8 @@ var LocalAssetUploadStagingStore = class {
51668
52056
  const currentBytes = [...upload.parts.values()].filter((part2) => part2.chunkIndex !== chunkIndex).reduce((total, part2) => total + part2.sizeBytes, 0);
51669
52057
  assertUploadSize(currentBytes + bytes.byteLength);
51670
52058
  const uploadDir = await this.ensureUploadDir(upload.uploadId);
51671
- const filePath = path3.join(uploadDir, `part-${chunkIndex}.bin`);
51672
- await writeFile2(filePath, bytes);
52059
+ const filePath = path4.join(uploadDir, `part-${chunkIndex}.bin`);
52060
+ await writeFile3(filePath, bytes);
51673
52061
  const part = {
51674
52062
  chunkIndex,
51675
52063
  sizeBytes: bytes.byteLength,
@@ -51682,7 +52070,7 @@ var LocalAssetUploadStagingStore = class {
51682
52070
  async putStreamPart(input) {
51683
52071
  const currentBytes = [...input.upload.parts.values()].filter((part2) => part2.chunkIndex !== input.chunkIndex).reduce((total, part2) => total + part2.sizeBytes, 0);
51684
52072
  const uploadDir = await this.ensureUploadDir(input.upload.uploadId);
51685
- const filePath = path3.join(uploadDir, `part-${input.chunkIndex}.bin`);
52073
+ const filePath = path4.join(uploadDir, `part-${input.chunkIndex}.bin`);
51686
52074
  const output = createWriteStream(filePath, { flags: "w" });
51687
52075
  let writtenBytes = 0;
51688
52076
  try {
@@ -51836,7 +52224,7 @@ var LocalAssetUploadStagingStore = class {
51836
52224
  const upload = this.uploads.get(uploadId);
51837
52225
  if (!upload) return 0;
51838
52226
  this.uploads.delete(uploadId);
51839
- const uploadDir = path3.join(STAGING_ROOT, hashPathPart(uploadId));
52227
+ const uploadDir = path4.join(STAGING_ROOT, hashPathPart(uploadId));
51840
52228
  await rm(uploadDir, { recursive: true, force: true });
51841
52229
  return upload.parts.size;
51842
52230
  }
@@ -51861,7 +52249,7 @@ var MAX_DIRECT_UPLOAD_BODY_BYTES = 300 * 1024 * 1024;
51861
52249
  var LOCAL_ORIGIN_RE = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/i;
51862
52250
  var TRUSTED_WAVE_STUDIO_ORIGIN_RE = /^https:\/\/(www\.)?wave3d\.ai$/i;
51863
52251
  function createBridgeId() {
51864
- return `wgbridge_${randomUUID4()}`;
52252
+ return `wgbridge_${randomUUID5()}`;
51865
52253
  }
51866
52254
  function isAllowedStudioOrigin(origin) {
51867
52255
  if (!origin) return true;
@@ -52364,10 +52752,10 @@ function readBooleanFlag(options, name) {
52364
52752
  return options.flags.get(name) === true;
52365
52753
  }
52366
52754
  function getDefaultCacheRoot() {
52367
- return path4.join(homedir2(), ".wave3d", "agent-cache");
52755
+ return path5.join(homedir2(), ".wave3d", "agent-cache");
52368
52756
  }
52369
52757
  function getCacheRoot(options) {
52370
- return path4.resolve(readStringFlag(options, "cache-dir") ?? process.env[CACHE_DIR_ENV] ?? getDefaultCacheRoot());
52758
+ return path5.resolve(readStringFlag(options, "cache-dir") ?? process.env[CACHE_DIR_ENV] ?? getDefaultCacheRoot());
52371
52759
  }
52372
52760
  function getDefaultMcpUrl(options) {
52373
52761
  const port = readNumberFlag(options, "port") ?? DEFAULT_PORT2;
@@ -52386,29 +52774,29 @@ function assertSafeListenHost(host, allowNetwork) {
52386
52774
  throw new Error(`Refusing to listen on non-loopback host ${host}. Use --allow-network only on a trusted network.`);
52387
52775
  }
52388
52776
  function assertSafeCacheRoot(cacheRoot) {
52389
- const resolved = path4.resolve(cacheRoot);
52390
- const root = path4.parse(resolved).root;
52777
+ const resolved = path5.resolve(cacheRoot);
52778
+ const root = path5.parse(resolved).root;
52391
52779
  const dangerous = /* @__PURE__ */ new Set([
52392
52780
  root,
52393
- path4.resolve(homedir2()),
52394
- path4.resolve(process.cwd()),
52395
- path4.resolve(path4.dirname(homedir2()))
52781
+ path5.resolve(homedir2()),
52782
+ path5.resolve(process.cwd()),
52783
+ path5.resolve(path5.dirname(homedir2()))
52396
52784
  ]);
52397
52785
  if (dangerous.has(resolved)) {
52398
52786
  throw new Error(`Refusing to use dangerous cache directory: ${resolved}`);
52399
52787
  }
52400
- const depth = path4.relative(root, resolved).split(path4.sep).filter(Boolean).length;
52788
+ const depth = path5.relative(root, resolved).split(path5.sep).filter(Boolean).length;
52401
52789
  if (depth < 2) {
52402
52790
  throw new Error(`Refusing to use shallow cache directory: ${resolved}`);
52403
52791
  }
52404
52792
  }
52405
52793
  function isDefaultCacheRoot(cacheRoot) {
52406
- return path4.resolve(cacheRoot) === path4.resolve(getDefaultCacheRoot());
52794
+ return path5.resolve(cacheRoot) === path5.resolve(getDefaultCacheRoot());
52407
52795
  }
52408
52796
  async function markCacheRoot(cacheRoot) {
52409
52797
  assertSafeCacheRoot(cacheRoot);
52410
- await mkdir3(cacheRoot, { recursive: true });
52411
- await writeFile3(path4.join(cacheRoot, CACHE_MARKER_FILE), "wave3d-agent-cache\n");
52798
+ await mkdir4(cacheRoot, { recursive: true });
52799
+ await writeFile4(path5.join(cacheRoot, CACHE_MARKER_FILE), "wave3d-agent-cache\n");
52412
52800
  }
52413
52801
  function isSafePathSegment(value) {
52414
52802
  return /^[A-Za-z0-9._-]+$/.test(value);
@@ -52737,11 +53125,11 @@ function assertCorpusManifestMatches(actual, expected) {
52737
53125
  }
52738
53126
  }
52739
53127
  function getBundledSdkDirectory() {
52740
- return path4.join(path4.dirname(fileURLToPath(import.meta.url)), BUNDLED_SDK_DIR_NAME);
53128
+ return path5.join(path5.dirname(fileURLToPath(import.meta.url)), BUNDLED_SDK_DIR_NAME);
52741
53129
  }
52742
53130
  async function readBundledSdkManifest() {
52743
53131
  try {
52744
- const parsed = JSON.parse(await readFile2(path4.join(getBundledSdkDirectory(), "manifest.json"), "utf8"));
53132
+ const parsed = JSON.parse(await readFile2(path5.join(getBundledSdkDirectory(), "manifest.json"), "utf8"));
52745
53133
  return isCorpusManifest(parsed) ? parsed : null;
52746
53134
  } catch {
52747
53135
  return null;
@@ -52749,7 +53137,7 @@ async function readBundledSdkManifest() {
52749
53137
  }
52750
53138
  async function readBundledSdkZip(manifest) {
52751
53139
  const bundleFileName = requireSafePathSegment(manifest.bundleFileName, "Bundled SDK zip file name");
52752
- const bytes = await readFile2(path4.join(getBundledSdkDirectory(), bundleFileName));
53140
+ const bytes = await readFile2(path5.join(getBundledSdkDirectory(), bundleFileName));
52753
53141
  if (bytes.length > MAX_CORPUS_ZIP_BYTES2) {
52754
53142
  throw new Error(`Bundled SDK zip is too large: ${bytes.length} bytes.`);
52755
53143
  }
@@ -52760,11 +53148,11 @@ async function writeCorpusCacheFromZip(input) {
52760
53148
  await markCacheRoot(cacheRoot);
52761
53149
  const cacheKey = requireSafePathSegment(input.manifest.cacheKey, "Corpus cacheKey");
52762
53150
  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);
53151
+ const versionDir = path5.join(cacheRoot, cacheKey);
53152
+ const corpusDir = path5.join(versionDir, "corpus");
53153
+ await mkdir4(versionDir, { recursive: true });
53154
+ await writeFile4(path5.join(versionDir, "corpus-handshake.json"), JSON.stringify(input.handshake, null, 2));
53155
+ await writeFile4(path5.join(versionDir, bundleFileName), input.zipBytes);
52768
53156
  await extractZipToDirectory(input.zipBytes, corpusDir, input.manifest);
52769
53157
  await buildSearchIndex(corpusDir);
52770
53158
  const current = {
@@ -52776,7 +53164,7 @@ async function writeCorpusCacheFromZip(input) {
52776
53164
  sourceVersions: input.manifest.sourceVersions,
52777
53165
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
52778
53166
  };
52779
- await writeFile3(path4.join(cacheRoot, "current.json"), JSON.stringify(current, null, 2));
53167
+ await writeFile4(path5.join(cacheRoot, "current.json"), JSON.stringify(current, null, 2));
52780
53168
  return current;
52781
53169
  }
52782
53170
  async function ensureBundledSdkCache(options, input) {
@@ -52915,16 +53303,16 @@ async function commandDoctor(options) {
52915
53303
  await commandCacheStatus(options);
52916
53304
  }
52917
53305
  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)) {
53306
+ const normalized = path5.posix.normalize(entryName.replace(/\\/g, "/"));
53307
+ if (normalized === "." || normalized === ".." || normalized.startsWith("../") || normalized.includes("\0") || path5.posix.isAbsolute(normalized)) {
52920
53308
  throw new Error(`Unsafe corpus zip entry: ${entryName}`);
52921
53309
  }
52922
53310
  return normalized;
52923
53311
  }
52924
53312
  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)) {
53313
+ const outputPath = path5.resolve(targetDir, ...normalizedEntryName.split("/"));
53314
+ const relative = path5.relative(targetDir, outputPath);
53315
+ if (relative.startsWith("..") || path5.isAbsolute(relative)) {
52928
53316
  throw new Error(`Unsafe corpus zip output path: ${normalizedEntryName}`);
52929
53317
  }
52930
53318
  return outputPath;
@@ -52938,8 +53326,8 @@ function isCorpusCurrentRecord(value) {
52938
53326
  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
53327
  }
52940
53328
  function isPathInsideDirectory2(parentDir, childPath) {
52941
- const relative = path4.relative(path4.resolve(parentDir), path4.resolve(childPath));
52942
- return relative === "" || !!relative && !relative.startsWith("..") && !path4.isAbsolute(relative);
53329
+ const relative = path5.relative(path5.resolve(parentDir), path5.resolve(childPath));
53330
+ return relative === "" || !!relative && !relative.startsWith("..") && !path5.isAbsolute(relative);
52943
53331
  }
52944
53332
  async function readCorpusManifestFromDirectory(corpusDir) {
52945
53333
  try {
@@ -52984,7 +53372,7 @@ async function extractZipToDirectory(zipBytes, targetDir, manifest) {
52984
53372
  const allowedPaths = /* @__PURE__ */ new Set(["manifest.json", ...expectedFiles.keys()]);
52985
53373
  const extractedFilePaths = /* @__PURE__ */ new Set();
52986
53374
  await rm2(targetDir, { recursive: true, force: true });
52987
- await mkdir3(targetDir, { recursive: true });
53375
+ await mkdir4(targetDir, { recursive: true });
52988
53376
  const entries = Object.values(zip.files);
52989
53377
  if (entries.length > MAX_CORPUS_FILE_COUNT) {
52990
53378
  throw new Error(`Corpus zip has too many entries: ${entries.length}.`);
@@ -53001,7 +53389,7 @@ async function extractZipToDirectory(zipBytes, targetDir, manifest) {
53001
53389
  throw new Error(`Corpus extracted size is too large: ${extractedBytes + expectedSize} bytes.`);
53002
53390
  }
53003
53391
  const outputPath = resolveZipOutputPath(targetDir, normalized);
53004
- await mkdir3(path4.dirname(outputPath), { recursive: true });
53392
+ await mkdir4(path5.dirname(outputPath), { recursive: true });
53005
53393
  const content = await entry.async("nodebuffer");
53006
53394
  extractedBytes += content.length;
53007
53395
  if (extractedBytes > MAX_CORPUS_EXTRACTED_BYTES2) {
@@ -53019,7 +53407,7 @@ async function extractZipToDirectory(zipBytes, targetDir, manifest) {
53019
53407
  }
53020
53408
  extractedFilePaths.add(normalized);
53021
53409
  }
53022
- await writeFile3(outputPath, content);
53410
+ await writeFile4(outputPath, content);
53023
53411
  }
53024
53412
  for (const expectedPath of expectedFiles.keys()) {
53025
53413
  if (!extractedFilePaths.has(expectedPath)) {
@@ -53031,7 +53419,7 @@ function parseJsonLines2(content) {
53031
53419
  return content.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line));
53032
53420
  }
53033
53421
  async function buildSearchIndex(corpusDir) {
53034
- const searchPath = path4.join(corpusDir, "search", "search-documents.jsonl");
53422
+ const searchPath = path5.join(corpusDir, "search", "search-documents.jsonl");
53035
53423
  const content = await readFile2(searchPath, "utf8");
53036
53424
  const documents = parseJsonLines2(content);
53037
53425
  const uniqueDocuments = [];
@@ -53058,7 +53446,7 @@ async function buildSearchIndex(corpusDir) {
53058
53446
  }
53059
53447
  });
53060
53448
  miniSearch.addAll(uniqueDocuments);
53061
- await writeFile3(path4.join(corpusDir, "search", "minisearch-index.json"), JSON.stringify(miniSearch.toJSON()));
53449
+ await writeFile4(path5.join(corpusDir, "search", "minisearch-index.json"), JSON.stringify(miniSearch.toJSON()));
53062
53450
  }
53063
53451
  async function commandCacheRefresh(options) {
53064
53452
  const mcpUrl = readStringFlag(options, "mcp-url");
@@ -53076,12 +53464,12 @@ async function commandCacheRefresh(options) {
53076
53464
  console.log(`Cache root: ${getCacheRoot(options)}`);
53077
53465
  }
53078
53466
  async function readCurrentCache(cacheRoot) {
53079
- const currentPath = path4.join(cacheRoot, "current.json");
53467
+ const currentPath = path5.join(cacheRoot, "current.json");
53080
53468
  if (!existsSync2(currentPath)) return null;
53081
53469
  try {
53082
53470
  const parsed = JSON.parse(await readFile2(currentPath, "utf8"));
53083
53471
  if (!isCorpusCurrentRecord(parsed)) return null;
53084
- const corpusDir = path4.resolve(parsed.corpusDir);
53472
+ const corpusDir = path5.resolve(parsed.corpusDir);
53085
53473
  if (!isPathInsideDirectory2(cacheRoot, corpusDir)) return null;
53086
53474
  const manifest = await readCorpusManifestFromDirectory(corpusDir);
53087
53475
  if (!manifest || !corpusCurrentMatchesManifest(parsed, manifest)) return null;
@@ -53110,7 +53498,7 @@ async function commandCacheStatus(options) {
53110
53498
  async function commandCacheClear(options) {
53111
53499
  const cacheRoot = getCacheRoot(options);
53112
53500
  assertSafeCacheRoot(cacheRoot);
53113
- if (!isDefaultCacheRoot(cacheRoot) && !existsSync2(path4.join(cacheRoot, CACHE_MARKER_FILE))) {
53501
+ if (!isDefaultCacheRoot(cacheRoot) && !existsSync2(path5.join(cacheRoot, CACHE_MARKER_FILE))) {
53114
53502
  throw new Error(`Refusing to clear unmarked custom cache directory: ${cacheRoot}`);
53115
53503
  }
53116
53504
  await rm2(cacheRoot, { recursive: true, force: true });
@@ -53124,7 +53512,7 @@ async function commandCacheSearch(options) {
53124
53512
  const current = await readCurrentCache(cacheRoot);
53125
53513
  const corpusDir = typeof current?.corpusDir === "string" ? current.corpusDir : null;
53126
53514
  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");
53515
+ const indexPath = path5.join(corpusDir, "search", "minisearch-index.json");
53128
53516
  const rawIndex = await readFile2(indexPath, "utf8");
53129
53517
  const miniSearch = MiniSearch2.loadJSON(rawIndex, {
53130
53518
  fields: ["title", "text"],