blueprint-extractor-mcp 2.1.0 → 2.2.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/dist/automation-controller.d.ts +94 -0
- package/dist/automation-controller.js +355 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +550 -19
- package/dist/project-controller.d.ts +10 -0
- package/dist/project-controller.js +65 -0
- package/package.json +2 -2
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
export type AutomationRunStatus = 'queued' | 'running' | 'succeeded' | 'failed' | 'timed_out' | 'cancelled';
|
|
3
|
+
export interface AutomationArtifact {
|
|
4
|
+
name: string;
|
|
5
|
+
path: string;
|
|
6
|
+
mimeType: string;
|
|
7
|
+
resourceUri: string;
|
|
8
|
+
relativePath?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface AutomationRunSummary {
|
|
11
|
+
successful: boolean;
|
|
12
|
+
totalTests?: number;
|
|
13
|
+
passedTests?: number;
|
|
14
|
+
failedTests?: number;
|
|
15
|
+
skippedTests?: number;
|
|
16
|
+
warningCount?: number;
|
|
17
|
+
reportAvailable: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface AutomationRunResult {
|
|
20
|
+
success: boolean;
|
|
21
|
+
operation: 'run_automation_tests' | 'get_automation_test_run';
|
|
22
|
+
runId: string;
|
|
23
|
+
automationFilter: string;
|
|
24
|
+
status: AutomationRunStatus;
|
|
25
|
+
terminal: boolean;
|
|
26
|
+
engineRoot: string;
|
|
27
|
+
projectPath: string;
|
|
28
|
+
projectDir: string;
|
|
29
|
+
target?: string;
|
|
30
|
+
reportOutputDir: string;
|
|
31
|
+
command: {
|
|
32
|
+
executable: string;
|
|
33
|
+
args: string[];
|
|
34
|
+
};
|
|
35
|
+
diagnostics: string[];
|
|
36
|
+
startedAt?: string;
|
|
37
|
+
completedAt?: string;
|
|
38
|
+
durationMs?: number;
|
|
39
|
+
exitCode?: number;
|
|
40
|
+
timeoutMs: number;
|
|
41
|
+
nullRhi: boolean;
|
|
42
|
+
artifacts: AutomationArtifact[];
|
|
43
|
+
summary?: AutomationRunSummary;
|
|
44
|
+
}
|
|
45
|
+
export interface AutomationRunListResult {
|
|
46
|
+
success: boolean;
|
|
47
|
+
operation: 'list_automation_test_runs';
|
|
48
|
+
includeCompleted: boolean;
|
|
49
|
+
runCount: number;
|
|
50
|
+
runs: AutomationRunResult[];
|
|
51
|
+
}
|
|
52
|
+
export interface RunAutomationTestsRequest {
|
|
53
|
+
engineRoot: string;
|
|
54
|
+
projectPath: string;
|
|
55
|
+
target?: string;
|
|
56
|
+
automationFilter: string;
|
|
57
|
+
reportOutputDir?: string;
|
|
58
|
+
timeoutMs?: number;
|
|
59
|
+
nullRhi?: boolean;
|
|
60
|
+
}
|
|
61
|
+
export interface AutomationControllerLike {
|
|
62
|
+
runAutomationTests(request: RunAutomationTestsRequest): Promise<AutomationRunResult>;
|
|
63
|
+
getAutomationTestRun(runId: string): Promise<AutomationRunResult | null>;
|
|
64
|
+
listAutomationTestRuns(includeCompleted?: boolean): Promise<AutomationRunListResult>;
|
|
65
|
+
readAutomationArtifact(runId: string, artifactName: string): Promise<AutomationArtifactReadResult | null>;
|
|
66
|
+
}
|
|
67
|
+
export interface AutomationArtifactReadResult {
|
|
68
|
+
artifact: AutomationArtifact;
|
|
69
|
+
data: Buffer;
|
|
70
|
+
}
|
|
71
|
+
export interface AutomationControllerOptions {
|
|
72
|
+
env?: NodeJS.ProcessEnv;
|
|
73
|
+
platform?: NodeJS.Platform;
|
|
74
|
+
now?: () => Date;
|
|
75
|
+
spawnProcess?: typeof spawn;
|
|
76
|
+
resolveEditorCommand?: (engineRoot: string, platform: NodeJS.Platform) => Promise<string>;
|
|
77
|
+
}
|
|
78
|
+
export declare class AutomationController implements AutomationControllerLike {
|
|
79
|
+
private readonly env;
|
|
80
|
+
private readonly platform;
|
|
81
|
+
private readonly now;
|
|
82
|
+
private readonly spawnProcess;
|
|
83
|
+
private readonly resolveEditorCommandFn;
|
|
84
|
+
private readonly runs;
|
|
85
|
+
constructor(options?: AutomationControllerOptions);
|
|
86
|
+
runAutomationTests(request: RunAutomationTestsRequest): Promise<AutomationRunResult>;
|
|
87
|
+
getAutomationTestRun(runId: string): Promise<AutomationRunResult | null>;
|
|
88
|
+
listAutomationTestRuns(includeCompleted?: boolean): Promise<AutomationRunListResult>;
|
|
89
|
+
readAutomationArtifact(runId: string, artifactName: string): Promise<AutomationArtifactReadResult | null>;
|
|
90
|
+
private cloneRun;
|
|
91
|
+
private registerArtifact;
|
|
92
|
+
private collectReportArtifacts;
|
|
93
|
+
private buildRunSummary;
|
|
94
|
+
}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { access, mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { createWriteStream } from 'node:fs';
|
|
5
|
+
import { dirname, extname, join, relative, resolve } from 'node:path';
|
|
6
|
+
import { constants as fsConstants } from 'node:fs';
|
|
7
|
+
import { resolveCommandInvocation } from './project-controller.js';
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 60 * 60 * 1000;
|
|
9
|
+
function defaultNow() {
|
|
10
|
+
return new Date();
|
|
11
|
+
}
|
|
12
|
+
function sanitizeSegment(value) {
|
|
13
|
+
const sanitized = value
|
|
14
|
+
.toLowerCase()
|
|
15
|
+
.replace(/[^a-z0-9]+/gu, '_')
|
|
16
|
+
.replace(/^_+|_+$/gu, '');
|
|
17
|
+
return sanitized.length > 0 ? sanitized : 'automation';
|
|
18
|
+
}
|
|
19
|
+
function inferMimeType(filePath) {
|
|
20
|
+
switch (extname(filePath).toLowerCase()) {
|
|
21
|
+
case '.json':
|
|
22
|
+
return 'application/json';
|
|
23
|
+
case '.html':
|
|
24
|
+
case '.htm':
|
|
25
|
+
return 'text/html';
|
|
26
|
+
case '.png':
|
|
27
|
+
return 'image/png';
|
|
28
|
+
case '.jpg':
|
|
29
|
+
case '.jpeg':
|
|
30
|
+
return 'image/jpeg';
|
|
31
|
+
case '.txt':
|
|
32
|
+
case '.log':
|
|
33
|
+
return 'text/plain';
|
|
34
|
+
default:
|
|
35
|
+
return 'application/octet-stream';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function collectFilesRecursive(root) {
|
|
39
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
40
|
+
const files = await Promise.all(entries.map(async (entry) => {
|
|
41
|
+
const fullPath = join(root, entry.name);
|
|
42
|
+
if (entry.isDirectory()) {
|
|
43
|
+
return await collectFilesRecursive(fullPath);
|
|
44
|
+
}
|
|
45
|
+
return [fullPath];
|
|
46
|
+
}));
|
|
47
|
+
return files.flat();
|
|
48
|
+
}
|
|
49
|
+
function firstNumericRecord(value, keys) {
|
|
50
|
+
if (typeof value !== 'object' || value === null) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const record = value;
|
|
54
|
+
for (const key of keys) {
|
|
55
|
+
const candidate = record[key];
|
|
56
|
+
if (typeof candidate === 'number' && Number.isFinite(candidate)) {
|
|
57
|
+
return candidate;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const nested of Object.values(record)) {
|
|
61
|
+
const found = firstNumericRecord(nested, keys);
|
|
62
|
+
if (typeof found === 'number') {
|
|
63
|
+
return found;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
function buildSummaryFromReport(report, successful) {
|
|
69
|
+
return {
|
|
70
|
+
successful,
|
|
71
|
+
totalTests: firstNumericRecord(report, ['totalTests', 'total', 'numTotal']),
|
|
72
|
+
passedTests: firstNumericRecord(report, ['passedTests', 'succeeded', 'numSucceeded', 'passCount']),
|
|
73
|
+
failedTests: firstNumericRecord(report, ['failedTests', 'failed', 'numFailed', 'failCount']),
|
|
74
|
+
skippedTests: firstNumericRecord(report, ['skippedTests', 'skipped', 'numSkipped']),
|
|
75
|
+
warningCount: firstNumericRecord(report, ['warningCount', 'warnings', 'numWarnings']),
|
|
76
|
+
reportAvailable: true,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async function tryReadJson(filePath) {
|
|
80
|
+
try {
|
|
81
|
+
const raw = await readFile(filePath, 'utf8');
|
|
82
|
+
return JSON.parse(raw);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function buildResourceUri(runId, artifactName) {
|
|
89
|
+
return `blueprint://test-runs/${encodeURIComponent(runId)}/${encodeURIComponent(artifactName)}`;
|
|
90
|
+
}
|
|
91
|
+
async function resolveEditorCommand(engineRoot, platform) {
|
|
92
|
+
const executable = platform === 'win32'
|
|
93
|
+
? resolve(engineRoot, 'Engine', 'Binaries', 'Win64', 'UnrealEditor-Cmd.exe')
|
|
94
|
+
: platform === 'darwin'
|
|
95
|
+
? resolve(engineRoot, 'Engine', 'Binaries', 'Mac', 'UnrealEditor-Cmd')
|
|
96
|
+
: resolve(engineRoot, 'Engine', 'Binaries', 'Linux', 'UnrealEditor-Cmd');
|
|
97
|
+
await access(executable, fsConstants.F_OK);
|
|
98
|
+
return executable;
|
|
99
|
+
}
|
|
100
|
+
export class AutomationController {
|
|
101
|
+
env;
|
|
102
|
+
platform;
|
|
103
|
+
now;
|
|
104
|
+
spawnProcess;
|
|
105
|
+
resolveEditorCommandFn;
|
|
106
|
+
runs = new Map();
|
|
107
|
+
constructor(options = {}) {
|
|
108
|
+
this.env = options.env ?? process.env;
|
|
109
|
+
this.platform = options.platform ?? process.platform;
|
|
110
|
+
this.now = options.now ?? defaultNow;
|
|
111
|
+
this.spawnProcess = options.spawnProcess ?? spawn;
|
|
112
|
+
this.resolveEditorCommandFn = options.resolveEditorCommand ?? resolveEditorCommand;
|
|
113
|
+
}
|
|
114
|
+
async runAutomationTests(request) {
|
|
115
|
+
if (!request.engineRoot) {
|
|
116
|
+
throw new Error('run_automation_tests requires engine_root');
|
|
117
|
+
}
|
|
118
|
+
if (!request.projectPath) {
|
|
119
|
+
throw new Error('run_automation_tests requires project_path');
|
|
120
|
+
}
|
|
121
|
+
if (!request.automationFilter) {
|
|
122
|
+
throw new Error('run_automation_tests requires automation_filter');
|
|
123
|
+
}
|
|
124
|
+
const started = this.now();
|
|
125
|
+
const filterSlug = sanitizeSegment(request.automationFilter);
|
|
126
|
+
const runId = `${filterSlug}_${started.getTime()}_${randomUUID().slice(0, 8)}`;
|
|
127
|
+
const projectDir = dirname(request.projectPath);
|
|
128
|
+
const runRoot = request.reportOutputDir
|
|
129
|
+
? resolve(request.reportOutputDir)
|
|
130
|
+
: resolve(projectDir, 'Saved', 'BlueprintExtractor', 'AutomationRuns', runId);
|
|
131
|
+
const reportOutputDir = resolve(runRoot, 'reports');
|
|
132
|
+
const stdoutPath = resolve(runRoot, 'stdout.log');
|
|
133
|
+
const stderrPath = resolve(runRoot, 'stderr.log');
|
|
134
|
+
const summaryPath = resolve(runRoot, 'summary.json');
|
|
135
|
+
const timeoutMs = request.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
136
|
+
const nullRhi = request.nullRhi ?? true;
|
|
137
|
+
await mkdir(reportOutputDir, { recursive: true });
|
|
138
|
+
const editorCmd = await this.resolveEditorCommandFn(request.engineRoot, this.platform);
|
|
139
|
+
const args = [
|
|
140
|
+
request.projectPath,
|
|
141
|
+
'-unattended',
|
|
142
|
+
'-nop4',
|
|
143
|
+
'-nosplash',
|
|
144
|
+
...(nullRhi ? ['-NullRHI'] : []),
|
|
145
|
+
'-RCWebControlEnable',
|
|
146
|
+
'-RCWebInterfaceEnable',
|
|
147
|
+
`-ReportExportPath=${reportOutputDir}`,
|
|
148
|
+
`-ExecCmds=Automation RunTests ${request.automationFilter};Quit`,
|
|
149
|
+
];
|
|
150
|
+
const invocation = resolveCommandInvocation(editorCmd, args, this.platform, this.env);
|
|
151
|
+
const run = {
|
|
152
|
+
success: true,
|
|
153
|
+
operation: 'run_automation_tests',
|
|
154
|
+
runId,
|
|
155
|
+
automationFilter: request.automationFilter,
|
|
156
|
+
status: 'queued',
|
|
157
|
+
terminal: false,
|
|
158
|
+
engineRoot: request.engineRoot,
|
|
159
|
+
projectPath: request.projectPath,
|
|
160
|
+
projectDir,
|
|
161
|
+
target: request.target,
|
|
162
|
+
reportOutputDir,
|
|
163
|
+
command: {
|
|
164
|
+
executable: invocation.executable,
|
|
165
|
+
args: invocation.args,
|
|
166
|
+
},
|
|
167
|
+
diagnostics: [],
|
|
168
|
+
timeoutMs,
|
|
169
|
+
nullRhi,
|
|
170
|
+
artifacts: [],
|
|
171
|
+
artifactMap: new Map(),
|
|
172
|
+
};
|
|
173
|
+
this.runs.set(runId, run);
|
|
174
|
+
const stdoutStream = createWriteStream(stdoutPath, { encoding: 'utf8' });
|
|
175
|
+
const stderrStream = createWriteStream(stderrPath, { encoding: 'utf8' });
|
|
176
|
+
const child = this.spawnProcess(invocation.executable, invocation.args, {
|
|
177
|
+
cwd: projectDir,
|
|
178
|
+
env: this.env,
|
|
179
|
+
shell: false,
|
|
180
|
+
windowsVerbatimArguments: false,
|
|
181
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
182
|
+
});
|
|
183
|
+
run.status = 'running';
|
|
184
|
+
run.startedAt = started.toISOString();
|
|
185
|
+
child.stdout?.on('data', (chunk) => {
|
|
186
|
+
stdoutStream.write(chunk);
|
|
187
|
+
});
|
|
188
|
+
child.stderr?.on('data', (chunk) => {
|
|
189
|
+
stderrStream.write(chunk);
|
|
190
|
+
});
|
|
191
|
+
const finalize = async (status, exitCode, diagnostic) => {
|
|
192
|
+
if (run.terminal) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
run.status = status;
|
|
196
|
+
run.exitCode = exitCode;
|
|
197
|
+
run.completedAt = this.now().toISOString();
|
|
198
|
+
run.durationMs = new Date(run.completedAt).getTime() - started.getTime();
|
|
199
|
+
run.success = status !== 'failed' && status !== 'timed_out' && status !== 'cancelled';
|
|
200
|
+
if (diagnostic) {
|
|
201
|
+
run.diagnostics.push(diagnostic);
|
|
202
|
+
}
|
|
203
|
+
await Promise.all([
|
|
204
|
+
new Promise((resolveClose) => stdoutStream.end(resolveClose)),
|
|
205
|
+
new Promise((resolveClose) => stderrStream.end(resolveClose)),
|
|
206
|
+
]);
|
|
207
|
+
this.registerArtifact(run, 'stdout', stdoutPath, 'text/plain');
|
|
208
|
+
this.registerArtifact(run, 'stderr', stderrPath, 'text/plain');
|
|
209
|
+
const reportFiles = await this.collectReportArtifacts(runId, reportOutputDir);
|
|
210
|
+
for (const artifact of reportFiles) {
|
|
211
|
+
this.registerArtifact(run, artifact.name, artifact.path, artifact.mimeType, artifact.relativePath);
|
|
212
|
+
}
|
|
213
|
+
const summary = await this.buildRunSummary(run, reportOutputDir);
|
|
214
|
+
run.summary = summary;
|
|
215
|
+
await writeFile(summaryPath, JSON.stringify({
|
|
216
|
+
runId,
|
|
217
|
+
automationFilter: run.automationFilter,
|
|
218
|
+
status: run.status,
|
|
219
|
+
exitCode: run.exitCode,
|
|
220
|
+
durationMs: run.durationMs,
|
|
221
|
+
diagnostics: run.diagnostics,
|
|
222
|
+
reportOutputDir: run.reportOutputDir,
|
|
223
|
+
summary,
|
|
224
|
+
}, null, 2), 'utf8');
|
|
225
|
+
this.registerArtifact(run, 'summary', summaryPath, 'application/json');
|
|
226
|
+
run.terminal = true;
|
|
227
|
+
};
|
|
228
|
+
const timeoutHandle = setTimeout(() => {
|
|
229
|
+
if (!run.terminal) {
|
|
230
|
+
child.kill('SIGTERM');
|
|
231
|
+
}
|
|
232
|
+
}, timeoutMs);
|
|
233
|
+
child.on('error', async (error) => {
|
|
234
|
+
clearTimeout(timeoutHandle);
|
|
235
|
+
await finalize('failed', 1, error.message);
|
|
236
|
+
});
|
|
237
|
+
child.on('close', async (code, signal) => {
|
|
238
|
+
clearTimeout(timeoutHandle);
|
|
239
|
+
const status = signal === 'SIGTERM' && !run.terminal
|
|
240
|
+
? 'timed_out'
|
|
241
|
+
: (code ?? 1) === 0
|
|
242
|
+
? 'succeeded'
|
|
243
|
+
: 'failed';
|
|
244
|
+
const diagnostic = signal === 'SIGTERM' && status === 'timed_out'
|
|
245
|
+
? `Automation run exceeded timeout of ${timeoutMs}ms`
|
|
246
|
+
: undefined;
|
|
247
|
+
await finalize(status, code ?? 1, diagnostic);
|
|
248
|
+
});
|
|
249
|
+
return this.cloneRun(run, 'run_automation_tests');
|
|
250
|
+
}
|
|
251
|
+
async getAutomationTestRun(runId) {
|
|
252
|
+
const run = this.runs.get(runId);
|
|
253
|
+
return run ? this.cloneRun(run, 'get_automation_test_run') : null;
|
|
254
|
+
}
|
|
255
|
+
async listAutomationTestRuns(includeCompleted = true) {
|
|
256
|
+
const runs = Array.from(this.runs.values())
|
|
257
|
+
.filter((run) => includeCompleted || !run.terminal)
|
|
258
|
+
.sort((left, right) => {
|
|
259
|
+
const leftTime = left.startedAt ? Date.parse(left.startedAt) : 0;
|
|
260
|
+
const rightTime = right.startedAt ? Date.parse(right.startedAt) : 0;
|
|
261
|
+
return rightTime - leftTime;
|
|
262
|
+
})
|
|
263
|
+
.map((run) => this.cloneRun(run, 'get_automation_test_run'));
|
|
264
|
+
return {
|
|
265
|
+
success: true,
|
|
266
|
+
operation: 'list_automation_test_runs',
|
|
267
|
+
includeCompleted,
|
|
268
|
+
runCount: runs.length,
|
|
269
|
+
runs,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
async readAutomationArtifact(runId, artifactName) {
|
|
273
|
+
const run = this.runs.get(runId);
|
|
274
|
+
if (!run) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
const artifact = run.artifactMap.get(artifactName);
|
|
278
|
+
if (!artifact) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
const data = await readFile(artifact.path);
|
|
282
|
+
return {
|
|
283
|
+
artifact,
|
|
284
|
+
data,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
cloneRun(run, operation) {
|
|
288
|
+
return {
|
|
289
|
+
...run,
|
|
290
|
+
operation,
|
|
291
|
+
artifacts: [...run.artifacts],
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
registerArtifact(run, name, filePath, mimeType, relativePath) {
|
|
295
|
+
const existing = run.artifactMap.get(name);
|
|
296
|
+
const artifact = {
|
|
297
|
+
name,
|
|
298
|
+
path: filePath,
|
|
299
|
+
mimeType,
|
|
300
|
+
relativePath,
|
|
301
|
+
resourceUri: buildResourceUri(run.runId, name),
|
|
302
|
+
};
|
|
303
|
+
if (existing) {
|
|
304
|
+
const index = run.artifacts.findIndex((candidate) => candidate.name === name);
|
|
305
|
+
if (index >= 0) {
|
|
306
|
+
run.artifacts[index] = artifact;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
run.artifacts.push(artifact);
|
|
311
|
+
}
|
|
312
|
+
run.artifactMap.set(name, artifact);
|
|
313
|
+
}
|
|
314
|
+
async collectReportArtifacts(runId, reportOutputDir) {
|
|
315
|
+
try {
|
|
316
|
+
const files = await collectFilesRecursive(reportOutputDir);
|
|
317
|
+
return files.map((filePath) => {
|
|
318
|
+
const rel = relative(reportOutputDir, filePath).replaceAll('\\', '/');
|
|
319
|
+
const lowerRel = rel.toLowerCase();
|
|
320
|
+
const name = lowerRel === 'index.json'
|
|
321
|
+
? 'report'
|
|
322
|
+
: lowerRel === 'index.html'
|
|
323
|
+
? 'report_html'
|
|
324
|
+
: `report__${sanitizeSegment(rel.replaceAll('/', '__'))}`;
|
|
325
|
+
return {
|
|
326
|
+
name,
|
|
327
|
+
path: filePath,
|
|
328
|
+
mimeType: inferMimeType(filePath),
|
|
329
|
+
relativePath: rel,
|
|
330
|
+
resourceUri: buildResourceUri(runId, name),
|
|
331
|
+
};
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
return [];
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async buildRunSummary(run, reportOutputDir) {
|
|
339
|
+
const reportArtifact = run.artifactMap.get('report');
|
|
340
|
+
if (!reportArtifact) {
|
|
341
|
+
return {
|
|
342
|
+
successful: run.status === 'succeeded',
|
|
343
|
+
reportAvailable: false,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
const parsed = await tryReadJson(reportArtifact.path);
|
|
347
|
+
if (!parsed) {
|
|
348
|
+
return {
|
|
349
|
+
successful: run.status === 'succeeded',
|
|
350
|
+
reportAvailable: true,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
return buildSummaryFromReport(parsed, run.status === 'succeeded');
|
|
354
|
+
}
|
|
355
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { UEClient } from './ue-client.js';
|
|
5
5
|
import { type ProjectControllerLike } from './project-controller.js';
|
|
6
|
+
import { type AutomationControllerLike } from './automation-controller.js';
|
|
6
7
|
export type UEClientLike = Pick<UEClient, 'callSubsystem'> & Partial<Pick<UEClient, 'checkConnection'>>;
|
|
7
8
|
type ToolExample = {
|
|
8
9
|
title: string;
|
|
@@ -22,5 +23,5 @@ type PromptCatalogEntry = {
|
|
|
22
23
|
};
|
|
23
24
|
export declare const exampleCatalog: Record<string, ExampleFamily>;
|
|
24
25
|
export declare const promptCatalog: Record<string, PromptCatalogEntry>;
|
|
25
|
-
export declare function createBlueprintExtractorServer(client?: UEClientLike, projectController?: ProjectControllerLike): McpServer;
|
|
26
|
+
export declare function createBlueprintExtractorServer(client?: UEClientLike, projectController?: ProjectControllerLike, automationController?: AutomationControllerLike): McpServer;
|
|
26
27
|
export {};
|