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 +658 -174
- package/dist/sdk/README.md +3 -3
- package/dist/sdk/manifest.json +26 -26
- package/dist/sdk/wave-engine-sdk-2026-06-24.4._Dtk7L-p5Py75MSJ1DzWkQJnZa-Y3mpS.zip +0 -0
- package/package.json +1 -1
- package/dist/sdk/wave-engine-sdk-2026-06-24.4.TfG1gvbOLSyom3MfrRMlDZCm3Rji9aGt.zip +0 -0
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
25426
|
-
const index =
|
|
25425
|
+
static GetFilename(path6) {
|
|
25426
|
+
const index = path6.lastIndexOf("/");
|
|
25427
25427
|
if (index < 0) {
|
|
25428
|
-
return
|
|
25428
|
+
return path6;
|
|
25429
25429
|
}
|
|
25430
|
-
return
|
|
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"
|
|
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: ["
|
|
42013
|
-
pathExtensions: ["
|
|
42014
|
-
assetManagerAlias: "
|
|
42015
|
-
monaco: { interfaceName: "
|
|
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.
|
|
42524
|
-
var WAVE3D_AGENT_SDK_REQUIRED_BUILD = "agent-sdk-
|
|
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
|
|
43724
|
-
if (!
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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 `
|
|
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(
|
|
44798
|
-
const trimmed =
|
|
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
|
|
44935
|
-
return typeof
|
|
44936
|
-
}).filter((
|
|
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
|
|
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
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
46887
|
-
const normalized =
|
|
47305
|
+
function isAuthoredCodePath(path6) {
|
|
47306
|
+
const normalized = path6.trim().toLowerCase();
|
|
46888
47307
|
return normalized.endsWith(".ts") || normalized.endsWith(".js");
|
|
46889
47308
|
}
|
|
46890
|
-
function warnUnsafeCallbackUsage(
|
|
46891
|
-
if (!isAuthoredCodePath(
|
|
46892
|
-
const diagnostics = collectRuntimeHotReloadSafetyDiagnostics(
|
|
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 ${
|
|
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
|
|
47059
|
-
if (!
|
|
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:
|
|
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:
|
|
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
|
|
47113
|
-
if (!
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
47176
|
-
if (!
|
|
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(
|
|
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:
|
|
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:
|
|
47610
|
+
path: path6,
|
|
47192
47611
|
baseHash: resolvedHash.baseHash,
|
|
47193
47612
|
edits: editArray.edits
|
|
47194
47613
|
}] : [{
|
|
47195
47614
|
type: "textEdits",
|
|
47196
|
-
path:
|
|
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:
|
|
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
|
|
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 (!
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
47711
|
+
const path6 = typeof args?.path === "string" ? normalizeWaveFilePath(args.path) : "";
|
|
47293
47712
|
const oldText = requireNonEmptyString(args?.oldText);
|
|
47294
|
-
if (!
|
|
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(
|
|
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:
|
|
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:
|
|
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(
|
|
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:
|
|
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:
|
|
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
|
|
47364
|
-
if (!
|
|
47365
|
-
warnUnsafeCallbackUsage(
|
|
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:
|
|
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:
|
|
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
|
|
47393
|
-
if (!
|
|
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(
|
|
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:
|
|
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
|
|
47438
|
-
if (!
|
|
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:
|
|
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 =
|
|
50284
|
-
await
|
|
50285
|
-
const localPath =
|
|
50286
|
-
await
|
|
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_${
|
|
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
|
|
51425
|
+
const path6 = requireNonEmptyString(args?.path);
|
|
50942
51426
|
const nextName = requireNonEmptyString(stringAliasArg(args, ["name", "newName", "displayName"]));
|
|
50943
|
-
if (!
|
|
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:
|
|
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
|
|
50958
|
-
if (!
|
|
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:
|
|
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
|
|
51902
|
+
import { createHash as createHash4, randomUUID as randomUUID3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
51419
51903
|
function createSessionId() {
|
|
51420
|
-
return `wgb_${
|
|
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
|
|
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
|
|
51563
|
-
import { tmpdir as
|
|
51564
|
-
import
|
|
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 =
|
|
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_${
|
|
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 =
|
|
51632
|
-
await
|
|
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 =
|
|
51672
|
-
await
|
|
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 =
|
|
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 =
|
|
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_${
|
|
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
|
|
52851
|
+
return path5.join(homedir2(), ".wave3d", "agent-cache");
|
|
52368
52852
|
}
|
|
52369
52853
|
function getCacheRoot(options) {
|
|
52370
|
-
return
|
|
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 =
|
|
52390
|
-
const 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
|
-
|
|
52394
|
-
|
|
52395
|
-
|
|
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 =
|
|
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
|
|
52890
|
+
return path5.resolve(cacheRoot) === path5.resolve(getDefaultCacheRoot());
|
|
52407
52891
|
}
|
|
52408
52892
|
async function markCacheRoot(cacheRoot) {
|
|
52409
52893
|
assertSafeCacheRoot(cacheRoot);
|
|
52410
|
-
await
|
|
52411
|
-
await
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
52764
|
-
const corpusDir =
|
|
52765
|
-
await
|
|
52766
|
-
await
|
|
52767
|
-
await
|
|
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
|
|
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 =
|
|
52919
|
-
if (normalized === "." || normalized === ".." || normalized.startsWith("../") || normalized.includes("\0") ||
|
|
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 =
|
|
52926
|
-
const relative =
|
|
52927
|
-
if (relative.startsWith("..") ||
|
|
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 =
|
|
52942
|
-
return relative === "" || !!relative && !relative.startsWith("..") && !
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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"],
|