blueprint-extractor-mcp 7.0.8 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/dist/helpers/next-step-hints.js +7 -45
- package/dist/helpers/project-resolution.js +40 -17
- package/dist/helpers/tool-help.js +0 -17
- package/dist/helpers/workspace-project.d.ts +4 -0
- package/dist/helpers/workspace-project.js +33 -1
- package/dist/project-controller.d.ts +4 -2
- package/dist/project-controller.js +62 -29
- package/dist/register-server-tools.js +1 -2
- package/dist/resources/static-doc-resources.js +7 -5
- package/dist/schemas/tool-inputs.d.ts +0 -1
- package/dist/schemas/tool-inputs.js +1 -13
- package/dist/server-config.js +8 -10
- package/dist/server-factory.js +38 -3
- package/dist/tool-surface-manager.d.ts +5 -1
- package/dist/tool-surface-manager.js +42 -22
- package/dist/tools/widget-structure.d.ts +1 -2
- package/dist/tools/widget-structure.js +23 -267
- package/dist/tools/window-ui.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Blueprint Extractor MCP is a [Model Context Protocol](https://modelcontextprotoc
|
|
|
23
23
|
AI Assistant stdio MCP Server HTTP :30010 Unreal Editor
|
|
24
24
|
───────────── ◄────────────► ───────────────── ◄──────────────────► ─────────────────
|
|
25
25
|
Claude Code Node.js process Remote Control API
|
|
26
|
-
Codex / OpenCode
|
|
26
|
+
Codex / OpenCode 109 tools BlueprintExtractor
|
|
27
27
|
... 38 resources plugin
|
|
28
28
|
4 resource templates
|
|
29
29
|
12 prompts
|
|
@@ -112,22 +112,23 @@ codex mcp add --env UE_REMOTE_CONTROL_PORT=30010 \
|
|
|
112
112
|
|
|
113
113
|
## Tool Surface
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
Use `activate_tool_profile` to switch between the compact `default` surface and the full `expert` surface. The default profile keeps the context window lean and loads specialized families on demand via `activate_workflow_scope`.
|
|
116
116
|
|
|
117
117
|
| Scope | What It Unlocks |
|
|
118
118
|
|:------|:----------------|
|
|
119
|
-
| **Core** *(always on)* | Search, extraction, list/save/help,
|
|
119
|
+
| **Core** *(always on in `default` profile)* | Search, extraction, `find_and_extract`, list/save/help, and the profile/scope switches such as `extract_asset`, `search_assets`, `save_assets`, `get_tool_help`, `activate_tool_profile`, and `activate_workflow_scope` |
|
|
120
120
|
| `widget_authoring` | Parent scope that loads `widget_authoring_structure`, `widget_authoring_visual`, and `widget_verification` together |
|
|
121
|
-
| `widget_authoring_structure` |
|
|
121
|
+
| `widget_authoring_structure` | Recipe-first widget authoring, tree replacement, unified-diff patching, and focused structure edits without the deprecated widget aliases |
|
|
122
122
|
| `widget_authoring_visual` | Widget compile flows, CommonUI styles, widget animations, and widget preview capture |
|
|
123
123
|
| `widget_verification` | Widget capture, checkpoint bundles, capture listing, cleanup, and reference comparison |
|
|
124
|
-
| `material_authoring` |
|
|
125
|
-
| `blueprint_authoring` |
|
|
124
|
+
| `material_authoring` | `create_material_setup`, `modify_material`, `material_graph_operation`, compile, and material-instance edits |
|
|
125
|
+
| `blueprint_authoring` | `scaffold_blueprint`, graph/member edits, Blueprint creation, and Live Coding trigger |
|
|
126
126
|
| `schema_ai_authoring` | Structs, enums, Blackboards, Behavior Trees, and State Trees |
|
|
127
127
|
| `animation_authoring` | Anim sequences, montages, blend spaces, and widget motion authoring |
|
|
128
128
|
| `data_tables` | Data assets, data tables, curves, Input Actions, and Input Mapping Contexts |
|
|
129
129
|
| `import` | Async asset import and import-job polling |
|
|
130
|
-
| `
|
|
130
|
+
| `project_control` | Editor-session binding, launch/wait, project automation context, PIE lifecycle control, host build/restart/sync, and `apply_window_ui_changes` |
|
|
131
|
+
| `automation_testing` | Host-side automation runs and automation-run polling |
|
|
131
132
|
| `analysis` | Deterministic Blueprint review and low-noise project asset audits |
|
|
132
133
|
| `project_intelligence` | Bounded editor context, project indexing, freshness status, and snippet-first context search |
|
|
133
134
|
| `verification` | Editor/runtime screenshots, capture comparison, motion verification, and artifact inspection |
|
|
@@ -254,7 +254,7 @@ export const NEXT_STEP_HINTS_REGISTRY = new Map([
|
|
|
254
254
|
['extract_widget_blueprint', extractionHints('extract_widget_blueprint', {
|
|
255
255
|
on_success: [
|
|
256
256
|
'Use patch_widget or replace_widget_tree to modify the widget.',
|
|
257
|
-
'Use
|
|
257
|
+
'Use compile_widget to check for errors.',
|
|
258
258
|
'Use capture_widget_preview for visual verification.',
|
|
259
259
|
],
|
|
260
260
|
scope_suggestion: 'widget_authoring',
|
|
@@ -276,25 +276,17 @@ export const NEXT_STEP_HINTS_REGISTRY = new Map([
|
|
|
276
276
|
// =========================================================================
|
|
277
277
|
// Widget structure tools
|
|
278
278
|
// =========================================================================
|
|
279
|
-
['create_widget_blueprint', creationHints('extract_widget_blueprint', '
|
|
279
|
+
['create_widget_blueprint', creationHints('extract_widget_blueprint', 'replace_widget_tree', {
|
|
280
280
|
on_success: [
|
|
281
|
-
'Use
|
|
281
|
+
'Use replace_widget_tree to populate the widget tree.',
|
|
282
282
|
'Use extract_widget_blueprint to verify.',
|
|
283
283
|
'Use save_assets to persist.',
|
|
284
284
|
],
|
|
285
285
|
scope_suggestion: 'widget_authoring',
|
|
286
286
|
})],
|
|
287
|
-
['build_widget_tree', modificationHints('extract_widget_blueprint', {
|
|
288
|
-
on_success: [
|
|
289
|
-
'Use compile_widget_blueprint to verify the tree compiles.',
|
|
290
|
-
'Use extract_widget_blueprint to inspect the result.',
|
|
291
|
-
'Use save_assets to persist.',
|
|
292
|
-
],
|
|
293
|
-
scope_suggestion: 'widget_authoring',
|
|
294
|
-
})],
|
|
295
287
|
['replace_widget_tree', modificationHints('extract_widget_blueprint', {
|
|
296
288
|
on_success: [
|
|
297
|
-
'Use
|
|
289
|
+
'Use compile_widget to verify the new tree.',
|
|
298
290
|
'Use extract_widget_blueprint to inspect the result.',
|
|
299
291
|
'Use save_assets to persist.',
|
|
300
292
|
],
|
|
@@ -344,7 +336,7 @@ export const NEXT_STEP_HINTS_REGISTRY = new Map([
|
|
|
344
336
|
})],
|
|
345
337
|
['replace_widget_class', modificationHints('extract_widget_blueprint', {
|
|
346
338
|
on_success: [
|
|
347
|
-
'Use
|
|
339
|
+
'Use compile_widget to verify the class change.',
|
|
348
340
|
'Use extract_widget_blueprint to inspect the result.',
|
|
349
341
|
'Use save_assets to persist.',
|
|
350
342
|
],
|
|
@@ -352,7 +344,7 @@ export const NEXT_STEP_HINTS_REGISTRY = new Map([
|
|
|
352
344
|
})],
|
|
353
345
|
['batch_widget_operations', modificationHints('extract_widget_blueprint', {
|
|
354
346
|
on_success: [
|
|
355
|
-
'Use
|
|
347
|
+
'Use compile_widget to verify the batch result.',
|
|
356
348
|
'Use extract_widget_blueprint to inspect changes.',
|
|
357
349
|
'Use save_assets to persist.',
|
|
358
350
|
],
|
|
@@ -370,36 +362,6 @@ export const NEXT_STEP_HINTS_REGISTRY = new Map([
|
|
|
370
362
|
],
|
|
371
363
|
scope_suggestion: 'widget_authoring',
|
|
372
364
|
}],
|
|
373
|
-
['modify_widget_blueprint', modificationHints('extract_widget_blueprint', {
|
|
374
|
-
on_success: [
|
|
375
|
-
'Use operation-specific tools (patch_widget, replace_widget_tree, etc.) instead.',
|
|
376
|
-
'Use save_assets to persist changes.',
|
|
377
|
-
],
|
|
378
|
-
on_error: [
|
|
379
|
-
'Use extract_widget_blueprint to inspect current state.',
|
|
380
|
-
'Consider using operation-specific tools for clearer errors.',
|
|
381
|
-
],
|
|
382
|
-
scope_suggestion: 'widget_authoring',
|
|
383
|
-
})],
|
|
384
|
-
['modify_widget', modificationHints('extract_widget_blueprint', {
|
|
385
|
-
on_success: [
|
|
386
|
-
'Use save_assets to persist changes.',
|
|
387
|
-
'Use extract_widget_blueprint to verify.',
|
|
388
|
-
],
|
|
389
|
-
scope_suggestion: 'widget_authoring',
|
|
390
|
-
})],
|
|
391
|
-
['compile_widget_blueprint', {
|
|
392
|
-
on_success: [
|
|
393
|
-
'Inspect compile.messages and diagnostics for warnings.',
|
|
394
|
-
'Re-extract the widget blueprint before applying the next patch.',
|
|
395
|
-
'Check BindWidget names/types if there are abstract class references.',
|
|
396
|
-
],
|
|
397
|
-
on_error: [
|
|
398
|
-
'Use extract_widget_blueprint to inspect the current state.',
|
|
399
|
-
'Fix reported issues and recompile.',
|
|
400
|
-
],
|
|
401
|
-
scope_suggestion: 'widget_authoring',
|
|
402
|
-
}],
|
|
403
365
|
// =========================================================================
|
|
404
366
|
// Widget verification tools
|
|
405
367
|
// =========================================================================
|
|
@@ -464,7 +426,7 @@ export const NEXT_STEP_HINTS_REGISTRY = new Map([
|
|
|
464
426
|
['apply_commonui_button_style', {
|
|
465
427
|
on_success: [
|
|
466
428
|
'Use extract_widget_blueprint to verify the style was applied.',
|
|
467
|
-
'Use
|
|
429
|
+
'Use compile_widget to verify compilation.',
|
|
468
430
|
'Use save_assets to persist.',
|
|
469
431
|
],
|
|
470
432
|
on_error: [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { access } from 'node:fs/promises';
|
|
2
2
|
import { constants as fsConstants } from 'node:fs';
|
|
3
|
-
import path, {
|
|
4
|
-
import { buildEngineAssociationCandidates, readProjectEngineAssociation } from './workspace-project.js';
|
|
3
|
+
import path, { posix as posixPath, win32 as win32Path } from 'node:path';
|
|
4
|
+
import { buildEngineAssociationCandidates, isWindowsStylePath, isWslMountedWindowsPath, readProjectEngineAssociation, toHostFilesystemPath, toWindowsStylePath, } from './workspace-project.js';
|
|
5
5
|
export function rememberExternalBuild(result) {
|
|
6
6
|
return {
|
|
7
7
|
success: result.success === true,
|
|
@@ -32,6 +32,9 @@ export async function getProjectAutomationContext(deps) {
|
|
|
32
32
|
return nextContext;
|
|
33
33
|
}
|
|
34
34
|
const cachedHeuristicEngineRoots = new Map();
|
|
35
|
+
function getPathModule(platform) {
|
|
36
|
+
return platform === 'win32' ? win32Path : posixPath;
|
|
37
|
+
}
|
|
35
38
|
export function getHeuristicEngineCandidates(platform = process.platform) {
|
|
36
39
|
if (platform === 'win32') {
|
|
37
40
|
return [
|
|
@@ -74,10 +77,13 @@ function getEngineMarkers(platform) {
|
|
|
74
77
|
'Engine/Build/BatchFiles/Build.sh',
|
|
75
78
|
];
|
|
76
79
|
}
|
|
77
|
-
async function accessFirstMatchingMarker(root, markers) {
|
|
80
|
+
async function accessFirstMatchingMarker(root, markers, targetPlatform, hostPlatform) {
|
|
81
|
+
const pathModule = getPathModule(targetPlatform);
|
|
82
|
+
const normalizedRoot = targetPlatform === 'win32' ? toWindowsStylePath(root) : root;
|
|
78
83
|
for (const marker of markers) {
|
|
79
84
|
try {
|
|
80
|
-
|
|
85
|
+
const candidate = pathModule.resolve(normalizedRoot, marker);
|
|
86
|
+
await access(toHostFilesystemPath(candidate, targetPlatform, hostPlatform), fsConstants.F_OK);
|
|
81
87
|
return true;
|
|
82
88
|
}
|
|
83
89
|
catch {
|
|
@@ -86,24 +92,24 @@ async function accessFirstMatchingMarker(root, markers) {
|
|
|
86
92
|
}
|
|
87
93
|
return false;
|
|
88
94
|
}
|
|
89
|
-
async function probeEngineRootHeuristic(
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
async function probeEngineRootHeuristic(targetPlatform, hostPlatform) {
|
|
96
|
+
const cacheKey = `${hostPlatform}:${targetPlatform}`;
|
|
97
|
+
if (cachedHeuristicEngineRoots.has(cacheKey)) {
|
|
98
|
+
return cachedHeuristicEngineRoots.get(cacheKey) || undefined;
|
|
92
99
|
}
|
|
93
|
-
for (const candidate of getHeuristicEngineCandidates(
|
|
94
|
-
if (await accessFirstMatchingMarker(candidate, getEngineMarkers(
|
|
95
|
-
cachedHeuristicEngineRoots.set(
|
|
100
|
+
for (const candidate of getHeuristicEngineCandidates(targetPlatform)) {
|
|
101
|
+
if (await accessFirstMatchingMarker(candidate, getEngineMarkers(targetPlatform), targetPlatform, hostPlatform)) {
|
|
102
|
+
cachedHeuristicEngineRoots.set(cacheKey, candidate);
|
|
96
103
|
return candidate;
|
|
97
104
|
}
|
|
98
105
|
}
|
|
99
|
-
cachedHeuristicEngineRoots.set(
|
|
106
|
+
cachedHeuristicEngineRoots.set(cacheKey, '');
|
|
100
107
|
return undefined;
|
|
101
108
|
}
|
|
102
|
-
async function probePreferredEngineRoot(candidates,
|
|
103
|
-
const markers = getEngineMarkers(platform);
|
|
109
|
+
async function probePreferredEngineRoot(candidates, hostPlatform) {
|
|
104
110
|
for (const candidate of candidates) {
|
|
105
|
-
if (await accessFirstMatchingMarker(candidate,
|
|
106
|
-
return candidate;
|
|
111
|
+
if (await accessFirstMatchingMarker(candidate.path, getEngineMarkers(candidate.platform), candidate.platform, hostPlatform)) {
|
|
112
|
+
return candidate.path;
|
|
107
113
|
}
|
|
108
114
|
}
|
|
109
115
|
return undefined;
|
|
@@ -133,7 +139,16 @@ export async function resolveProjectInputs(request, deps) {
|
|
|
133
139
|
const projectPath = firstDefinedString(request.project_path, projectPathFromContext, projectPathFromWorkspace, projectPathFromEnv);
|
|
134
140
|
const target = firstDefinedString(request.target, targetFromContext, targetFromWorkspace, targetFromEnv);
|
|
135
141
|
const engineAssociation = projectPath ? await readProjectEngineAssociation(projectPath) : undefined;
|
|
136
|
-
const
|
|
142
|
+
const windowsWorkspaceHint = [request.engine_root, request.project_path, projectPathFromWorkspace, projectPathFromEnv]
|
|
143
|
+
.filter((value) => typeof value === 'string' && value.length > 0)
|
|
144
|
+
.some((value) => isWindowsStylePath(value) || isWslMountedWindowsPath(value));
|
|
145
|
+
const heuristicPlatforms = windowsWorkspaceHint && platform !== 'win32'
|
|
146
|
+
? ['win32', platform]
|
|
147
|
+
: [platform];
|
|
148
|
+
const associationCandidates = heuristicPlatforms.flatMap((candidatePlatform) => (buildEngineAssociationCandidates(engineAssociation, candidatePlatform).map((candidate) => ({
|
|
149
|
+
path: candidate,
|
|
150
|
+
platform: candidatePlatform,
|
|
151
|
+
}))));
|
|
137
152
|
let engineRoot = firstDefinedString(request.engine_root, engineRootFromContext, engineRootFromEnv);
|
|
138
153
|
let engineRootSource;
|
|
139
154
|
if (request.engine_root) {
|
|
@@ -147,7 +162,15 @@ export async function resolveProjectInputs(request, deps) {
|
|
|
147
162
|
}
|
|
148
163
|
else {
|
|
149
164
|
const preferredCandidate = await probePreferredEngineRoot(associationCandidates, platform);
|
|
150
|
-
|
|
165
|
+
let heuristicRoot = preferredCandidate;
|
|
166
|
+
if (!heuristicRoot) {
|
|
167
|
+
for (const candidatePlatform of heuristicPlatforms) {
|
|
168
|
+
heuristicRoot = await probeEngineRootHeuristic(candidatePlatform, platform);
|
|
169
|
+
if (heuristicRoot) {
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
151
174
|
if (heuristicRoot) {
|
|
152
175
|
engineRoot = heuristicRoot;
|
|
153
176
|
engineRootSource = 'filesystem_heuristic';
|
|
@@ -102,22 +102,6 @@ export function summarizeOutputSchema(schema) {
|
|
|
102
102
|
}
|
|
103
103
|
export function collectToolExampleFamilies(exampleCatalog, toolName) {
|
|
104
104
|
const equivalentToolNames = new Set([toolName]);
|
|
105
|
-
if (toolName === 'modify_widget_blueprint') {
|
|
106
|
-
[
|
|
107
|
-
'patch_widget',
|
|
108
|
-
'patch_widget_class_defaults',
|
|
109
|
-
'insert_widget_child',
|
|
110
|
-
'remove_widget',
|
|
111
|
-
'move_widget',
|
|
112
|
-
'wrap_widget',
|
|
113
|
-
'replace_widget_class',
|
|
114
|
-
'replace_widget_tree',
|
|
115
|
-
'batch_widget_operations',
|
|
116
|
-
].forEach((name) => equivalentToolNames.add(name));
|
|
117
|
-
}
|
|
118
|
-
if (toolName === 'compile_widget_blueprint') {
|
|
119
|
-
equivalentToolNames.add('compile_widget');
|
|
120
|
-
}
|
|
121
105
|
return Object.entries(exampleCatalog)
|
|
122
106
|
.flatMap(([family, entry]) => {
|
|
123
107
|
const exampleTitles = entry.examples
|
|
@@ -143,7 +127,6 @@ export function collectRelatedResources(toolName) {
|
|
|
143
127
|
if (toolName.startsWith('create_')
|
|
144
128
|
|| toolName.startsWith('modify_')
|
|
145
129
|
|| toolName.startsWith('apply_')
|
|
146
|
-
|| toolName === 'build_widget_tree'
|
|
147
130
|
|| toolName === 'save_assets') {
|
|
148
131
|
resources.add('blueprint://write-capabilities');
|
|
149
132
|
resources.add('blueprint://authoring-conventions');
|
|
@@ -3,6 +3,10 @@ export type WorkspaceProjectResolution = {
|
|
|
3
3
|
ambiguous: boolean;
|
|
4
4
|
searchedFrom: string;
|
|
5
5
|
};
|
|
6
|
+
export declare function isWindowsStylePath(input: string): boolean;
|
|
7
|
+
export declare function isWslMountedWindowsPath(input: string): boolean;
|
|
8
|
+
export declare function toWindowsStylePath(input: string): string;
|
|
9
|
+
export declare function toHostFilesystemPath(input: string, targetPlatform: NodeJS.Platform, hostPlatform?: NodeJS.Platform): string;
|
|
6
10
|
export declare function normalizeFilesystemPath(input: string | undefined): string | undefined;
|
|
7
11
|
export declare function filesystemPathsEqual(left: string | undefined, right: string | undefined): boolean;
|
|
8
12
|
export declare function findNearestWorkspaceProject(startDir?: string): Promise<WorkspaceProjectResolution>;
|
|
@@ -2,9 +2,41 @@ import { readFile, readdir } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
const WINDOWS_DRIVE_PATH = /^[A-Za-z]:[\\/]/;
|
|
4
4
|
const WINDOWS_UNC_PATH = /^(?:\\\\|\/\/)[^\\/]+[\\/][^\\/]+/;
|
|
5
|
-
|
|
5
|
+
const WSL_MOUNT_PATH = /^\/mnt\/([a-zA-Z])(\/.*)?$/;
|
|
6
|
+
export function isWindowsStylePath(input) {
|
|
6
7
|
return WINDOWS_DRIVE_PATH.test(input) || WINDOWS_UNC_PATH.test(input);
|
|
7
8
|
}
|
|
9
|
+
export function isWslMountedWindowsPath(input) {
|
|
10
|
+
return WSL_MOUNT_PATH.test(input);
|
|
11
|
+
}
|
|
12
|
+
export function toWindowsStylePath(input) {
|
|
13
|
+
if (isWindowsStylePath(input)) {
|
|
14
|
+
return path.win32.normalize(input);
|
|
15
|
+
}
|
|
16
|
+
const match = input.replaceAll('\\', '/').match(WSL_MOUNT_PATH);
|
|
17
|
+
if (!match) {
|
|
18
|
+
return input;
|
|
19
|
+
}
|
|
20
|
+
const drive = match[1]?.toUpperCase();
|
|
21
|
+
const suffix = match[2] ?? '';
|
|
22
|
+
return path.win32.normalize(`${drive}:${suffix}`);
|
|
23
|
+
}
|
|
24
|
+
export function toHostFilesystemPath(input, targetPlatform, hostPlatform = process.platform) {
|
|
25
|
+
if (targetPlatform !== 'win32' || hostPlatform === 'win32') {
|
|
26
|
+
return input;
|
|
27
|
+
}
|
|
28
|
+
if (isWslMountedWindowsPath(input)) {
|
|
29
|
+
return path.posix.normalize(input.replaceAll('\\', '/'));
|
|
30
|
+
}
|
|
31
|
+
const normalized = input.replaceAll('\\', '/');
|
|
32
|
+
const match = normalized.match(/^([A-Za-z]):(\/.*)?$/);
|
|
33
|
+
if (!match) {
|
|
34
|
+
return input;
|
|
35
|
+
}
|
|
36
|
+
const drive = match[1]?.toLowerCase();
|
|
37
|
+
const suffix = match[2] ?? '';
|
|
38
|
+
return path.posix.normalize(`/mnt/${drive}${suffix}`);
|
|
39
|
+
}
|
|
8
40
|
export function normalizeFilesystemPath(input) {
|
|
9
41
|
if (!input) {
|
|
10
42
|
return undefined;
|
|
@@ -89,6 +89,7 @@ export interface CommandRunner {
|
|
|
89
89
|
cwd?: string;
|
|
90
90
|
env?: NodeJS.ProcessEnv;
|
|
91
91
|
timeoutMs: number;
|
|
92
|
+
platform?: NodeJS.Platform;
|
|
92
93
|
}): Promise<{
|
|
93
94
|
exitCode: number;
|
|
94
95
|
stdout: string;
|
|
@@ -121,10 +122,11 @@ export interface ProjectControllerLike {
|
|
|
121
122
|
waitForReconnect?: boolean;
|
|
122
123
|
}): Promise<RestartReconnectResult>;
|
|
123
124
|
}
|
|
125
|
+
export declare function inferExecutionPlatform(engineRoot: string | undefined, projectPath: string | undefined, hostPlatform?: NodeJS.Platform): NodeJS.Platform;
|
|
124
126
|
export declare function resolveCommandInvocation(executable: string, args: string[], platform: NodeJS.Platform, env?: NodeJS.ProcessEnv): CommandInvocation;
|
|
125
127
|
export declare function getBuildScriptCandidates(engineRoot: string, platform: NodeJS.Platform): string[];
|
|
126
|
-
export declare function resolveBuildScript(engineRoot: string, platform: NodeJS.Platform): Promise<string>;
|
|
127
|
-
export declare function resolveEditorExecutable(engineRoot: string, platform: NodeJS.Platform, mode?: 'editor' | 'commandlet'): Promise<string>;
|
|
128
|
+
export declare function resolveBuildScript(engineRoot: string, platform: NodeJS.Platform, hostPlatform?: NodeJS.Platform): Promise<string>;
|
|
129
|
+
export declare function resolveEditorExecutable(engineRoot: string, platform: NodeJS.Platform, mode?: 'editor' | 'commandlet', hostPlatform?: NodeJS.Platform): Promise<string>;
|
|
128
130
|
export declare function classifyChangedPaths(changedPaths: string[], forceRebuild?: boolean): SyncStrategyPlan;
|
|
129
131
|
/** Classify UBT build output into an error category. */
|
|
130
132
|
export declare function classifyBuildError(stdout: string, stderr: string, exitCode: number): {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { execSync, spawn } from 'node:child_process';
|
|
2
2
|
import { access, readdir, unlink } from 'node:fs/promises';
|
|
3
3
|
import { constants as fsConstants } from 'node:fs';
|
|
4
|
-
import {
|
|
4
|
+
import { posix as posixPath, win32 as win32Path } from 'node:path';
|
|
5
|
+
import { isWindowsStylePath, isWslMountedWindowsPath, toHostFilesystemPath, toWindowsStylePath, } from './helpers/workspace-project.js';
|
|
5
6
|
const DEFAULT_BUILD_TIMEOUT_MS = 30 * 60 * 1000;
|
|
6
7
|
const DEFAULT_DISCONNECT_TIMEOUT_MS = 60 * 1000;
|
|
7
8
|
const DEFAULT_RECONNECT_TIMEOUT_MS = 3 * 60 * 1000;
|
|
@@ -13,6 +14,27 @@ function defaultSleep(ms) {
|
|
|
13
14
|
function isWindowsBatchScript(executable, platform) {
|
|
14
15
|
return platform === 'win32' && /\.(bat|cmd)$/iu.test(executable);
|
|
15
16
|
}
|
|
17
|
+
function getPlatformPath(platform) {
|
|
18
|
+
return platform === 'win32' ? win32Path : posixPath;
|
|
19
|
+
}
|
|
20
|
+
function resolvePathForPlatform(root, segments, platform) {
|
|
21
|
+
const normalizedRoot = platform === 'win32' ? toWindowsStylePath(root) : root;
|
|
22
|
+
return getPlatformPath(platform).resolve(normalizedRoot, ...segments);
|
|
23
|
+
}
|
|
24
|
+
function dirnameForPlatform(filePath, platform) {
|
|
25
|
+
const normalizedPath = platform === 'win32' ? toWindowsStylePath(filePath) : filePath;
|
|
26
|
+
return getPlatformPath(platform).dirname(normalizedPath);
|
|
27
|
+
}
|
|
28
|
+
export function inferExecutionPlatform(engineRoot, projectPath, hostPlatform = process.platform) {
|
|
29
|
+
if (hostPlatform !== 'linux') {
|
|
30
|
+
return hostPlatform;
|
|
31
|
+
}
|
|
32
|
+
const pathHints = [engineRoot, projectPath].filter((value) => typeof value === 'string' && value.length > 0);
|
|
33
|
+
if (pathHints.some((value) => isWindowsStylePath(value) || isWslMountedWindowsPath(value))) {
|
|
34
|
+
return 'win32';
|
|
35
|
+
}
|
|
36
|
+
return hostPlatform;
|
|
37
|
+
}
|
|
16
38
|
function quoteWindowsCommandArg(value) {
|
|
17
39
|
if (value.length === 0) {
|
|
18
40
|
return '""';
|
|
@@ -45,24 +67,24 @@ export function resolveCommandInvocation(executable, args, platform, env = proce
|
|
|
45
67
|
export function getBuildScriptCandidates(engineRoot, platform) {
|
|
46
68
|
if (platform === 'win32') {
|
|
47
69
|
return [
|
|
48
|
-
|
|
70
|
+
resolvePathForPlatform(engineRoot, ['Engine', 'Build', 'BatchFiles', 'Build.bat'], platform),
|
|
49
71
|
];
|
|
50
72
|
}
|
|
51
73
|
if (platform === 'darwin') {
|
|
52
74
|
return [
|
|
53
|
-
|
|
54
|
-
|
|
75
|
+
resolvePathForPlatform(engineRoot, ['Engine', 'Build', 'BatchFiles', 'Mac', 'Build.sh'], platform),
|
|
76
|
+
resolvePathForPlatform(engineRoot, ['Engine', 'Build', 'BatchFiles', 'Build.sh'], platform),
|
|
55
77
|
];
|
|
56
78
|
}
|
|
57
79
|
return [
|
|
58
|
-
|
|
59
|
-
|
|
80
|
+
resolvePathForPlatform(engineRoot, ['Engine', 'Build', 'BatchFiles', 'Linux', 'Build.sh'], platform),
|
|
81
|
+
resolvePathForPlatform(engineRoot, ['Engine', 'Build', 'BatchFiles', 'Build.sh'], platform),
|
|
60
82
|
];
|
|
61
83
|
}
|
|
62
|
-
export async function resolveBuildScript(engineRoot, platform) {
|
|
84
|
+
export async function resolveBuildScript(engineRoot, platform, hostPlatform = process.platform) {
|
|
63
85
|
for (const candidate of getBuildScriptCandidates(engineRoot, platform)) {
|
|
64
86
|
try {
|
|
65
|
-
await access(candidate, fsConstants.F_OK);
|
|
87
|
+
await access(toHostFilesystemPath(candidate, platform, hostPlatform), fsConstants.F_OK);
|
|
66
88
|
return candidate;
|
|
67
89
|
}
|
|
68
90
|
catch {
|
|
@@ -71,26 +93,27 @@ export async function resolveBuildScript(engineRoot, platform) {
|
|
|
71
93
|
}
|
|
72
94
|
throw new Error(`Unable to locate an Unreal build script under ${engineRoot} for platform ${platform}.`);
|
|
73
95
|
}
|
|
74
|
-
export async function resolveEditorExecutable(engineRoot, platform, mode = 'editor') {
|
|
96
|
+
export async function resolveEditorExecutable(engineRoot, platform, mode = 'editor', hostPlatform = process.platform) {
|
|
75
97
|
const executableName = mode === 'commandlet'
|
|
76
98
|
? (platform === 'win32' ? 'UnrealEditor-Cmd.exe' : 'UnrealEditor-Cmd')
|
|
77
99
|
: (platform === 'win32' ? 'UnrealEditor.exe' : 'UnrealEditor');
|
|
78
100
|
const executable = platform === 'win32'
|
|
79
|
-
?
|
|
101
|
+
? resolvePathForPlatform(engineRoot, ['Engine', 'Binaries', 'Win64', executableName], platform)
|
|
80
102
|
: platform === 'darwin'
|
|
81
|
-
?
|
|
82
|
-
:
|
|
83
|
-
await access(executable, fsConstants.F_OK);
|
|
103
|
+
? resolvePathForPlatform(engineRoot, ['Engine', 'Binaries', 'Mac', executableName], platform)
|
|
104
|
+
: resolvePathForPlatform(engineRoot, ['Engine', 'Binaries', 'Linux', executableName], platform);
|
|
105
|
+
await access(toHostFilesystemPath(executable, platform, hostPlatform), fsConstants.F_OK);
|
|
84
106
|
return executable;
|
|
85
107
|
}
|
|
86
108
|
async function defaultRunCommand(executable, args, options) {
|
|
87
109
|
return await new Promise((resolveRun, rejectRun) => {
|
|
88
|
-
const
|
|
110
|
+
const commandPlatform = options.platform ?? process.platform;
|
|
111
|
+
const invocation = resolveCommandInvocation(executable, args, commandPlatform, options.env ?? process.env);
|
|
89
112
|
const child = spawn(invocation.executable, invocation.args, {
|
|
90
113
|
cwd: options.cwd,
|
|
91
114
|
env: options.env,
|
|
92
115
|
shell: false,
|
|
93
|
-
windowsVerbatimArguments: isWindowsBatchScript(executable,
|
|
116
|
+
windowsVerbatimArguments: isWindowsBatchScript(executable, commandPlatform),
|
|
94
117
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
95
118
|
});
|
|
96
119
|
let stdout = '';
|
|
@@ -165,7 +188,7 @@ function fileName(path) {
|
|
|
165
188
|
return lastSlash >= 0 ? normalized.slice(lastSlash + 1) : normalized;
|
|
166
189
|
}
|
|
167
190
|
function normalizeFilesystemPathForCommand(path, platform) {
|
|
168
|
-
return platform === 'win32' ? win32Path.normalize(path) : path;
|
|
191
|
+
return platform === 'win32' ? win32Path.normalize(toWindowsStylePath(path)) : path;
|
|
169
192
|
}
|
|
170
193
|
export function classifyChangedPaths(changedPaths, forceRebuild = false) {
|
|
171
194
|
const reasons = new Set();
|
|
@@ -213,6 +236,7 @@ export function classifyChangedPaths(changedPaths, forceRebuild = false) {
|
|
|
213
236
|
}
|
|
214
237
|
const UHT_CACHE_PATTERN = /\.(uhtpath|uhtsettings)$/i;
|
|
215
238
|
async function walkAndDeleteMatching(dir, pattern, deleted) {
|
|
239
|
+
const hostPath = getPlatformPath(process.platform);
|
|
216
240
|
let entries;
|
|
217
241
|
try {
|
|
218
242
|
entries = await readdir(dir, { withFileTypes: true });
|
|
@@ -221,7 +245,7 @@ async function walkAndDeleteMatching(dir, pattern, deleted) {
|
|
|
221
245
|
return;
|
|
222
246
|
}
|
|
223
247
|
for (const entry of entries) {
|
|
224
|
-
const fullPath = resolve(dir, entry.name);
|
|
248
|
+
const fullPath = hostPath.resolve(dir, entry.name);
|
|
225
249
|
if (entry.isDirectory()) {
|
|
226
250
|
await walkAndDeleteMatching(fullPath, pattern, deleted);
|
|
227
251
|
}
|
|
@@ -238,7 +262,7 @@ async function walkAndDeleteMatching(dir, pattern, deleted) {
|
|
|
238
262
|
}
|
|
239
263
|
async function clearUhtCacheFiles(projectDir) {
|
|
240
264
|
const deleted = [];
|
|
241
|
-
const intermediateDir = resolve(projectDir, 'Intermediate');
|
|
265
|
+
const intermediateDir = getPlatformPath(process.platform).resolve(projectDir, 'Intermediate');
|
|
242
266
|
await walkAndDeleteMatching(intermediateDir, UHT_CACHE_PATTERN, deleted);
|
|
243
267
|
return deleted;
|
|
244
268
|
}
|
|
@@ -345,6 +369,7 @@ export class ProjectController {
|
|
|
345
369
|
const projectPath = request.projectPath ?? this.env.UE_PROJECT_PATH;
|
|
346
370
|
const target = request.target ?? this.env.UE_PROJECT_TARGET ?? this.env.UE_EDITOR_TARGET;
|
|
347
371
|
const platform = parseBuildPlatform(request.platform ?? this.env.UE_BUILD_PLATFORM, this.platform);
|
|
372
|
+
const executionPlatform = inferExecutionPlatform(engineRoot, projectPath, this.platform);
|
|
348
373
|
const configuration = parseConfiguration(request.configuration ?? this.env.UE_BUILD_CONFIGURATION);
|
|
349
374
|
const includeOutput = request.includeOutput ?? false;
|
|
350
375
|
const buildTimeoutMs = request.buildTimeoutMs ?? DEFAULT_BUILD_TIMEOUT_MS;
|
|
@@ -357,11 +382,13 @@ export class ProjectController {
|
|
|
357
382
|
if (!target) {
|
|
358
383
|
throw new Error('compile_project_code requires target or UE_PROJECT_TARGET/UE_EDITOR_TARGET');
|
|
359
384
|
}
|
|
360
|
-
const buildScript = await resolveBuildScript(engineRoot, this.platform);
|
|
361
|
-
const commandProjectPath = normalizeFilesystemPathForCommand(projectPath,
|
|
385
|
+
const buildScript = await resolveBuildScript(engineRoot, executionPlatform, this.platform);
|
|
386
|
+
const commandProjectPath = normalizeFilesystemPathForCommand(projectPath, executionPlatform);
|
|
387
|
+
const projectDir = dirnameForPlatform(projectPath, executionPlatform);
|
|
388
|
+
const hostProjectDir = toHostFilesystemPath(projectDir, executionPlatform, this.platform);
|
|
362
389
|
let uhtCacheFilesDeleted;
|
|
363
390
|
if (request.clearUhtCache) {
|
|
364
|
-
uhtCacheFilesDeleted = await clearUhtCacheFiles(
|
|
391
|
+
uhtCacheFilesDeleted = await clearUhtCacheFiles(hostProjectDir);
|
|
365
392
|
}
|
|
366
393
|
const args = [
|
|
367
394
|
target,
|
|
@@ -373,9 +400,10 @@ export class ProjectController {
|
|
|
373
400
|
];
|
|
374
401
|
const startedAt = Date.now();
|
|
375
402
|
const completed = await this.runCommand(buildScript, args, {
|
|
376
|
-
cwd:
|
|
403
|
+
cwd: hostProjectDir,
|
|
377
404
|
env: this.env,
|
|
378
405
|
timeoutMs: buildTimeoutMs,
|
|
406
|
+
platform: executionPlatform,
|
|
379
407
|
});
|
|
380
408
|
const result = {
|
|
381
409
|
success: completed.exitCode === 0,
|
|
@@ -383,7 +411,7 @@ export class ProjectController {
|
|
|
383
411
|
strategy: 'external_build',
|
|
384
412
|
engineRoot,
|
|
385
413
|
projectPath,
|
|
386
|
-
projectDir
|
|
414
|
+
projectDir,
|
|
387
415
|
target,
|
|
388
416
|
platform,
|
|
389
417
|
configuration,
|
|
@@ -465,25 +493,30 @@ export class ProjectController {
|
|
|
465
493
|
const engineRoot = request.engineRoot ?? this.env.UE_ENGINE_ROOT;
|
|
466
494
|
const projectPath = request.projectPath ?? this.env.UE_PROJECT_PATH;
|
|
467
495
|
const diagnostics = [];
|
|
496
|
+
const executionPlatform = inferExecutionPlatform(engineRoot, projectPath, this.platform);
|
|
468
497
|
if (!engineRoot) {
|
|
469
498
|
throw new Error('launch_editor requires engine_root or UE_ENGINE_ROOT');
|
|
470
499
|
}
|
|
471
500
|
if (!projectPath) {
|
|
472
501
|
throw new Error('launch_editor requires project_path or UE_PROJECT_PATH');
|
|
473
502
|
}
|
|
474
|
-
const executable = await resolveEditorExecutable(engineRoot,
|
|
503
|
+
const executable = await resolveEditorExecutable(engineRoot, executionPlatform, 'editor', this.platform);
|
|
504
|
+
const hostExecutable = toHostFilesystemPath(executable, executionPlatform, this.platform);
|
|
475
505
|
const requestedArgs = request.additionalArgs ?? [];
|
|
506
|
+
const commandProjectPath = normalizeFilesystemPathForCommand(projectPath, executionPlatform);
|
|
476
507
|
const args = [
|
|
477
|
-
|
|
508
|
+
commandProjectPath,
|
|
478
509
|
...DEFAULT_EDITOR_LAUNCH_ARGS.filter((arg) => !requestedArgs.includes(arg)),
|
|
479
510
|
...requestedArgs,
|
|
480
511
|
];
|
|
481
|
-
const invocation = resolveCommandInvocation(
|
|
512
|
+
const invocation = resolveCommandInvocation(hostExecutable, args, executionPlatform, this.env);
|
|
513
|
+
const projectDir = dirnameForPlatform(projectPath, executionPlatform);
|
|
514
|
+
const hostProjectDir = toHostFilesystemPath(projectDir, executionPlatform, this.platform);
|
|
482
515
|
const child = this.spawnProcess(invocation.executable, invocation.args, {
|
|
483
|
-
cwd:
|
|
516
|
+
cwd: hostProjectDir,
|
|
484
517
|
env: this.env,
|
|
485
518
|
shell: false,
|
|
486
|
-
windowsVerbatimArguments: isWindowsBatchScript(
|
|
519
|
+
windowsVerbatimArguments: isWindowsBatchScript(hostExecutable, executionPlatform),
|
|
487
520
|
detached: true,
|
|
488
521
|
stdio: 'ignore',
|
|
489
522
|
});
|
|
@@ -494,7 +527,7 @@ export class ProjectController {
|
|
|
494
527
|
operation: 'launch_editor',
|
|
495
528
|
engineRoot,
|
|
496
529
|
projectPath,
|
|
497
|
-
projectDir
|
|
530
|
+
projectDir,
|
|
498
531
|
processId: child.pid ?? undefined,
|
|
499
532
|
command: {
|
|
500
533
|
executable: invocation.executable,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { exampleCatalog } from './catalogs/example-catalog.js';
|
|
3
3
|
import { collectRelatedResources, collectToolExampleFamilies as collectToolExampleFamiliesFromCatalog, summarizeOutputSchema, summarizeSchemaFields, } from './helpers/tool-help.js';
|
|
4
|
-
import { AnimMontageMutationOperationSchema, AnimationNotifySelectorSchema, AnimSequenceMutationOperationSchema, BehaviorTreeMutationOperationSchema, BehaviorTreeNodeSelectorSchema, BlackboardKeySchema, BlackboardMutationOperationSchema, BlendParameterSchema, BlendSpaceMutationOperationSchema, BlendSpaceSampleSchema, BlueprintGraphMutationOperationSchema, BlueprintMemberMutationOperationSchema, BuildConfigurationSchema, BuildPlatformSchema, CurveChannelSchema, CurveKeyDeleteSchema, CurveKeyUpsertSchema, CurveTableModeSchema, CurveTableRowSchema, CurveTypeSchema, DataTableRowSchema, ExtractAssetTypeSchema, EnhancedInputValueTypeSchema, FontImportItemSchema, ImportJobListSchema, ImportJobSchema, InputMappingSchema, JsonObjectSchema, MaterialConnectionSelectorFieldsSchema, MaterialFontParameterSchema, MaterialGraphOperationKindSchema, MaterialGraphOperationSchema, MaterialLayerStackSchema, MaterialNodePositionSchema, MaterialScalarParameterSchema, MaterialStaticSwitchParameterSchema, MaterialTextureParameterSchema, MaterialVectorParameterSchema, MeshImportOptionsSchema, StateTreeEditorNodeSelectorSchema, StateTreeMutationOperationSchema, StateTreeStateSelectorSchema, StateTreeTransitionSelectorSchema, StateTreeBindingsObjectSchema, TextureImportOptionsSchema, UserDefinedEnumEntrySchema, UserDefinedEnumMutationOperationSchema, UserDefinedStructFieldSchema, UserDefinedStructMutationOperationSchema,
|
|
4
|
+
import { AnimMontageMutationOperationSchema, AnimationNotifySelectorSchema, AnimSequenceMutationOperationSchema, BehaviorTreeMutationOperationSchema, BehaviorTreeNodeSelectorSchema, BlackboardKeySchema, BlackboardMutationOperationSchema, BlendParameterSchema, BlendSpaceMutationOperationSchema, BlendSpaceSampleSchema, BlueprintGraphMutationOperationSchema, BlueprintMemberMutationOperationSchema, BuildConfigurationSchema, BuildPlatformSchema, CurveChannelSchema, CurveKeyDeleteSchema, CurveKeyUpsertSchema, CurveTableModeSchema, CurveTableRowSchema, CurveTypeSchema, DataTableRowSchema, ExtractAssetTypeSchema, EnhancedInputValueTypeSchema, FontImportItemSchema, ImportJobListSchema, ImportJobSchema, InputMappingSchema, JsonObjectSchema, MaterialConnectionSelectorFieldsSchema, MaterialFontParameterSchema, MaterialGraphOperationKindSchema, MaterialGraphOperationSchema, MaterialLayerStackSchema, MaterialNodePositionSchema, MaterialScalarParameterSchema, MaterialStaticSwitchParameterSchema, MaterialTextureParameterSchema, MaterialVectorParameterSchema, MeshImportOptionsSchema, StateTreeEditorNodeSelectorSchema, StateTreeMutationOperationSchema, StateTreeStateSelectorSchema, StateTreeTransitionSelectorSchema, StateTreeBindingsObjectSchema, TextureImportOptionsSchema, UserDefinedEnumEntrySchema, UserDefinedEnumMutationOperationSchema, UserDefinedStructFieldSchema, UserDefinedStructMutationOperationSchema, WidgetNodeSchema, WidgetSelectorFieldsSchema, WindowFontApplicationSchema, } from './schemas/tool-inputs.js';
|
|
5
5
|
import { applyWindowUiChangesResultSchema, AutomationRunListSchema, automationRunSchema, CaptureResultSchema, CascadeResultSchema, CleanupCapturesResultSchema, CompareCaptureResultSchema, CompareMotionCaptureBundleResultSchema, CreateModifyWidgetAnimationResultSchema, ExtractWidgetAnimationResultSchema, ListCapturesResultSchema, motionCaptureModeSchema, MotionCaptureBundleResultSchema, widgetAnimationCheckpointSchema, } from './schemas/tool-results.js';
|
|
6
6
|
import { registerAnimationAuthoringTools } from './tools/animation-authoring.js';
|
|
7
7
|
import { registerAnalysisTools } from './tools/analysis-tools.js';
|
|
@@ -47,7 +47,6 @@ export function registerServerTools({ server, client, projectController, automat
|
|
|
47
47
|
server,
|
|
48
48
|
callSubsystemJson,
|
|
49
49
|
widgetNodeSchema: WidgetNodeSchema,
|
|
50
|
-
widgetBlueprintMutationOperationSchema: WidgetBlueprintMutationOperationSchema,
|
|
51
50
|
});
|
|
52
51
|
registerWidgetExtractionTools({
|
|
53
52
|
server,
|