blueprint-extractor-mcp 2.2.0 → 2.3.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 +4 -3
- package/dist/automation-controller.js +3 -10
- package/dist/index.js +656 -101
- package/dist/project-controller.d.ts +28 -0
- package/dist/project-controller.js +62 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ const serverInstructions = [
|
|
|
17
17
|
'Use the composable material tools for settings, node creation, node connection, and root-property binding. Treat modify_material and modify_material_function as advanced escape hatches.',
|
|
18
18
|
'Use create_input_action, modify_input_action, create_input_mapping_context, and modify_input_mapping_context for Enhanced Input authoring. Generic data asset mutation is intentionally rejected for those asset classes.',
|
|
19
19
|
'Verify blueprint wiring, layout data, and authored values semantically with the existing extract_* tools before relying on screenshots.',
|
|
20
|
-
'After widget or
|
|
20
|
+
'After widget or other user-facing UI mutations, treat the task as incomplete until capture_widget_preview succeeds or you explicitly report partial verification with the blocking reason.',
|
|
21
21
|
'Use run_automation_tests for gameplay or runtime verification. If no Automation Spec or Functional Test exists for a mechanic, report verification as partial instead of inferring success from structure alone.',
|
|
22
22
|
'Successful tool results mirror the same JSON in structuredContent and text. Recoverable execution failures return isError=true with code, message, recoverable, and next_steps.',
|
|
23
23
|
].join('\n');
|
|
@@ -38,11 +38,12 @@ const taskAwareTools = new Set([
|
|
|
38
38
|
]);
|
|
39
39
|
export const exampleCatalog = {
|
|
40
40
|
widget_blueprint: {
|
|
41
|
-
summary: 'Inspect the current widget, apply the smallest structural change that solves the layout problem, compile, then save.',
|
|
41
|
+
summary: 'Inspect the current widget, apply the smallest structural change that solves the layout problem, compile, visually confirm the rendered result, then save.',
|
|
42
42
|
recommended_flow: [
|
|
43
43
|
'extract_widget_blueprint',
|
|
44
44
|
'modify_widget_blueprint',
|
|
45
45
|
'compile_widget_blueprint',
|
|
46
|
+
'capture_widget_preview',
|
|
46
47
|
'save_assets',
|
|
47
48
|
],
|
|
48
49
|
examples: [
|
|
@@ -168,11 +169,12 @@ export const exampleCatalog = {
|
|
|
168
169
|
],
|
|
169
170
|
},
|
|
170
171
|
window_ui_polish: {
|
|
171
|
-
summary: 'Use the thin sequencing helper when a screen change touches variable flags, class defaults, compile
|
|
172
|
+
summary: 'Use the thin sequencing helper when a screen change touches variable flags, class defaults, compile, and optional code sync in one flow, then gate persistence on visual confirmation.',
|
|
172
173
|
recommended_flow: [
|
|
173
174
|
'extract_widget_blueprint',
|
|
174
175
|
'apply_window_ui_changes',
|
|
175
|
-
'
|
|
176
|
+
'capture_widget_preview',
|
|
177
|
+
'save_assets',
|
|
176
178
|
],
|
|
177
179
|
examples: [
|
|
178
180
|
{
|
|
@@ -190,7 +192,7 @@ export const exampleCatalog = {
|
|
|
190
192
|
ActiveTitleBarMaterial: '/Game/UI/MI_TitleBarActive.MI_TitleBarActive',
|
|
191
193
|
},
|
|
192
194
|
compile_after: true,
|
|
193
|
-
save_after:
|
|
195
|
+
save_after: false,
|
|
194
196
|
},
|
|
195
197
|
},
|
|
196
198
|
],
|
|
@@ -234,7 +236,8 @@ export const promptCatalog = {
|
|
|
234
236
|
parent_class_path ? `Expected parent class: ${parent_class_path}.` : 'Choose the narrowest appropriate parent widget class.',
|
|
235
237
|
existing_hud_asset_path ? `Inspect the existing HUD first: ${existing_hud_asset_path}.` : 'Inspect the current HUD wiring before replacing the screen.',
|
|
236
238
|
existing_transition_asset_path ? `Inspect the transition asset first: ${existing_transition_asset_path}.` : 'Inspect transition widgets and activatable-window flow before redesigning layout.',
|
|
237
|
-
'Produce a concrete widget-tree plan, required BindWidget names, class-default changes, and compile/save steps.',
|
|
239
|
+
'Produce a concrete widget-tree plan, required BindWidget names, class-default changes, and compile/capture/save steps.',
|
|
240
|
+
'The plan is not complete until it includes capture_widget_preview or an explicit partial verification fallback with a blocking reason.',
|
|
238
241
|
'Prefer centered_overlay, common_menu_shell, or activatable_window patterns over ad-hoc CanvasPanel placement.',
|
|
239
242
|
].join('\n'),
|
|
240
243
|
},
|
|
@@ -280,15 +283,19 @@ export const promptCatalog = {
|
|
|
280
283
|
buildPrompt: ({ widget_asset_path, compile_summary_json }) => [
|
|
281
284
|
`Debug WidgetBlueprint compile failures for ${widget_asset_path}.`,
|
|
282
285
|
compile_summary_json ? `Compile summary:\n${compile_summary_json}` : 'Start by compiling the widget blueprint and inspecting compile diagnostics.',
|
|
283
|
-
'Check for BindWidget type/name mismatches, abstract widget classes in the tree,
|
|
284
|
-
'
|
|
286
|
+
'Check for BindWidget type/name mismatches, abstract widget classes in the tree, stale class-default references, and degraded extraction states such as rootWidget=null with widgetTreeStatus/widgetTreeError.',
|
|
287
|
+
'If the failure is tied to CommonUI button styling, treat raw UButton background/style fields as unsupported wrapper surfaces and redirect to CommonUI style assets or a project-owned material-backed button base.',
|
|
288
|
+
'If the patch touched WidthOverride, HeightOverride, MinDesiredHeight, or similar overrides, verify the paired bOverride_* flags are enabled before assuming the write failed.',
|
|
289
|
+
'Return the minimal follow-up extract/modify/compile sequence needed to fix the compile state, then finish with capture_widget_preview or explicit partial verification if rendering is blocked.',
|
|
285
290
|
].join('\n'),
|
|
286
291
|
},
|
|
287
292
|
};
|
|
288
293
|
export function createBlueprintExtractorServer(client = new UEClient(), projectController = new ProjectController(), automationController = new AutomationController()) {
|
|
294
|
+
let cachedProjectAutomationContext = null;
|
|
295
|
+
let lastExternalBuildContext = null;
|
|
289
296
|
const server = new McpServer({
|
|
290
297
|
name: 'blueprint-extractor',
|
|
291
|
-
version: '2.
|
|
298
|
+
version: '2.3.0',
|
|
292
299
|
}, {
|
|
293
300
|
instructions: serverInstructions,
|
|
294
301
|
});
|
|
@@ -312,9 +319,261 @@ export function createBlueprintExtractorServer(client = new UEClient(), projectC
|
|
|
312
319
|
progress_message: z.string().optional(),
|
|
313
320
|
}).optional(),
|
|
314
321
|
}).passthrough();
|
|
322
|
+
const windowUiVerificationSchema = z.object({
|
|
323
|
+
required: z.boolean(),
|
|
324
|
+
status: z.enum(['compile_pending', 'unverified']),
|
|
325
|
+
surface: z.literal('editor_offscreen'),
|
|
326
|
+
recommendedTool: z.literal('capture_widget_preview'),
|
|
327
|
+
partialAllowed: z.boolean(),
|
|
328
|
+
reason: z.string(),
|
|
329
|
+
});
|
|
330
|
+
const applyWindowUiChangesResultSchema = v2ToolResultSchema.extend({
|
|
331
|
+
stoppedAt: z.string().optional(),
|
|
332
|
+
failedAfterStep: z.string().optional(),
|
|
333
|
+
steps: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
334
|
+
verification: windowUiVerificationSchema.optional(),
|
|
335
|
+
}).passthrough();
|
|
315
336
|
function isRecord(value) {
|
|
316
337
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
317
338
|
}
|
|
339
|
+
function isVerificationSurface(value) {
|
|
340
|
+
return value === 'editor_offscreen'
|
|
341
|
+
|| value === 'pie_runtime'
|
|
342
|
+
|| value === 'editor_tool_viewport'
|
|
343
|
+
|| value === 'external_packaged';
|
|
344
|
+
}
|
|
345
|
+
function inferVerificationSurface(captureType) {
|
|
346
|
+
if (isVerificationSurface(captureType)) {
|
|
347
|
+
return captureType;
|
|
348
|
+
}
|
|
349
|
+
switch (captureType) {
|
|
350
|
+
case 'widget_preview':
|
|
351
|
+
case 'comparison_diff':
|
|
352
|
+
default:
|
|
353
|
+
return 'editor_offscreen';
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function unknownToStringArray(value) {
|
|
357
|
+
if (!Array.isArray(value)) {
|
|
358
|
+
return [];
|
|
359
|
+
}
|
|
360
|
+
return value.filter((entry) => typeof entry === 'string' && entry.length > 0);
|
|
361
|
+
}
|
|
362
|
+
function buildDefaultArtifactScenarioId(payload) {
|
|
363
|
+
const captureType = typeof payload.captureType === 'string' && payload.captureType.length > 0
|
|
364
|
+
? payload.captureType
|
|
365
|
+
: 'capture';
|
|
366
|
+
const source = typeof payload.assetPath === 'string' && payload.assetPath.length > 0
|
|
367
|
+
? payload.assetPath
|
|
368
|
+
: typeof payload.captureId === 'string' && payload.captureId.length > 0
|
|
369
|
+
? payload.captureId
|
|
370
|
+
: 'capture';
|
|
371
|
+
return `${captureType}:${source}`;
|
|
372
|
+
}
|
|
373
|
+
function buildDefaultWorldContext(payload, surface) {
|
|
374
|
+
if (isRecord(payload.worldContext)) {
|
|
375
|
+
return payload.worldContext;
|
|
376
|
+
}
|
|
377
|
+
if (surface === 'editor_offscreen') {
|
|
378
|
+
const context = {
|
|
379
|
+
contextType: 'widget_blueprint',
|
|
380
|
+
renderLane: 'offscreen',
|
|
381
|
+
};
|
|
382
|
+
if (typeof payload.assetPath === 'string' && payload.assetPath.length > 0) {
|
|
383
|
+
context.assetPath = payload.assetPath;
|
|
384
|
+
}
|
|
385
|
+
if (typeof payload.widgetClass === 'string' && payload.widgetClass.length > 0) {
|
|
386
|
+
context.widgetClass = payload.widgetClass;
|
|
387
|
+
}
|
|
388
|
+
return context;
|
|
389
|
+
}
|
|
390
|
+
return undefined;
|
|
391
|
+
}
|
|
392
|
+
function buildDefaultCameraContext(payload, surface) {
|
|
393
|
+
if (isRecord(payload.cameraContext)) {
|
|
394
|
+
return payload.cameraContext;
|
|
395
|
+
}
|
|
396
|
+
const width = typeof payload.width === 'number' ? payload.width : undefined;
|
|
397
|
+
const height = typeof payload.height === 'number' ? payload.height : undefined;
|
|
398
|
+
if (surface === 'editor_offscreen' && (typeof width === 'number' || typeof height === 'number')) {
|
|
399
|
+
return {
|
|
400
|
+
contextType: 'offscreen_widget',
|
|
401
|
+
...(typeof width === 'number' ? { width } : {}),
|
|
402
|
+
...(typeof height === 'number' ? { height } : {}),
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
407
|
+
function normalizeVerificationArtifact(payload) {
|
|
408
|
+
const basePayload = isRecord(payload) ? { ...payload } : { data: payload };
|
|
409
|
+
const assetPaths = unknownToStringArray(basePayload.assetPaths);
|
|
410
|
+
const assetPath = typeof basePayload.assetPath === 'string'
|
|
411
|
+
? basePayload.assetPath
|
|
412
|
+
: assetPaths[0] ?? '';
|
|
413
|
+
const mergedAssetPaths = assetPaths.length > 0
|
|
414
|
+
? assetPaths
|
|
415
|
+
: assetPath
|
|
416
|
+
? [assetPath]
|
|
417
|
+
: [];
|
|
418
|
+
const surface = isVerificationSurface(basePayload.surface)
|
|
419
|
+
? basePayload.surface
|
|
420
|
+
: inferVerificationSurface(basePayload.captureType);
|
|
421
|
+
const worldContext = buildDefaultWorldContext(basePayload, surface);
|
|
422
|
+
const cameraContext = buildDefaultCameraContext(basePayload, surface);
|
|
423
|
+
return {
|
|
424
|
+
...basePayload,
|
|
425
|
+
assetPath,
|
|
426
|
+
assetPaths: mergedAssetPaths,
|
|
427
|
+
surface,
|
|
428
|
+
scenarioId: typeof basePayload.scenarioId === 'string' && basePayload.scenarioId.length > 0
|
|
429
|
+
? basePayload.scenarioId
|
|
430
|
+
: buildDefaultArtifactScenarioId({
|
|
431
|
+
...basePayload,
|
|
432
|
+
assetPath,
|
|
433
|
+
}),
|
|
434
|
+
...(worldContext ? { worldContext } : {}),
|
|
435
|
+
...(cameraContext ? { cameraContext } : {}),
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
function normalizeVerificationComparison(payload) {
|
|
439
|
+
const basePayload = isRecord(payload) ? payload : {};
|
|
440
|
+
const nested = isRecord(basePayload.comparison) ? basePayload.comparison : {};
|
|
441
|
+
const result = { ...nested };
|
|
442
|
+
const assignString = (key) => {
|
|
443
|
+
const value = typeof nested[key] === 'string'
|
|
444
|
+
? nested[key]
|
|
445
|
+
: typeof basePayload[key] === 'string'
|
|
446
|
+
? basePayload[key]
|
|
447
|
+
: undefined;
|
|
448
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
449
|
+
result[key] = value;
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
const assignNumber = (key) => {
|
|
453
|
+
const value = typeof nested[key] === 'number'
|
|
454
|
+
? nested[key]
|
|
455
|
+
: typeof basePayload[key] === 'number'
|
|
456
|
+
? basePayload[key]
|
|
457
|
+
: undefined;
|
|
458
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
459
|
+
result[key] = value;
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
const assignBoolean = (key) => {
|
|
463
|
+
const value = typeof nested[key] === 'boolean'
|
|
464
|
+
? nested[key]
|
|
465
|
+
: typeof basePayload[key] === 'boolean'
|
|
466
|
+
? basePayload[key]
|
|
467
|
+
: undefined;
|
|
468
|
+
if (typeof value === 'boolean') {
|
|
469
|
+
result[key] = value;
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
assignString('capturePath');
|
|
473
|
+
assignString('referencePath');
|
|
474
|
+
assignString('diffCaptureId');
|
|
475
|
+
assignString('diffArtifactPath');
|
|
476
|
+
assignNumber('tolerance');
|
|
477
|
+
assignBoolean('pass');
|
|
478
|
+
assignNumber('rmse');
|
|
479
|
+
assignNumber('maxPixelDelta');
|
|
480
|
+
assignNumber('mismatchPixelCount');
|
|
481
|
+
assignNumber('mismatchPercentage');
|
|
482
|
+
return result;
|
|
483
|
+
}
|
|
484
|
+
function isImageMimeType(value) {
|
|
485
|
+
return typeof value === 'string' && value.startsWith('image/');
|
|
486
|
+
}
|
|
487
|
+
function inferAutomationArtifactCaptureType(name, relativePath) {
|
|
488
|
+
const lower = `${name} ${relativePath ?? ''}`.toLowerCase();
|
|
489
|
+
if (lower.includes('diff')) {
|
|
490
|
+
return 'automation_diff';
|
|
491
|
+
}
|
|
492
|
+
if (lower.includes('screenshot') || lower.includes('capture')) {
|
|
493
|
+
return 'automation_screenshot';
|
|
494
|
+
}
|
|
495
|
+
return 'automation_image_artifact';
|
|
496
|
+
}
|
|
497
|
+
function normalizeAutomationVerificationArtifacts(payload) {
|
|
498
|
+
const createdAt = firstDefinedString(payload.completedAt, payload.startedAt) ?? '';
|
|
499
|
+
const runId = firstDefinedString(payload.runId) ?? 'automation';
|
|
500
|
+
const automationFilter = firstDefinedString(payload.automationFilter) ?? runId;
|
|
501
|
+
const target = firstDefinedString(payload.target);
|
|
502
|
+
const projectDir = firstDefinedString(payload.projectDir);
|
|
503
|
+
const existing = Array.isArray(payload.verificationArtifacts)
|
|
504
|
+
? payload.verificationArtifacts.filter(isRecord)
|
|
505
|
+
: [];
|
|
506
|
+
if (existing.length > 0) {
|
|
507
|
+
return existing.map((artifact) => {
|
|
508
|
+
const normalized = normalizeVerificationArtifact({
|
|
509
|
+
...artifact,
|
|
510
|
+
surface: isVerificationSurface(artifact.surface) ? artifact.surface : 'pie_runtime',
|
|
511
|
+
captureType: typeof artifact.captureType === 'string' && artifact.captureType.length > 0
|
|
512
|
+
? artifact.captureType
|
|
513
|
+
: 'automation_image_artifact',
|
|
514
|
+
createdAt: typeof artifact.createdAt === 'string' && artifact.createdAt.length > 0
|
|
515
|
+
? artifact.createdAt
|
|
516
|
+
: createdAt,
|
|
517
|
+
});
|
|
518
|
+
return {
|
|
519
|
+
...normalized,
|
|
520
|
+
resourceUri: typeof artifact.resourceUri === 'string' ? artifact.resourceUri : '',
|
|
521
|
+
...(typeof artifact.mimeType === 'string' ? { mimeType: artifact.mimeType } : {}),
|
|
522
|
+
...(typeof artifact.relativePath === 'string' ? { relativePath: artifact.relativePath } : {}),
|
|
523
|
+
};
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
const artifacts = Array.isArray(payload.artifacts)
|
|
527
|
+
? payload.artifacts.filter(isRecord)
|
|
528
|
+
: [];
|
|
529
|
+
return artifacts
|
|
530
|
+
.filter((artifact) => isImageMimeType(artifact.mimeType) && typeof artifact.path === 'string')
|
|
531
|
+
.map((artifact, index) => {
|
|
532
|
+
const relativePath = typeof artifact.relativePath === 'string' ? artifact.relativePath : undefined;
|
|
533
|
+
const name = typeof artifact.name === 'string' && artifact.name.length > 0
|
|
534
|
+
? artifact.name
|
|
535
|
+
: `automation_artifact_${index}`;
|
|
536
|
+
const normalized = normalizeVerificationArtifact({
|
|
537
|
+
captureId: `${runId}:${name}`,
|
|
538
|
+
captureType: inferAutomationArtifactCaptureType(name, relativePath),
|
|
539
|
+
surface: 'pie_runtime',
|
|
540
|
+
scenarioId: `automation:${automationFilter}:${name}`,
|
|
541
|
+
assetPath: '',
|
|
542
|
+
assetPaths: [],
|
|
543
|
+
artifactPath: artifact.path,
|
|
544
|
+
createdAt,
|
|
545
|
+
worldContext: {
|
|
546
|
+
contextType: 'automation_run',
|
|
547
|
+
runId,
|
|
548
|
+
automationFilter,
|
|
549
|
+
...(target ? { target } : {}),
|
|
550
|
+
...(projectDir ? { projectDir } : {}),
|
|
551
|
+
...(typeof payload.nullRhi === 'boolean' ? { nullRhi: payload.nullRhi } : {}),
|
|
552
|
+
reportArtifactName: name,
|
|
553
|
+
...(relativePath ? { relativePath } : {}),
|
|
554
|
+
},
|
|
555
|
+
cameraContext: {
|
|
556
|
+
contextType: 'automation_report_artifact',
|
|
557
|
+
captureLane: 'automation',
|
|
558
|
+
...(relativePath ? { relativePath } : {}),
|
|
559
|
+
},
|
|
560
|
+
...(projectDir ? { projectDir } : {}),
|
|
561
|
+
});
|
|
562
|
+
return {
|
|
563
|
+
...normalized,
|
|
564
|
+
resourceUri: typeof artifact.resourceUri === 'string' ? artifact.resourceUri : '',
|
|
565
|
+
mimeType: artifact.mimeType,
|
|
566
|
+
...(relativePath ? { relativePath } : {}),
|
|
567
|
+
};
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
function normalizeAutomationRunResult(payload) {
|
|
571
|
+
const basePayload = isRecord(payload) ? { ...payload } : { data: payload };
|
|
572
|
+
return {
|
|
573
|
+
...basePayload,
|
|
574
|
+
verificationArtifacts: normalizeAutomationVerificationArtifacts(basePayload),
|
|
575
|
+
};
|
|
576
|
+
}
|
|
318
577
|
function tryParseJsonText(text) {
|
|
319
578
|
if (!text) {
|
|
320
579
|
return undefined;
|
|
@@ -528,10 +787,32 @@ export function createBlueprintExtractorServer(client = new UEClient(), projectC
|
|
|
528
787
|
output_directory: z.string(),
|
|
529
788
|
manifest: z.array(cascadeManifestEntrySchema),
|
|
530
789
|
}).passthrough();
|
|
531
|
-
const
|
|
790
|
+
const verificationSurfaceSchema = z.enum([
|
|
791
|
+
'editor_offscreen',
|
|
792
|
+
'pie_runtime',
|
|
793
|
+
'editor_tool_viewport',
|
|
794
|
+
'external_packaged',
|
|
795
|
+
]);
|
|
796
|
+
const verificationContextSchema = z.record(z.string(), z.unknown());
|
|
797
|
+
const verificationComparisonSchema = z.object({
|
|
798
|
+
capturePath: z.string().optional(),
|
|
799
|
+
referencePath: z.string().optional(),
|
|
800
|
+
tolerance: z.number().min(0).optional(),
|
|
801
|
+
pass: z.boolean().optional(),
|
|
802
|
+
rmse: z.number().min(0).optional(),
|
|
803
|
+
maxPixelDelta: z.number().int().min(0).optional(),
|
|
804
|
+
mismatchPixelCount: z.number().int().min(0).optional(),
|
|
805
|
+
mismatchPercentage: z.number().min(0).optional(),
|
|
806
|
+
diffCaptureId: z.string().optional(),
|
|
807
|
+
diffArtifactPath: z.string().optional(),
|
|
808
|
+
}).passthrough();
|
|
809
|
+
const verificationArtifactSchema = z.object({
|
|
532
810
|
captureId: z.string(),
|
|
533
|
-
captureType: z.
|
|
811
|
+
captureType: z.string().min(1),
|
|
812
|
+
surface: verificationSurfaceSchema,
|
|
813
|
+
scenarioId: z.string(),
|
|
534
814
|
assetPath: z.string(),
|
|
815
|
+
assetPaths: z.array(z.string()),
|
|
535
816
|
widgetClass: z.string().optional(),
|
|
536
817
|
captureDirectory: z.string(),
|
|
537
818
|
artifactPath: z.string(),
|
|
@@ -540,11 +821,37 @@ export function createBlueprintExtractorServer(client = new UEClient(), projectC
|
|
|
540
821
|
height: z.number().int().min(1),
|
|
541
822
|
fileSizeBytes: z.number().int().min(0),
|
|
542
823
|
createdAt: z.string(),
|
|
824
|
+
worldContext: verificationContextSchema.optional(),
|
|
825
|
+
cameraContext: verificationContextSchema.optional(),
|
|
826
|
+
comparison: verificationComparisonSchema.optional(),
|
|
543
827
|
projectDir: z.string().optional(),
|
|
544
828
|
}).passthrough();
|
|
829
|
+
const verificationArtifactReferenceSchema = z.object({
|
|
830
|
+
captureId: z.string(),
|
|
831
|
+
captureType: z.string().min(1),
|
|
832
|
+
surface: verificationSurfaceSchema,
|
|
833
|
+
scenarioId: z.string(),
|
|
834
|
+
assetPath: z.string().optional(),
|
|
835
|
+
assetPaths: z.array(z.string()),
|
|
836
|
+
artifactPath: z.string(),
|
|
837
|
+
resourceUri: z.string(),
|
|
838
|
+
createdAt: z.string(),
|
|
839
|
+
metadataPath: z.string().optional(),
|
|
840
|
+
captureDirectory: z.string().optional(),
|
|
841
|
+
width: z.number().int().min(1).optional(),
|
|
842
|
+
height: z.number().int().min(1).optional(),
|
|
843
|
+
fileSizeBytes: z.number().int().min(0).optional(),
|
|
844
|
+
widgetClass: z.string().optional(),
|
|
845
|
+
worldContext: verificationContextSchema.optional(),
|
|
846
|
+
cameraContext: verificationContextSchema.optional(),
|
|
847
|
+
comparison: verificationComparisonSchema.optional(),
|
|
848
|
+
projectDir: z.string().optional(),
|
|
849
|
+
mimeType: z.string().optional(),
|
|
850
|
+
relativePath: z.string().optional(),
|
|
851
|
+
}).passthrough();
|
|
545
852
|
const CaptureResultSchema = v2ToolResultSchema.extend({
|
|
546
853
|
resourceUri: z.string(),
|
|
547
|
-
}).merge(
|
|
854
|
+
}).merge(verificationArtifactSchema).passthrough();
|
|
548
855
|
const CompareCaptureResultSchema = v2ToolResultSchema.extend({
|
|
549
856
|
capturePath: z.string(),
|
|
550
857
|
referencePath: z.string(),
|
|
@@ -557,11 +864,12 @@ export function createBlueprintExtractorServer(client = new UEClient(), projectC
|
|
|
557
864
|
diffCaptureId: z.string(),
|
|
558
865
|
diffArtifactPath: z.string(),
|
|
559
866
|
diffResourceUri: z.string(),
|
|
867
|
+
comparison: verificationComparisonSchema,
|
|
560
868
|
}).passthrough();
|
|
561
869
|
const ListCapturesResultSchema = v2ToolResultSchema.extend({
|
|
562
870
|
assetPathFilter: z.string(),
|
|
563
871
|
captureCount: z.number().int().min(0),
|
|
564
|
-
captures: z.array(
|
|
872
|
+
captures: z.array(verificationArtifactSchema),
|
|
565
873
|
}).passthrough();
|
|
566
874
|
const CleanupCapturesResultSchema = v2ToolResultSchema.extend({
|
|
567
875
|
deletedCount: z.number().int().min(0),
|
|
@@ -606,6 +914,7 @@ export function createBlueprintExtractorServer(client = new UEClient(), projectC
|
|
|
606
914
|
timeoutMs: z.number().int().positive(),
|
|
607
915
|
nullRhi: z.boolean(),
|
|
608
916
|
artifacts: z.array(automationArtifactSchema),
|
|
917
|
+
verificationArtifacts: z.array(verificationArtifactReferenceSchema).optional(),
|
|
609
918
|
summary: automationRunSummarySchema.optional(),
|
|
610
919
|
}).passthrough();
|
|
611
920
|
const AutomationRunListSchema = v2ToolResultSchema.extend({
|
|
@@ -695,7 +1004,7 @@ export function createBlueprintExtractorServer(client = new UEClient(), projectC
|
|
|
695
1004
|
'- modify_widget supports direct widget_name or widget_path patches for one widget.',
|
|
696
1005
|
'- modify_widget_blueprint is the primary structural API: replace_tree, patch_widget, patch_class_defaults, insert_child, remove_widget, move_widget, wrap_widget, replace_widget_class, batch, or compile.',
|
|
697
1006
|
'- compile_widget_blueprint validates the asset but still does not save it.',
|
|
698
|
-
'- apply_window_ui_changes is a thin MCP helper that sequences variable-flag updates, class defaults, optional font work, compile
|
|
1007
|
+
'- apply_window_ui_changes is a thin MCP helper that sequences variable-flag updates, class defaults, optional font work, compile, optional save, and optional code sync.',
|
|
699
1008
|
'',
|
|
700
1009
|
'Explicit deferrals:',
|
|
701
1010
|
'- Blueprint graph authoring is explicit and opt-in via modify_blueprint_graphs; generic arbitrary graph synthesis is still intentionally bounded to targeted graph operations.',
|
|
@@ -762,7 +1071,8 @@ export function createBlueprintExtractorServer(client = new UEClient(), projectC
|
|
|
762
1071
|
'- Use the narrowest mutation tool that fits: patch one widget/member first, replace whole trees only when structure is changing broadly.',
|
|
763
1072
|
'- Keep payloads small by sending only changed fields, not full extracted objects, unless the tool explicitly expects a full replacement payload.',
|
|
764
1073
|
'- Re-extract after mutation when you need confirmation; do not assume UE normalized fields exactly as sent.',
|
|
765
|
-
'- For multi-step widget work, prefer extract_widget_blueprint -> modify_widget_blueprint -> compile_widget_blueprint -> save_assets.',
|
|
1074
|
+
'- For multi-step widget work, prefer extract_widget_blueprint -> modify_widget_blueprint -> compile_widget_blueprint -> capture_widget_preview -> save_assets.',
|
|
1075
|
+
'- If widget preview capture is blocked, report partial verification explicitly instead of treating compile/save as visual proof.',
|
|
766
1076
|
'- For code orchestration, pass explicit changed_paths to sync_project_code instead of relying on source-control inference.',
|
|
767
1077
|
].join('\n'),
|
|
768
1078
|
}],
|
|
@@ -801,6 +1111,11 @@ export function createBlueprintExtractorServer(client = new UEClient(), projectC
|
|
|
801
1111
|
'- Prefer CommonActivatableWidget as the root parent for CommonUI screens and windows.',
|
|
802
1112
|
'- Prefer VerticalBox, HorizontalBox, Overlay, Border, SizeBox, ScrollBox, and NamedSlot over CanvasPanel unless absolute positioning is required.',
|
|
803
1113
|
'- Keep styling centralized. Reuse fonts, colors, and button classes instead of repeating large inline property blobs.',
|
|
1114
|
+
'- CommonButtonBase is not a raw UButton surface. Do not target UButton background/style fields through patch_class_defaults or patch_widget on CommonUI wrappers; use a CommonUI style asset or a project-owned material-backed button base.',
|
|
1115
|
+
'- When extract_widget_blueprint reports rootWidget=null, inspect widgetTreeStatus, widgetTreeError, and compile.errors before attempting structural rewrites. A degraded snapshot is still useful for recovery.',
|
|
1116
|
+
'- WidthOverride, HeightOverride, MinDesiredHeight, and similar fields may require paired bOverride_* flags. The write path now enables those flags automatically, but re-extract after patching to confirm the authored value landed where expected.',
|
|
1117
|
+
'- For long multi-step UI flows, use apply_window_ui_changes.checkpoint_after_mutation_steps=true so later compile/debug interruptions do not discard earlier successful edits.',
|
|
1118
|
+
'- For user-facing widget changes, compile/save is not the terminal checkpoint. Finish with capture_widget_preview or return partial verification with the blocking reason.',
|
|
804
1119
|
'- Prefer event-driven updates and explicit setter functions over heavy property bindings or per-frame tick work.',
|
|
805
1120
|
'- Use TSubclassOf + CreateWidget only for truly dynamic repeated elements. Keep authored static layout in the Widget Blueprint tree.',
|
|
806
1121
|
].join('\n'),
|
|
@@ -880,9 +1195,11 @@ export function createBlueprintExtractorServer(client = new UEClient(), projectC
|
|
|
880
1195
|
'- get_project_automation_context returns the editor-derived engine root, project file path, and editor target that project-control tools use as their first fallback.',
|
|
881
1196
|
'- compile_project_code runs an external UBT build from the MCP host.',
|
|
882
1197
|
'- compile_project_code and sync_project_code resolve engine_root, project_path, and target in this order: explicit args -> editor context -> environment.',
|
|
883
|
-
'- trigger_live_coding requests an editor-side Live Coding compile and is only supported on Windows-focused setups. changed_paths remains an accepted compatibility input but the current editor-side trigger ignores it.',
|
|
1198
|
+
'- trigger_live_coding requests an editor-side Live Coding compile and is only supported on Windows-focused setups. changed_paths remains an accepted compatibility input but the current editor-side trigger ignores it. When Live Coding reports NoChanges or another fallback state, the result includes fallbackRecommended, reason, and the last external build context when available.',
|
|
884
1199
|
'- restart_editor requests an editor restart, then waits for Remote Control to disconnect and reconnect. When save_dirty_assets is true, all dirty packages are saved before the restart to prevent modal save dialogs.',
|
|
885
1200
|
'- sync_project_code requires explicit changed_paths and chooses Live Coding vs build_and_restart deterministically.',
|
|
1201
|
+
'- sync_project_code.restart_first now means shutdown-first: save/checkpoint if requested, ask the editor to close without relaunching, build with the DLL unlocked, then launch the editor from the MCP host and wait for reconnect.',
|
|
1202
|
+
'- apply_window_ui_changes can checkpoint after each mutation step without changing the low-level explicit-save contract. Use that when debugging editor ensures or breakpoint-heavy UI iterations.',
|
|
886
1203
|
'',
|
|
887
1204
|
'build_and_restart is forced for:',
|
|
888
1205
|
'- .h/.hpp/.inl/.generated.h changes',
|
|
@@ -914,6 +1231,7 @@ export function createBlueprintExtractorServer(client = new UEClient(), projectC
|
|
|
914
1231
|
'- Gameplay mechanics, scripted interactions, runtime flows: automation tests first.',
|
|
915
1232
|
'',
|
|
916
1233
|
'Guardrail:',
|
|
1234
|
+
'- User-facing widget work is not complete until capture_widget_preview succeeds or the response explicitly reports partial verification with a blocking reason.',
|
|
917
1235
|
'- If no Automation Spec or Functional Test exists for a gameplay mechanic, report verification as partial instead of inferring success from structure or screenshots alone.',
|
|
918
1236
|
].join('\n'),
|
|
919
1237
|
}],
|
|
@@ -1019,6 +1337,21 @@ export function createBlueprintExtractorServer(client = new UEClient(), projectC
|
|
|
1019
1337
|
'Common BindWidget names:',
|
|
1020
1338
|
'- TitleText, SubtitleText, PrimaryButton, SecondaryButton',
|
|
1021
1339
|
],
|
|
1340
|
+
material_button_base: [
|
|
1341
|
+
'Pattern: material_button_base',
|
|
1342
|
+
'',
|
|
1343
|
+
'Parent class:',
|
|
1344
|
+
'- Project-owned CommonButtonBase subclass or project-owned UserWidget wrapper',
|
|
1345
|
+
'',
|
|
1346
|
+
'Recommended hierarchy:',
|
|
1347
|
+
'- Overlay Root',
|
|
1348
|
+
'- Image or Border MaterialPlate',
|
|
1349
|
+
'- NamedSlot or Overlay Content',
|
|
1350
|
+
'- Optional SizeBox HitTarget',
|
|
1351
|
+
'',
|
|
1352
|
+
'Use this when the project wants button visuals driven by a material-backed plate instead of raw UButton style fields.',
|
|
1353
|
+
'Keep hover, pressed, and disabled visuals centralized in the material instance or style asset, then expose only the project-owned tuning knobs through class defaults.',
|
|
1354
|
+
],
|
|
1022
1355
|
};
|
|
1023
1356
|
server.resource('examples', new ResourceTemplate('blueprint://examples/{family}', {
|
|
1024
1357
|
list: async () => ({
|
|
@@ -1097,12 +1430,14 @@ export function createBlueprintExtractorServer(client = new UEClient(), projectC
|
|
|
1097
1430
|
server.resource('captures', new ResourceTemplate('blueprint://captures/{capture_id}', {
|
|
1098
1431
|
list: undefined,
|
|
1099
1432
|
}), {
|
|
1100
|
-
description: 'Read a
|
|
1433
|
+
description: 'Read a visual verification capture PNG by capture id.',
|
|
1101
1434
|
mimeType: 'image/png',
|
|
1102
1435
|
}, async (uri, variables) => {
|
|
1103
1436
|
const captureId = String(variables.capture_id ?? '');
|
|
1104
1437
|
const listed = await callSubsystemJson('ListCaptures', { AssetPathFilter: '' });
|
|
1105
|
-
const captures = Array.isArray(listed.captures)
|
|
1438
|
+
const captures = Array.isArray(listed.captures)
|
|
1439
|
+
? listed.captures.map((capture) => normalizeVerificationArtifact(capture))
|
|
1440
|
+
: [];
|
|
1106
1441
|
const capture = captures.find((candidate) => (isRecord(candidate)
|
|
1107
1442
|
&& typeof candidate.captureId === 'string'
|
|
1108
1443
|
&& candidate.captureId === captureId
|
|
@@ -1152,6 +1487,7 @@ export function createBlueprintExtractorServer(client = new UEClient(), projectC
|
|
|
1152
1487
|
'- Generic create_data_asset and modify_data_asset reject Enhanced Input asset classes. Use the dedicated InputAction/InputMappingContext tools instead.',
|
|
1153
1488
|
'- modify_material and modify_material_function remain available but are advanced escape hatches, not the primary authoring workflow.',
|
|
1154
1489
|
'- There is still no first-class Substrate graph DSL.',
|
|
1490
|
+
'- CommonUI wrapper widgets are not a backdoor into internal Slate/UButton background or style fields. For CommonButtonBase-family widgets, treat raw UButton background/style properties as unsupported and use CommonUI style assets or a project-owned material-backed button base.',
|
|
1155
1491
|
'- Raw authored animation track synthesis is out of scope. Animation authoring remains metadata-oriented.',
|
|
1156
1492
|
'- World editing and runtime actor manipulation are out of scope for this server.',
|
|
1157
1493
|
].join('\n'),
|
|
@@ -1170,7 +1506,9 @@ export function createBlueprintExtractorServer(client = new UEClient(), projectC
|
|
|
1170
1506
|
'2. Inspect class defaults, BindWidget names, and current activatable-window flow before replacing any widget tree.',
|
|
1171
1507
|
'3. Choose a preset layout pattern such as centered_overlay, common_menu_shell, activatable_window, or list_detail.',
|
|
1172
1508
|
'4. Apply the smallest modify_widget_blueprint patch possible. Only use build_widget_tree or replace_tree when broad structure must change.',
|
|
1173
|
-
'5. Compile immediately after structural changes,
|
|
1509
|
+
'5. Compile immediately after structural changes. If compile fails, inspect compile diagnostics and rerun the smallest recovery patch first.',
|
|
1510
|
+
'6. Run capture_widget_preview after the compile result is clean so the rendered result is visually confirmed.',
|
|
1511
|
+
'7. Save after capture succeeds, or report partial verification explicitly when the visual checkpoint is blocked.',
|
|
1174
1512
|
].join('\n'),
|
|
1175
1513
|
}],
|
|
1176
1514
|
}));
|
|
@@ -1274,7 +1612,62 @@ export function createBlueprintExtractorServer(client = new UEClient(), projectC
|
|
|
1274
1612
|
}
|
|
1275
1613
|
return null;
|
|
1276
1614
|
}
|
|
1277
|
-
|
|
1615
|
+
function rememberExternalBuild(result) {
|
|
1616
|
+
lastExternalBuildContext = {
|
|
1617
|
+
success: result.success === true,
|
|
1618
|
+
operation: result.operation,
|
|
1619
|
+
strategy: result.strategy,
|
|
1620
|
+
engineRoot: result.engineRoot,
|
|
1621
|
+
projectPath: result.projectPath,
|
|
1622
|
+
target: result.target,
|
|
1623
|
+
platform: result.platform,
|
|
1624
|
+
configuration: result.configuration,
|
|
1625
|
+
exitCode: result.exitCode,
|
|
1626
|
+
durationMs: result.durationMs,
|
|
1627
|
+
restartRequired: result.restartRequired,
|
|
1628
|
+
restartReasons: result.restartReasons,
|
|
1629
|
+
errorCategory: result.errorCategory,
|
|
1630
|
+
errorSummary: result.errorSummary,
|
|
1631
|
+
lockedFiles: result.lockedFiles,
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
function deriveLiveCodingFallbackReason(result) {
|
|
1635
|
+
const status = typeof result.status === 'string' ? result.status.toLowerCase() : '';
|
|
1636
|
+
const compileResult = typeof result.compileResult === 'string' ? result.compileResult.toLowerCase() : '';
|
|
1637
|
+
const existingReason = typeof result.reason === 'string' ? result.reason : undefined;
|
|
1638
|
+
if (compileResult === 'nochanges' || result.noOp === true) {
|
|
1639
|
+
return 'live_coding_reported_nochanges';
|
|
1640
|
+
}
|
|
1641
|
+
if (status === 'unsupported' || compileResult === 'unsupported') {
|
|
1642
|
+
return 'live_coding_unsupported';
|
|
1643
|
+
}
|
|
1644
|
+
if (status === 'unavailable' || compileResult === 'unavailable') {
|
|
1645
|
+
return 'live_coding_unavailable';
|
|
1646
|
+
}
|
|
1647
|
+
return existingReason;
|
|
1648
|
+
}
|
|
1649
|
+
function enrichLiveCodingResult(result, changedPaths = []) {
|
|
1650
|
+
const headerChanges = changedPaths.filter((path) => /\.(h|hpp|inl)$/i.test(path.replace(/\\/g, '/')));
|
|
1651
|
+
const warnings = Array.isArray(result.warnings)
|
|
1652
|
+
? [...result.warnings.filter((value) => typeof value === 'string')]
|
|
1653
|
+
: [];
|
|
1654
|
+
if (headerChanges.length > 0) {
|
|
1655
|
+
warnings.push('Live Coding cannot add, remove, or reorder UPROPERTYs or change class layouts. '
|
|
1656
|
+
+ 'Use compile_project_code + restart_editor for class layout changes.');
|
|
1657
|
+
}
|
|
1658
|
+
const fallbackRecommended = canFallbackFromLiveCoding(result);
|
|
1659
|
+
const reason = deriveLiveCodingFallbackReason(result);
|
|
1660
|
+
return {
|
|
1661
|
+
...result,
|
|
1662
|
+
fallbackRecommended,
|
|
1663
|
+
...(reason ? { reason } : {}),
|
|
1664
|
+
...(fallbackRecommended && lastExternalBuildContext ? { lastExternalBuild: lastExternalBuildContext } : {}),
|
|
1665
|
+
changedPathsAccepted: changedPaths,
|
|
1666
|
+
changedPathsAppliedByEditor: false,
|
|
1667
|
+
headerChangesDetected: headerChanges,
|
|
1668
|
+
warnings,
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1278
1671
|
function firstDefinedString(...values) {
|
|
1279
1672
|
for (const value of values) {
|
|
1280
1673
|
if (typeof value === 'string' && value.length > 0) {
|
|
@@ -2594,7 +2987,7 @@ RETURNS: JSON array of objects with path, name, and class for each asset (and su
|
|
|
2594
2987
|
});
|
|
2595
2988
|
server.registerTool('capture_widget_preview', {
|
|
2596
2989
|
title: 'Capture Widget Preview',
|
|
2597
|
-
description: 'Render a WidgetBlueprint offscreen, write a PNG capture under Saved/BlueprintExtractor/Captures, and return
|
|
2990
|
+
description: 'Render a WidgetBlueprint offscreen, write a PNG capture under Saved/BlueprintExtractor/Captures, and return a verification artifact plus visual artifacts.',
|
|
2598
2991
|
inputSchema: {
|
|
2599
2992
|
asset_path: z.string().describe('UE content path to the WidgetBlueprint to preview.'),
|
|
2600
2993
|
width: z.number().int().min(64).max(2048).default(512).describe('Requested capture width in pixels. The editor clamps to a safe range.'),
|
|
@@ -2615,18 +3008,19 @@ RETURNS: JSON array of objects with path, name, and class for each asset (and su
|
|
|
2615
3008
|
Width: width,
|
|
2616
3009
|
Height: height,
|
|
2617
3010
|
});
|
|
2618
|
-
const
|
|
3011
|
+
const artifact = normalizeVerificationArtifact(parsed);
|
|
3012
|
+
const captureId = typeof artifact.captureId === 'string' ? artifact.captureId : '';
|
|
2619
3013
|
const resourceUri = `blueprint://captures/${encodeURIComponent(captureId)}`;
|
|
2620
3014
|
const extraContent = [];
|
|
2621
3015
|
if (captureId) {
|
|
2622
3016
|
extraContent.push(buildResourceLinkContent(resourceUri, `Capture ${captureId}`, 'image/png', 'Rendered widget preview capture.'));
|
|
2623
3017
|
}
|
|
2624
|
-
const inlineImage = await maybeBuildInlineImageContent(
|
|
3018
|
+
const inlineImage = await maybeBuildInlineImageContent(typeof artifact.artifactPath === 'string' ? artifact.artifactPath : undefined);
|
|
2625
3019
|
if (inlineImage) {
|
|
2626
3020
|
extraContent.push(inlineImage);
|
|
2627
3021
|
}
|
|
2628
3022
|
return jsonToolSuccess({
|
|
2629
|
-
...
|
|
3023
|
+
...artifact,
|
|
2630
3024
|
resourceUri,
|
|
2631
3025
|
}, { extraContent });
|
|
2632
3026
|
}
|
|
@@ -2636,7 +3030,7 @@ RETURNS: JSON array of objects with path, name, and class for each asset (and su
|
|
|
2636
3030
|
});
|
|
2637
3031
|
server.registerTool('compare_capture_to_reference', {
|
|
2638
3032
|
title: 'Compare Capture To Reference',
|
|
2639
|
-
description: 'Compare a saved capture
|
|
3033
|
+
description: 'Compare a saved verification capture or PNG path against another capture or reference PNG and produce a diff verification artifact.',
|
|
2640
3034
|
inputSchema: {
|
|
2641
3035
|
capture: z.string().describe('Capture id or absolute PNG path for the actual result.'),
|
|
2642
3036
|
reference: z.string().describe('Capture id or absolute PNG path for the expected reference.'),
|
|
@@ -2657,6 +3051,7 @@ RETURNS: JSON array of objects with path, name, and class for each asset (and su
|
|
|
2657
3051
|
ReferenceIdOrPath: reference,
|
|
2658
3052
|
Tolerance: tolerance,
|
|
2659
3053
|
});
|
|
3054
|
+
const comparison = normalizeVerificationComparison(parsed);
|
|
2660
3055
|
const diffCaptureId = typeof parsed.diffCaptureId === 'string' ? parsed.diffCaptureId : '';
|
|
2661
3056
|
const diffResourceUri = `blueprint://captures/${encodeURIComponent(diffCaptureId)}`;
|
|
2662
3057
|
const extraContent = [];
|
|
@@ -2670,6 +3065,7 @@ RETURNS: JSON array of objects with path, name, and class for each asset (and su
|
|
|
2670
3065
|
return jsonToolSuccess({
|
|
2671
3066
|
...parsed,
|
|
2672
3067
|
diffResourceUri,
|
|
3068
|
+
comparison,
|
|
2673
3069
|
}, { extraContent });
|
|
2674
3070
|
}
|
|
2675
3071
|
catch (e) {
|
|
@@ -2678,7 +3074,7 @@ RETURNS: JSON array of objects with path, name, and class for each asset (and su
|
|
|
2678
3074
|
});
|
|
2679
3075
|
server.registerTool('list_captures', {
|
|
2680
3076
|
title: 'List Captures',
|
|
2681
|
-
description: 'List saved
|
|
3077
|
+
description: 'List saved visual verification captures recorded by the editor-side verification lane.',
|
|
2682
3078
|
inputSchema: {
|
|
2683
3079
|
asset_path_filter: z.string().default('').describe('Optional exact asset path filter for captures created from one asset.'),
|
|
2684
3080
|
},
|
|
@@ -2695,7 +3091,14 @@ RETURNS: JSON array of objects with path, name, and class for each asset (and su
|
|
|
2695
3091
|
const parsed = await callSubsystemJson('ListCaptures', {
|
|
2696
3092
|
AssetPathFilter: asset_path_filter,
|
|
2697
3093
|
});
|
|
2698
|
-
|
|
3094
|
+
const captures = Array.isArray(parsed.captures)
|
|
3095
|
+
? parsed.captures.map((capture) => normalizeVerificationArtifact(capture))
|
|
3096
|
+
: [];
|
|
3097
|
+
return jsonToolSuccess({
|
|
3098
|
+
...parsed,
|
|
3099
|
+
captureCount: captures.length,
|
|
3100
|
+
captures,
|
|
3101
|
+
});
|
|
2699
3102
|
}
|
|
2700
3103
|
catch (e) {
|
|
2701
3104
|
return jsonToolError(e);
|
|
@@ -3748,11 +4151,18 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
3748
4151
|
timeoutMs: timeout_seconds * 1000,
|
|
3749
4152
|
nullRhi: null_rhi,
|
|
3750
4153
|
});
|
|
3751
|
-
const
|
|
4154
|
+
const normalized = normalizeAutomationRunResult(parsed);
|
|
4155
|
+
const extraContent = (Array.isArray(normalized.artifacts) ? normalized.artifacts : [])
|
|
3752
4156
|
.slice(0, 6)
|
|
3753
|
-
.map((artifact) => buildResourceLinkContent(artifact.resourceUri, `Automation ${artifact.name}`, artifact.mimeType, artifact.relativePath
|
|
4157
|
+
.map((artifact) => buildResourceLinkContent(String(artifact.resourceUri ?? ''), `Automation ${String(artifact.name ?? 'artifact')}`, String(artifact.mimeType ?? 'application/octet-stream'), typeof artifact.relativePath === 'string' ? artifact.relativePath : String(artifact.path ?? '')));
|
|
4158
|
+
for (const artifact of (Array.isArray(normalized.verificationArtifacts) ? normalized.verificationArtifacts : []).slice(0, 2)) {
|
|
4159
|
+
const inlineImage = await maybeBuildInlineImageContent(typeof artifact.artifactPath === 'string' ? artifact.artifactPath : undefined);
|
|
4160
|
+
if (inlineImage) {
|
|
4161
|
+
extraContent.push(inlineImage);
|
|
4162
|
+
}
|
|
4163
|
+
}
|
|
3754
4164
|
return jsonToolSuccess({
|
|
3755
|
-
...
|
|
4165
|
+
...normalized,
|
|
3756
4166
|
inputResolution: {
|
|
3757
4167
|
engineRoot: resolved.sources.engineRoot,
|
|
3758
4168
|
projectPath: resolved.sources.projectPath,
|
|
@@ -3785,10 +4195,17 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
3785
4195
|
if (!parsed) {
|
|
3786
4196
|
return jsonToolError(new Error(`Automation test run '${run_id}' was not found.`));
|
|
3787
4197
|
}
|
|
3788
|
-
const
|
|
4198
|
+
const normalized = normalizeAutomationRunResult(parsed);
|
|
4199
|
+
const extraContent = (Array.isArray(normalized.artifacts) ? normalized.artifacts : [])
|
|
3789
4200
|
.slice(0, 6)
|
|
3790
|
-
.map((artifact) => buildResourceLinkContent(artifact.resourceUri, `Automation ${artifact.name}`, artifact.mimeType, artifact.relativePath
|
|
3791
|
-
|
|
4201
|
+
.map((artifact) => buildResourceLinkContent(String(artifact.resourceUri ?? ''), `Automation ${String(artifact.name ?? 'artifact')}`, String(artifact.mimeType ?? 'application/octet-stream'), typeof artifact.relativePath === 'string' ? artifact.relativePath : String(artifact.path ?? '')));
|
|
4202
|
+
for (const artifact of (Array.isArray(normalized.verificationArtifacts) ? normalized.verificationArtifacts : []).slice(0, 2)) {
|
|
4203
|
+
const inlineImage = await maybeBuildInlineImageContent(typeof artifact.artifactPath === 'string' ? artifact.artifactPath : undefined);
|
|
4204
|
+
if (inlineImage) {
|
|
4205
|
+
extraContent.push(inlineImage);
|
|
4206
|
+
}
|
|
4207
|
+
}
|
|
4208
|
+
return jsonToolSuccess(normalized, { extraContent });
|
|
3792
4209
|
}
|
|
3793
4210
|
catch (e) {
|
|
3794
4211
|
return jsonToolError(e);
|
|
@@ -3811,7 +4228,12 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
3811
4228
|
}, async ({ include_completed }) => {
|
|
3812
4229
|
try {
|
|
3813
4230
|
const parsed = await automationController.listAutomationTestRuns(include_completed);
|
|
3814
|
-
return jsonToolSuccess(
|
|
4231
|
+
return jsonToolSuccess({
|
|
4232
|
+
...parsed,
|
|
4233
|
+
runs: Array.isArray(parsed.runs)
|
|
4234
|
+
? parsed.runs.map((run) => normalizeAutomationRunResult(run))
|
|
4235
|
+
: [],
|
|
4236
|
+
});
|
|
3815
4237
|
}
|
|
3816
4238
|
catch (e) {
|
|
3817
4239
|
return jsonToolError(e);
|
|
@@ -3850,6 +4272,7 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
3850
4272
|
includeOutput: include_output,
|
|
3851
4273
|
clearUhtCache: clear_uht_cache,
|
|
3852
4274
|
});
|
|
4275
|
+
rememberExternalBuild(parsed);
|
|
3853
4276
|
return jsonToolSuccess({
|
|
3854
4277
|
...parsed,
|
|
3855
4278
|
inputResolution: {
|
|
@@ -3894,19 +4317,7 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
3894
4317
|
bEnableForSession: true,
|
|
3895
4318
|
bWaitForCompletion: wait_for_completion,
|
|
3896
4319
|
});
|
|
3897
|
-
|
|
3898
|
-
const warnings = [];
|
|
3899
|
-
if (headerChanges.length > 0) {
|
|
3900
|
-
warnings.push('Live Coding cannot add, remove, or reorder UPROPERTYs or change class layouts. '
|
|
3901
|
-
+ 'Use compile_project_code + restart_editor for class layout changes.');
|
|
3902
|
-
}
|
|
3903
|
-
return jsonToolSuccess({
|
|
3904
|
-
...parsed,
|
|
3905
|
-
changedPathsAccepted: changed_paths ?? [],
|
|
3906
|
-
changedPathsAppliedByEditor: false,
|
|
3907
|
-
headerChangesDetected: headerChanges,
|
|
3908
|
-
warnings,
|
|
3909
|
-
});
|
|
4320
|
+
return jsonToolSuccess(enrichLiveCodingResult(parsed, changed_paths ?? []));
|
|
3910
4321
|
}
|
|
3911
4322
|
catch (e) {
|
|
3912
4323
|
return jsonToolError(e);
|
|
@@ -3933,6 +4344,7 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
3933
4344
|
const restartRequest = await callSubsystemJson('RestartEditor', {
|
|
3934
4345
|
bWarn: false,
|
|
3935
4346
|
bSaveDirtyAssets: save_dirty_assets,
|
|
4347
|
+
bRelaunch: true,
|
|
3936
4348
|
});
|
|
3937
4349
|
cachedProjectAutomationContext = null;
|
|
3938
4350
|
if (!wait_for_reconnect || restartRequest.success === false) {
|
|
@@ -3976,7 +4388,7 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
3976
4388
|
reconnect_timeout_seconds: z.number().int().positive().default(180).describe('Maximum seconds to wait for Remote Control to return after the editor restarts.'),
|
|
3977
4389
|
include_output: z.boolean().default(false).describe('When true, include full build stdout and stderr in the result. Failure cases include output automatically.'),
|
|
3978
4390
|
clear_uht_cache: z.boolean().default(false).describe('When true, delete UHT cache files (.uhtpath, .uhtsettings) from Intermediate/ before building so that Unreal Header Tool regenerates headers for any new or changed UPROPERTYs.'),
|
|
3979
|
-
restart_first: z.boolean().default(false).describe('When true,
|
|
4391
|
+
restart_first: z.boolean().default(false).describe('When true, shut the editor down before building to release locked DLLs, then launch it from the MCP host after the build finishes. Use this when a previous build failed due to a locked DLL (errorCategory: locked_file).'),
|
|
3980
4392
|
},
|
|
3981
4393
|
annotations: {
|
|
3982
4394
|
title: 'Sync Project Code',
|
|
@@ -4010,10 +4422,10 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
4010
4422
|
};
|
|
4011
4423
|
}
|
|
4012
4424
|
else {
|
|
4013
|
-
const liveCoding = await callSubsystemJson('TriggerLiveCoding', {
|
|
4425
|
+
const liveCoding = enrichLiveCodingResult(await callSubsystemJson('TriggerLiveCoding', {
|
|
4014
4426
|
bEnableForSession: true,
|
|
4015
4427
|
bWaitForCompletion: true,
|
|
4016
|
-
});
|
|
4428
|
+
}), changed_paths);
|
|
4017
4429
|
if (!canFallbackFromLiveCoding(liveCoding)) {
|
|
4018
4430
|
return jsonToolSuccess({
|
|
4019
4431
|
success: liveCoding.success === true,
|
|
@@ -4043,6 +4455,7 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
4043
4455
|
const preRestart = await callSubsystemJson('RestartEditor', {
|
|
4044
4456
|
bWarn: false,
|
|
4045
4457
|
bSaveDirtyAssets: save_dirty_assets,
|
|
4458
|
+
bRelaunch: false,
|
|
4046
4459
|
});
|
|
4047
4460
|
cachedProjectAutomationContext = null;
|
|
4048
4461
|
structuredResult.preRestart = preRestart;
|
|
@@ -4050,14 +4463,15 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
4050
4463
|
structuredResult.strategy = 'restart_first';
|
|
4051
4464
|
return jsonToolSuccess(structuredResult);
|
|
4052
4465
|
}
|
|
4053
|
-
const
|
|
4466
|
+
const preDisconnect = await projectController.waitForEditorRestart(supportsConnectionProbe(client), {
|
|
4054
4467
|
disconnectTimeoutMs: disconnect_timeout_seconds * 1000,
|
|
4055
4468
|
reconnectTimeoutMs: reconnect_timeout_seconds * 1000,
|
|
4469
|
+
waitForReconnect: false,
|
|
4056
4470
|
});
|
|
4057
|
-
structuredResult.
|
|
4058
|
-
if (!
|
|
4059
|
-
|
|
4060
|
-
structuredResult
|
|
4471
|
+
structuredResult.preDisconnect = preDisconnect;
|
|
4472
|
+
if (!preDisconnect.success) {
|
|
4473
|
+
structuredResult.strategy = 'restart_first';
|
|
4474
|
+
return jsonToolSuccess(structuredResult);
|
|
4061
4475
|
}
|
|
4062
4476
|
}
|
|
4063
4477
|
const build = await projectController.compileProjectCode({
|
|
@@ -4070,6 +4484,7 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
4070
4484
|
includeOutput: include_output,
|
|
4071
4485
|
clearUhtCache: clear_uht_cache,
|
|
4072
4486
|
});
|
|
4487
|
+
rememberExternalBuild(build);
|
|
4073
4488
|
structuredResult.strategy = restart_first ? 'restart_first' : 'build_and_restart';
|
|
4074
4489
|
structuredResult.build = build;
|
|
4075
4490
|
if (!build.success) {
|
|
@@ -4084,26 +4499,42 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
4084
4499
|
return jsonToolSuccess(structuredResult);
|
|
4085
4500
|
}
|
|
4086
4501
|
}
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4502
|
+
let reconnect;
|
|
4503
|
+
if (restart_first) {
|
|
4504
|
+
const launch = await projectController.launchEditor({
|
|
4505
|
+
engineRoot: resolvedProjectInputs.engineRoot,
|
|
4506
|
+
projectPath: resolvedProjectInputs.projectPath,
|
|
4507
|
+
});
|
|
4508
|
+
cachedProjectAutomationContext = null;
|
|
4509
|
+
structuredResult.editorLaunch = launch;
|
|
4510
|
+
if (!launch.success) {
|
|
4511
|
+
return jsonToolSuccess(structuredResult);
|
|
4512
|
+
}
|
|
4513
|
+
reconnect = await projectController.waitForEditorRestart(supportsConnectionProbe(client), {
|
|
4514
|
+
disconnectTimeoutMs: disconnect_timeout_seconds * 1000,
|
|
4515
|
+
reconnectTimeoutMs: reconnect_timeout_seconds * 1000,
|
|
4516
|
+
waitForDisconnect: false,
|
|
4517
|
+
});
|
|
4518
|
+
}
|
|
4519
|
+
else {
|
|
4520
|
+
const restartRequest = await callSubsystemJson('RestartEditor', {
|
|
4092
4521
|
bWarn: false,
|
|
4093
|
-
bSaveDirtyAssets:
|
|
4522
|
+
bSaveDirtyAssets: save_dirty_assets,
|
|
4523
|
+
bRelaunch: true,
|
|
4524
|
+
});
|
|
4525
|
+
cachedProjectAutomationContext = null;
|
|
4526
|
+
structuredResult.restartRequest = restartRequest;
|
|
4527
|
+
structuredResult.restartRequestSaveDirtyAssetsAccepted = save_dirty_assets;
|
|
4528
|
+
if (restartRequest.success === false) {
|
|
4529
|
+
return jsonToolSuccess(structuredResult);
|
|
4530
|
+
}
|
|
4531
|
+
reconnect = await projectController.waitForEditorRestart(supportsConnectionProbe(client), {
|
|
4532
|
+
disconnectTimeoutMs: disconnect_timeout_seconds * 1000,
|
|
4533
|
+
reconnectTimeoutMs: reconnect_timeout_seconds * 1000,
|
|
4094
4534
|
});
|
|
4095
|
-
cachedProjectAutomationContext = null;
|
|
4096
|
-
structuredResult.restartRequest = restartRequest;
|
|
4097
|
-
structuredResult.restartRequestSaveDirtyAssetsAccepted = restart_first ? false : save_dirty_assets;
|
|
4098
|
-
if (restartRequest.success === false) {
|
|
4099
|
-
return jsonToolSuccess(structuredResult);
|
|
4100
4535
|
}
|
|
4101
|
-
const reconnect = await projectController.waitForEditorRestart(supportsConnectionProbe(client), {
|
|
4102
|
-
disconnectTimeoutMs: disconnect_timeout_seconds * 1000,
|
|
4103
|
-
reconnectTimeoutMs: reconnect_timeout_seconds * 1000,
|
|
4104
|
-
});
|
|
4105
4536
|
structuredResult.reconnect = reconnect;
|
|
4106
|
-
structuredResult.success = reconnect.success;
|
|
4537
|
+
structuredResult.success = reconnect.success === true;
|
|
4107
4538
|
return jsonToolSuccess(structuredResult);
|
|
4108
4539
|
}
|
|
4109
4540
|
catch (e) {
|
|
@@ -4113,7 +4544,8 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
4113
4544
|
});
|
|
4114
4545
|
server.registerTool('apply_window_ui_changes', {
|
|
4115
4546
|
title: 'Apply Window UI Changes',
|
|
4116
|
-
description: 'Thin helper that applies variable flags, class defaults, font work, compile
|
|
4547
|
+
description: 'Thin helper that applies variable flags, class defaults, font work, compile, optional save, and optional code sync in one ordered flow. It does not replace the final visual verification step.',
|
|
4548
|
+
outputSchema: applyWindowUiChangesResultSchema,
|
|
4117
4549
|
inputSchema: {
|
|
4118
4550
|
asset_path: z.string().describe('UE content path to the WidgetBlueprint to update.'),
|
|
4119
4551
|
variable_widgets: z.array(WidgetSelectorFieldsSchema.extend({
|
|
@@ -4129,7 +4561,8 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
4129
4561
|
}).optional().describe('Optional explicit-file-path font import payload passed through to ImportFonts.'),
|
|
4130
4562
|
font_applications: z.array(WindowFontApplicationSchema).optional().describe('Optional compact font applications passed through to ApplyWidgetFonts.'),
|
|
4131
4563
|
compile_after: z.boolean().default(true).describe('When true, compile the widget Blueprint after the requested mutations.'),
|
|
4132
|
-
save_after: z.boolean().default(
|
|
4564
|
+
save_after: z.boolean().default(false).describe('When true, save the widget asset and any explicit extra save paths after a successful compile. Leave false to keep visual verification ahead of final persistence.'),
|
|
4565
|
+
checkpoint_after_mutation_steps: z.boolean().default(false).describe('When true, save checkpoint assets after each successful mutation step so multi-step UI flows can recover from later interruptions.'),
|
|
4133
4566
|
save_asset_paths: z.array(z.string()).optional().describe('Optional extra asset paths to save with the widget asset.'),
|
|
4134
4567
|
sync_project_code: z.object({
|
|
4135
4568
|
changed_paths: z.array(z.string()).min(1),
|
|
@@ -4155,9 +4588,71 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
4155
4588
|
idempotentHint: false,
|
|
4156
4589
|
openWorldHint: false,
|
|
4157
4590
|
},
|
|
4158
|
-
}, async ({ asset_path, variable_widgets, class_defaults, font_import, font_applications, compile_after, save_after, save_asset_paths, sync_project_code, }) => {
|
|
4591
|
+
}, async ({ asset_path, variable_widgets, class_defaults, font_import, font_applications, compile_after, save_after, checkpoint_after_mutation_steps, save_asset_paths, sync_project_code, }) => {
|
|
4159
4592
|
try {
|
|
4160
4593
|
const steps = [];
|
|
4594
|
+
const buildVerification = () => {
|
|
4595
|
+
const status = compile_after ? 'unverified' : 'compile_pending';
|
|
4596
|
+
return {
|
|
4597
|
+
required: true,
|
|
4598
|
+
status,
|
|
4599
|
+
surface: 'editor_offscreen',
|
|
4600
|
+
recommendedTool: 'capture_widget_preview',
|
|
4601
|
+
partialAllowed: true,
|
|
4602
|
+
reason: compile_after
|
|
4603
|
+
? 'apply_window_ui_changes completed the mutation flow but did not perform the final rendered-widget verification step.'
|
|
4604
|
+
: 'apply_window_ui_changes completed the mutation flow without compiling the widget, so compile and visual verification are still pending.',
|
|
4605
|
+
};
|
|
4606
|
+
};
|
|
4607
|
+
const buildVerificationNextSteps = (status) => {
|
|
4608
|
+
if (status === 'compile_pending') {
|
|
4609
|
+
return [
|
|
4610
|
+
'Compile the widget blueprint or rerun apply_window_ui_changes with compile_after=true before visual verification.',
|
|
4611
|
+
`Run capture_widget_preview for ${asset_path} after the compile result is clean.`,
|
|
4612
|
+
'If preview capture is blocked, report partial verification explicitly with the blocking reason.',
|
|
4613
|
+
];
|
|
4614
|
+
}
|
|
4615
|
+
return [
|
|
4616
|
+
`Run capture_widget_preview for ${asset_path} to visually confirm the rendered widget before calling the change verified.`,
|
|
4617
|
+
'If preview capture is blocked, report partial verification explicitly with the blocking reason.',
|
|
4618
|
+
];
|
|
4619
|
+
};
|
|
4620
|
+
const collectCheckpointAssetPaths = (extraPaths = []) => {
|
|
4621
|
+
const assetPaths = new Set([asset_path]);
|
|
4622
|
+
for (const extraPath of save_asset_paths ?? []) {
|
|
4623
|
+
assetPaths.add(extraPath);
|
|
4624
|
+
}
|
|
4625
|
+
if (font_import?.font_asset_path) {
|
|
4626
|
+
assetPaths.add(font_import.font_asset_path);
|
|
4627
|
+
}
|
|
4628
|
+
for (const extraPath of extraPaths) {
|
|
4629
|
+
assetPaths.add(extraPath);
|
|
4630
|
+
}
|
|
4631
|
+
return Array.from(assetPaths);
|
|
4632
|
+
};
|
|
4633
|
+
const checkpointMutationStep = async (stepName, extraPaths = []) => {
|
|
4634
|
+
if (!checkpoint_after_mutation_steps) {
|
|
4635
|
+
return null;
|
|
4636
|
+
}
|
|
4637
|
+
const result = await callSubsystemJson('SaveAssets', {
|
|
4638
|
+
AssetPathsJson: JSON.stringify(collectCheckpointAssetPaths(extraPaths)),
|
|
4639
|
+
});
|
|
4640
|
+
steps.push({
|
|
4641
|
+
step: 'checkpoint_after_mutation_step',
|
|
4642
|
+
afterStep: stepName,
|
|
4643
|
+
result,
|
|
4644
|
+
});
|
|
4645
|
+
if (result.success === false) {
|
|
4646
|
+
return jsonToolSuccess({
|
|
4647
|
+
success: false,
|
|
4648
|
+
operation: 'apply_window_ui_changes',
|
|
4649
|
+
stoppedAt: 'checkpoint_after_mutation_step',
|
|
4650
|
+
failedAfterStep: stepName,
|
|
4651
|
+
steps,
|
|
4652
|
+
});
|
|
4653
|
+
}
|
|
4654
|
+
return null;
|
|
4655
|
+
};
|
|
4161
4656
|
for (const selector of variable_widgets) {
|
|
4162
4657
|
const widgetIdentifier = getWidgetIdentifier(selector.widget_name, selector.widget_path);
|
|
4163
4658
|
if (!widgetIdentifier) {
|
|
@@ -4184,6 +4679,10 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
4184
4679
|
steps,
|
|
4185
4680
|
});
|
|
4186
4681
|
}
|
|
4682
|
+
const checkpointResult = await checkpointMutationStep('mark_widget_variable');
|
|
4683
|
+
if (checkpointResult) {
|
|
4684
|
+
return checkpointResult;
|
|
4685
|
+
}
|
|
4187
4686
|
}
|
|
4188
4687
|
if (class_defaults) {
|
|
4189
4688
|
const result = await callSubsystemJson('ModifyWidgetBlueprintStructure', {
|
|
@@ -4204,6 +4703,10 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
4204
4703
|
steps,
|
|
4205
4704
|
});
|
|
4206
4705
|
}
|
|
4706
|
+
const checkpointResult = await checkpointMutationStep('patch_class_defaults');
|
|
4707
|
+
if (checkpointResult) {
|
|
4708
|
+
return checkpointResult;
|
|
4709
|
+
}
|
|
4207
4710
|
}
|
|
4208
4711
|
if (font_import) {
|
|
4209
4712
|
const result = await callSubsystemJson('ImportFonts', {
|
|
@@ -4222,6 +4725,15 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
4222
4725
|
steps,
|
|
4223
4726
|
});
|
|
4224
4727
|
}
|
|
4728
|
+
const importedAssetPaths = Array.isArray(result.importedObjects)
|
|
4729
|
+
? result.importedObjects
|
|
4730
|
+
.map((value) => (typeof value === 'object' && value !== null && typeof value.assetPath === 'string' ? value.assetPath : null))
|
|
4731
|
+
.filter((value) => value !== null)
|
|
4732
|
+
: [];
|
|
4733
|
+
const checkpointResult = await checkpointMutationStep('import_fonts', importedAssetPaths);
|
|
4734
|
+
if (checkpointResult) {
|
|
4735
|
+
return checkpointResult;
|
|
4736
|
+
}
|
|
4225
4737
|
}
|
|
4226
4738
|
if (font_applications && font_applications.length > 0) {
|
|
4227
4739
|
const result = await callSubsystemJson('ApplyWidgetFonts', {
|
|
@@ -4241,6 +4753,10 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
4241
4753
|
steps,
|
|
4242
4754
|
});
|
|
4243
4755
|
}
|
|
4756
|
+
const checkpointResult = await checkpointMutationStep('apply_widget_fonts');
|
|
4757
|
+
if (checkpointResult) {
|
|
4758
|
+
return checkpointResult;
|
|
4759
|
+
}
|
|
4244
4760
|
}
|
|
4245
4761
|
if (compile_after) {
|
|
4246
4762
|
const result = await callSubsystemJson('CompileWidgetBlueprint', {
|
|
@@ -4258,6 +4774,10 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
4258
4774
|
steps,
|
|
4259
4775
|
});
|
|
4260
4776
|
}
|
|
4777
|
+
const checkpointResult = await checkpointMutationStep('compile_widget_blueprint');
|
|
4778
|
+
if (checkpointResult) {
|
|
4779
|
+
return checkpointResult;
|
|
4780
|
+
}
|
|
4261
4781
|
}
|
|
4262
4782
|
if (save_after) {
|
|
4263
4783
|
const assetPaths = new Set([asset_path]);
|
|
@@ -4292,10 +4812,10 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
4292
4812
|
});
|
|
4293
4813
|
let needsBuildRestart = syncPlan.strategy === 'build_and_restart' || !projectController.liveCodingSupported;
|
|
4294
4814
|
if (syncPlan.strategy === 'live_coding' && projectController.liveCodingSupported) {
|
|
4295
|
-
const liveCoding = await callSubsystemJson('TriggerLiveCoding', {
|
|
4815
|
+
const liveCoding = enrichLiveCodingResult(await callSubsystemJson('TriggerLiveCoding', {
|
|
4296
4816
|
bEnableForSession: true,
|
|
4297
4817
|
bWaitForCompletion: true,
|
|
4298
|
-
});
|
|
4818
|
+
}), sync_project_code.changed_paths);
|
|
4299
4819
|
if (!canFallbackFromLiveCoding(liveCoding)) {
|
|
4300
4820
|
steps.push({
|
|
4301
4821
|
step: 'sync_project_code',
|
|
@@ -4327,19 +4847,21 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
4327
4847
|
const preRestart = await callSubsystemJson('RestartEditor', {
|
|
4328
4848
|
bWarn: false,
|
|
4329
4849
|
bSaveDirtyAssets: sync_project_code.save_dirty_assets ?? true,
|
|
4850
|
+
bRelaunch: false,
|
|
4330
4851
|
});
|
|
4331
4852
|
cachedProjectAutomationContext = null;
|
|
4332
|
-
const
|
|
4853
|
+
const preDisconnect = await projectController.waitForEditorRestart(supportsConnectionProbe(client), {
|
|
4333
4854
|
disconnectTimeoutMs: (sync_project_code.disconnect_timeout_seconds ?? 60) * 1000,
|
|
4334
4855
|
reconnectTimeoutMs: (sync_project_code.reconnect_timeout_seconds ?? 180) * 1000,
|
|
4856
|
+
waitForReconnect: false,
|
|
4335
4857
|
});
|
|
4336
4858
|
steps.push({
|
|
4337
4859
|
step: 'sync_project_code_pre_restart',
|
|
4338
4860
|
strategy: 'restart_first',
|
|
4339
4861
|
restartRequest: preRestart,
|
|
4340
|
-
|
|
4862
|
+
disconnect: preDisconnect,
|
|
4341
4863
|
});
|
|
4342
|
-
if (preRestart.success === false) {
|
|
4864
|
+
if (preRestart.success === false || !preDisconnect.success) {
|
|
4343
4865
|
return jsonToolSuccess({
|
|
4344
4866
|
success: false,
|
|
4345
4867
|
operation: 'apply_window_ui_changes',
|
|
@@ -4360,6 +4882,7 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
4360
4882
|
includeOutput: sync_project_code.include_output ?? false,
|
|
4361
4883
|
clearUhtCache: sync_project_code.clear_uht_cache ?? false,
|
|
4362
4884
|
});
|
|
4885
|
+
rememberExternalBuild(build);
|
|
4363
4886
|
steps.push({
|
|
4364
4887
|
step: 'compile_project_code',
|
|
4365
4888
|
result: build,
|
|
@@ -4378,36 +4901,68 @@ RETURNS: JSON with validation summary, diagnostics, and dirtyPackages. Changes a
|
|
|
4378
4901
|
steps,
|
|
4379
4902
|
});
|
|
4380
4903
|
}
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4904
|
+
if (useRestartFirst) {
|
|
4905
|
+
const editorLaunch = await projectController.launchEditor({
|
|
4906
|
+
engineRoot: resolvedProjectInputs.engineRoot,
|
|
4907
|
+
projectPath: resolvedProjectInputs.projectPath,
|
|
4908
|
+
});
|
|
4909
|
+
cachedProjectAutomationContext = null;
|
|
4910
|
+
const reconnect = await projectController.waitForEditorRestart(supportsConnectionProbe(client), {
|
|
4911
|
+
disconnectTimeoutMs: (sync_project_code.disconnect_timeout_seconds ?? 60) * 1000,
|
|
4912
|
+
reconnectTimeoutMs: (sync_project_code.reconnect_timeout_seconds ?? 180) * 1000,
|
|
4913
|
+
waitForDisconnect: false,
|
|
4914
|
+
});
|
|
4915
|
+
steps.push({
|
|
4916
|
+
step: 'sync_project_code',
|
|
4917
|
+
strategy: 'restart_first',
|
|
4918
|
+
editorLaunch,
|
|
4919
|
+
reconnect,
|
|
4920
|
+
});
|
|
4921
|
+
if (!editorLaunch.success || !reconnect.success) {
|
|
4922
|
+
return jsonToolSuccess({
|
|
4923
|
+
success: false,
|
|
4924
|
+
operation: 'apply_window_ui_changes',
|
|
4925
|
+
stoppedAt: 'sync_project_code',
|
|
4926
|
+
steps,
|
|
4927
|
+
});
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
else {
|
|
4931
|
+
const restartRequest = await callSubsystemJson('RestartEditor', {
|
|
4932
|
+
bWarn: false,
|
|
4933
|
+
bSaveDirtyAssets: sync_project_code.save_dirty_assets ?? true,
|
|
4934
|
+
bRelaunch: true,
|
|
4403
4935
|
});
|
|
4936
|
+
cachedProjectAutomationContext = null;
|
|
4937
|
+
const reconnect = await projectController.waitForEditorRestart(supportsConnectionProbe(client), {
|
|
4938
|
+
disconnectTimeoutMs: (sync_project_code.disconnect_timeout_seconds ?? 60) * 1000,
|
|
4939
|
+
reconnectTimeoutMs: (sync_project_code.reconnect_timeout_seconds ?? 180) * 1000,
|
|
4940
|
+
});
|
|
4941
|
+
steps.push({
|
|
4942
|
+
step: 'sync_project_code',
|
|
4943
|
+
strategy: 'build_and_restart',
|
|
4944
|
+
restartRequest,
|
|
4945
|
+
saveDirtyAssetsAccepted: sync_project_code.save_dirty_assets ?? true,
|
|
4946
|
+
reconnect,
|
|
4947
|
+
});
|
|
4948
|
+
if (restartRequest.success === false || !reconnect.success) {
|
|
4949
|
+
return jsonToolSuccess({
|
|
4950
|
+
success: false,
|
|
4951
|
+
operation: 'apply_window_ui_changes',
|
|
4952
|
+
stoppedAt: 'sync_project_code',
|
|
4953
|
+
steps,
|
|
4954
|
+
});
|
|
4955
|
+
}
|
|
4404
4956
|
}
|
|
4405
4957
|
}
|
|
4406
4958
|
}
|
|
4959
|
+
const verification = buildVerification();
|
|
4407
4960
|
return jsonToolSuccess({
|
|
4408
4961
|
success: true,
|
|
4409
4962
|
operation: 'apply_window_ui_changes',
|
|
4410
4963
|
steps,
|
|
4964
|
+
verification,
|
|
4965
|
+
next_steps: buildVerificationNextSteps(verification.status),
|
|
4411
4966
|
});
|
|
4412
4967
|
}
|
|
4413
4968
|
catch (e) {
|