flockbay 0.10.20 → 0.10.22
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/{index-CX0Z8pmz.mjs → index-BjZUYSzh.mjs} +471 -31
- package/dist/{index-D_mglYG0.cjs → index-DQTqwzYd.cjs} +471 -31
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.mjs +1 -1
- package/dist/{runCodex-Biis9GFw.mjs → runCodex-B7fGICdv.mjs} +11 -13
- package/dist/{runCodex-CXJW0tzo.cjs → runCodex-CaWagdzG.cjs} +11 -13
- package/dist/{runGemini-FOBXtEU6.cjs → runGemini-8w5P093W.cjs} +234 -49
- package/dist/{runGemini-BSH4b0wu.mjs → runGemini-CK43WQk8.mjs} +234 -49
- package/dist/{types-C4QeUggl.mjs → types-CMWcip0F.mjs} +41 -3
- package/dist/{types-BYHCKlu_.cjs → types-Z2OYpI8c.cjs} +41 -2
- package/package.json +1 -1
- package/scripts/claude_version_utils.cjs +66 -12
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPEditorCommands.cpp +32 -11
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/UnrealMCP.Build.cs +1 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import chalk from 'chalk';
|
|
2
2
|
import os, { homedir } from 'node:os';
|
|
3
3
|
import { randomUUID, createCipheriv, randomBytes } from 'node:crypto';
|
|
4
|
-
import { l as logger, p as projectPath, d as backoff, e as delay, R as RawJSONLinesSchema, c as configuration, f as readDaemonState, g as clearDaemonState, b as packageJson, r as readSettings, h as readCredentials, u as updateSettings, w as writeCredentials, i as unrealMcpPythonDir, j as acquireDaemonLock, k as writeDaemonState, m as ApiMachineClient, n as releaseDaemonLock, s as sendUnrealMcpTcpCommand, A as ApiClient, o as clearCredentials, q as clearMachineId, t as installUnrealMcpPluginToEngine, v as
|
|
4
|
+
import { l as logger, p as projectPath, d as backoff, e as delay, R as RawJSONLinesSchema, c as configuration, f as readDaemonState, g as clearDaemonState, b as packageJson, r as readSettings, h as readCredentials, u as updateSettings, w as writeCredentials, i as unrealMcpPythonDir, j as acquireDaemonLock, k as writeDaemonState, m as ApiMachineClient, n as releaseDaemonLock, s as sendUnrealMcpTcpCommand, A as ApiClient, o as clearCredentials, q as clearMachineId, t as installUnrealMcpPluginToEngine, v as buildAndInstallUnrealMcpPlugin, x as getLatestDaemonLog, y as normalizeServerUrlForNode } from './types-CMWcip0F.mjs';
|
|
5
5
|
import { spawn, execFileSync, execSync } from 'node:child_process';
|
|
6
6
|
import path, { resolve, join, dirname } from 'node:path';
|
|
7
7
|
import { createInterface } from 'node:readline';
|
|
8
8
|
import * as fs from 'node:fs';
|
|
9
9
|
import fs__default, { existsSync, readFileSync, mkdirSync, readdirSync, accessSync, constants, statSync, createReadStream, writeFileSync, unlinkSync } from 'node:fs';
|
|
10
10
|
import process$1 from 'node:process';
|
|
11
|
-
import fs$1, { readFile, access as access$1, mkdir, readdir, stat } from 'node:fs/promises';
|
|
11
|
+
import fs$1, { readFile, access as access$1, mkdir, readdir, stat, rename, open as open$1 } from 'node:fs/promises';
|
|
12
12
|
import fs$2, { watch, access } from 'fs/promises';
|
|
13
13
|
import { useStdout, useInput, Box, Text, render } from 'ink';
|
|
14
14
|
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
|
@@ -386,7 +386,7 @@ const PLATFORM_SYSTEM_PROMPT = trimIdent(`
|
|
|
386
386
|
|
|
387
387
|
# Policy blocks (not user rejections)
|
|
388
388
|
|
|
389
|
-
If a tool call is **blocked by
|
|
389
|
+
If a tool call is **blocked by Policy** (e.g. a \`FlockbayPolicy\` card, or a denial reason like \u201CBlocked by Policy \u2026\u201D), this is automatic enforcement by the platform \u2014 it is **not** the user rejecting your tool call. Follow the provided next-step instructions (read docs/ledger, claim files, etc) and then retry.
|
|
390
390
|
|
|
391
391
|
# Documentation Library (server-stored docs)
|
|
392
392
|
|
|
@@ -1236,7 +1236,8 @@ function buildDaemonSafeEnv(baseEnv, binPath) {
|
|
|
1236
1236
|
if (!p) return;
|
|
1237
1237
|
if (!prepend.includes(p) && !existingParts.includes(p)) prepend.push(p);
|
|
1238
1238
|
};
|
|
1239
|
-
|
|
1239
|
+
const isPathLike = typeof binPath === "string" && binPath.length > 0 && (binPath.includes("/") || binPath.includes("\\")) && !binPath.startsWith("\\\\");
|
|
1240
|
+
if (isPathLike) {
|
|
1240
1241
|
add(dirname(binPath));
|
|
1241
1242
|
}
|
|
1242
1243
|
add(dirname(process$1.execPath));
|
|
@@ -1255,7 +1256,7 @@ const __dirname$1 = join(__filename$1, "..");
|
|
|
1255
1256
|
function getGlobalClaudeVersion(claudeExecutable) {
|
|
1256
1257
|
try {
|
|
1257
1258
|
const cleanEnv = buildDaemonSafeEnv(getCleanEnv(), claudeExecutable);
|
|
1258
|
-
const output = claudeExecutable.includes("/") && !claudeExecutable.startsWith("\\\\") ? execFileSync(claudeExecutable, ["--version"], {
|
|
1259
|
+
const output = (claudeExecutable.includes("/") || claudeExecutable.includes("\\")) && !claudeExecutable.startsWith("\\\\") ? execFileSync(claudeExecutable, ["--version"], {
|
|
1259
1260
|
encoding: "utf8",
|
|
1260
1261
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1261
1262
|
cwd: homedir(),
|
|
@@ -1273,6 +1274,35 @@ function getGlobalClaudeVersion(claudeExecutable) {
|
|
|
1273
1274
|
return null;
|
|
1274
1275
|
}
|
|
1275
1276
|
}
|
|
1277
|
+
function tryReadBundledClaudeVersion(nodeModulesCliPath) {
|
|
1278
|
+
try {
|
|
1279
|
+
const pkgPath = join(dirname(dirname(nodeModulesCliPath)), "package.json");
|
|
1280
|
+
if (!existsSync(pkgPath)) return null;
|
|
1281
|
+
const raw = readFileSync(pkgPath, "utf8");
|
|
1282
|
+
const parsed = JSON.parse(raw);
|
|
1283
|
+
const v = typeof parsed?.version === "string" ? parsed.version.trim() : "";
|
|
1284
|
+
return v || null;
|
|
1285
|
+
} catch {
|
|
1286
|
+
return null;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
function parseSemver3(v) {
|
|
1290
|
+
const m = String(v || "").trim().match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
1291
|
+
if (!m) return null;
|
|
1292
|
+
const a = Number(m[1]);
|
|
1293
|
+
const b = Number(m[2]);
|
|
1294
|
+
const c = Number(m[3]);
|
|
1295
|
+
if (!Number.isFinite(a) || !Number.isFinite(b) || !Number.isFinite(c)) return null;
|
|
1296
|
+
return [a, b, c];
|
|
1297
|
+
}
|
|
1298
|
+
function compareSemver(a, b) {
|
|
1299
|
+
const pa = parseSemver3(a);
|
|
1300
|
+
const pb = parseSemver3(b);
|
|
1301
|
+
if (!pa || !pb) return null;
|
|
1302
|
+
if (pa[0] !== pb[0]) return pa[0] - pb[0];
|
|
1303
|
+
if (pa[1] !== pb[1]) return pa[1] - pb[1];
|
|
1304
|
+
return pa[2] - pb[2];
|
|
1305
|
+
}
|
|
1276
1306
|
function getCleanEnv() {
|
|
1277
1307
|
const env = { ...process$1.env };
|
|
1278
1308
|
const cwd = process$1.cwd();
|
|
@@ -1392,11 +1422,22 @@ function getDefaultClaudeCodePath() {
|
|
|
1392
1422
|
return nodeModulesPath;
|
|
1393
1423
|
}
|
|
1394
1424
|
const globalVersion = getGlobalClaudeVersion(globalPath);
|
|
1425
|
+
const bundledVersion = tryReadBundledClaudeVersion(nodeModulesPath);
|
|
1395
1426
|
logger.debug(`[Claude SDK] Global version: ${globalVersion || "unknown"}`);
|
|
1396
|
-
|
|
1427
|
+
logger.debug(`[Claude SDK] Bundled version: ${bundledVersion || "unknown"}`);
|
|
1428
|
+
if (!globalVersion || !bundledVersion) {
|
|
1397
1429
|
logger.debug(`[Claude SDK] Cannot compare versions, using global: ${globalPath}`);
|
|
1398
1430
|
return globalPath;
|
|
1399
1431
|
}
|
|
1432
|
+
const cmp = compareSemver(bundledVersion, globalVersion);
|
|
1433
|
+
if (cmp === null) {
|
|
1434
|
+
logger.debug(`[Claude SDK] Cannot parse versions, using global: ${globalPath}`);
|
|
1435
|
+
return globalPath;
|
|
1436
|
+
}
|
|
1437
|
+
if (cmp > 0) {
|
|
1438
|
+
logger.debug(`[Claude SDK] Bundled Claude is newer (${bundledVersion} > ${globalVersion}), using bundled: ${nodeModulesPath}`);
|
|
1439
|
+
return nodeModulesPath;
|
|
1440
|
+
}
|
|
1400
1441
|
return globalPath;
|
|
1401
1442
|
}
|
|
1402
1443
|
function logDebug(message) {
|
|
@@ -1671,8 +1712,9 @@ function query(config) {
|
|
|
1671
1712
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1672
1713
|
signal: config.options?.abort,
|
|
1673
1714
|
env: spawnEnv,
|
|
1674
|
-
//
|
|
1675
|
-
|
|
1715
|
+
// Only use a shell on Windows when spawning a bare command (e.g. "claude").
|
|
1716
|
+
// Passing large `--allowedTools` lists through cmd.exe can hit the ~8k command line limit.
|
|
1717
|
+
shell: isCommandOnly && process$1.platform === "win32"
|
|
1676
1718
|
});
|
|
1677
1719
|
let childStdin = null;
|
|
1678
1720
|
if (typeof prompt === "string") {
|
|
@@ -2448,6 +2490,21 @@ async function consumeToolQuota(args) {
|
|
|
2448
2490
|
return { ok: true, allowed: true, unlimited: true };
|
|
2449
2491
|
}
|
|
2450
2492
|
|
|
2493
|
+
function canonicalizeToolNameForMatching(name) {
|
|
2494
|
+
if (name === "ExitPlanMode") return "exit_plan_mode";
|
|
2495
|
+
return name;
|
|
2496
|
+
}
|
|
2497
|
+
function stripUndefinedDeep(value) {
|
|
2498
|
+
if (value === null || value === void 0) return value;
|
|
2499
|
+
if (Array.isArray(value)) return value.map(stripUndefinedDeep);
|
|
2500
|
+
if (typeof value !== "object") return value;
|
|
2501
|
+
const out = {};
|
|
2502
|
+
for (const [key, child] of Object.entries(value)) {
|
|
2503
|
+
if (child === void 0) continue;
|
|
2504
|
+
out[key] = stripUndefinedDeep(child);
|
|
2505
|
+
}
|
|
2506
|
+
return out;
|
|
2507
|
+
}
|
|
2451
2508
|
class PermissionHandler {
|
|
2452
2509
|
toolCalls = [];
|
|
2453
2510
|
responses = /* @__PURE__ */ new Map();
|
|
@@ -2466,7 +2523,7 @@ class PermissionHandler {
|
|
|
2466
2523
|
const decision = args.decision;
|
|
2467
2524
|
const reason = args.reason;
|
|
2468
2525
|
const kind = decision === "approved" || decision === "approved_for_session" ? "policy_allow" : decision === "abort" && reason === "permission_prompt_required" ? "policy_prompt" : "policy_block";
|
|
2469
|
-
const summary = kind === "policy_allow" ? "Allowed." : kind === "policy_prompt" ? "Waiting for permission to run this tool." : reason ? `Blocked: ${reason}` : "Blocked by
|
|
2526
|
+
const summary = kind === "policy_allow" ? "Allowed." : kind === "policy_prompt" ? "Waiting for permission to run this tool." : reason ? `Blocked: ${reason}` : "Blocked by Policy.";
|
|
2470
2527
|
const callId = `policy:${args.toolCallId}:${randomUUID().slice(0, 8)}`;
|
|
2471
2528
|
const payload = {
|
|
2472
2529
|
kind,
|
|
@@ -2531,13 +2588,13 @@ class PermissionHandler {
|
|
|
2531
2588
|
if (args.reason === "docs_index_read_required") {
|
|
2532
2589
|
return {
|
|
2533
2590
|
uiReason: "read the game Documentation index before making edits.",
|
|
2534
|
-
modelMessage: "Blocked by
|
|
2591
|
+
modelMessage: "Blocked by Policy: read the game Documentation index before making edits.\nNext: call `mcp__flockbay__docs_index_read`, then retry the edit."
|
|
2535
2592
|
};
|
|
2536
2593
|
}
|
|
2537
2594
|
if (args.reason === "ledger_read_required") {
|
|
2538
2595
|
return {
|
|
2539
2596
|
uiReason: "read the ledger before making file edits.",
|
|
2540
|
-
modelMessage: "Blocked by
|
|
2597
|
+
modelMessage: "Blocked by Policy: read the ledger before making file edits.\nNext: call `mcp__flockbay__ledger_read` (or `mcp__flockbay__coordination_ledger_snapshot`), then retry the edit."
|
|
2541
2598
|
};
|
|
2542
2599
|
}
|
|
2543
2600
|
if (args.reason === "file_claim_required") {
|
|
@@ -2545,13 +2602,13 @@ class PermissionHandler {
|
|
|
2545
2602
|
const next = file ? `Next: claim it via \`mcp__flockbay__ledger_claim\` (files: ["${file}"]) or \`mcp__flockbay__coordination_claim_files\`, then retry the edit.` : "Next: claim the file via `mcp__flockbay__ledger_claim` (or `mcp__flockbay__coordination_claim_files`), then retry the edit.";
|
|
2546
2603
|
return {
|
|
2547
2604
|
uiReason: display,
|
|
2548
|
-
modelMessage: `Blocked by
|
|
2605
|
+
modelMessage: `Blocked by Policy: ${display}
|
|
2549
2606
|
${next}`
|
|
2550
2607
|
};
|
|
2551
2608
|
}
|
|
2552
2609
|
return {
|
|
2553
2610
|
uiReason: "this session is in read-only mode.",
|
|
2554
|
-
modelMessage: "Blocked by
|
|
2611
|
+
modelMessage: "Blocked by Policy: this session is in read-only mode.\nNext: switch permission mode to allow edits, then retry."
|
|
2555
2612
|
};
|
|
2556
2613
|
}
|
|
2557
2614
|
enforceCoordinationGate(toolName, input) {
|
|
@@ -2731,8 +2788,13 @@ ${next}`
|
|
|
2731
2788
|
}
|
|
2732
2789
|
let toolCallId = this.resolveToolCallId(toolName, input);
|
|
2733
2790
|
if (!toolCallId) {
|
|
2734
|
-
|
|
2735
|
-
|
|
2791
|
+
const isPlanMode = toolName === "exit_plan_mode" || toolName === "ExitPlanMode";
|
|
2792
|
+
const timeoutMs = isPlanMode ? 3e3 : 1e3;
|
|
2793
|
+
const deadline = Date.now() + timeoutMs;
|
|
2794
|
+
while (!toolCallId && Date.now() < deadline) {
|
|
2795
|
+
await delay(100);
|
|
2796
|
+
toolCallId = this.resolveToolCallId(toolName, input);
|
|
2797
|
+
}
|
|
2736
2798
|
if (!toolCallId) {
|
|
2737
2799
|
throw new Error(`Could not resolve tool call ID for ${toolName}`);
|
|
2738
2800
|
}
|
|
@@ -2813,9 +2875,12 @@ ${next}`
|
|
|
2813
2875
|
* Resolves tool call ID based on tool name and input
|
|
2814
2876
|
*/
|
|
2815
2877
|
resolveToolCallId(name, args) {
|
|
2878
|
+
const normalizedName = canonicalizeToolNameForMatching(name);
|
|
2879
|
+
const normalizedArgs = stripUndefinedDeep(args);
|
|
2816
2880
|
for (let i = this.toolCalls.length - 1; i >= 0; i--) {
|
|
2817
2881
|
const call = this.toolCalls[i];
|
|
2818
|
-
|
|
2882
|
+
const callName = canonicalizeToolNameForMatching(call.name);
|
|
2883
|
+
if (callName === normalizedName && deepEqual(stripUndefinedDeep(call.input), normalizedArgs)) {
|
|
2819
2884
|
if (call.used) {
|
|
2820
2885
|
return null;
|
|
2821
2886
|
}
|
|
@@ -2823,6 +2888,18 @@ ${next}`
|
|
|
2823
2888
|
return call.id;
|
|
2824
2889
|
}
|
|
2825
2890
|
}
|
|
2891
|
+
const candidates = [];
|
|
2892
|
+
for (let i = this.toolCalls.length - 1; i >= 0; i--) {
|
|
2893
|
+
const call = this.toolCalls[i];
|
|
2894
|
+
if (call.used) continue;
|
|
2895
|
+
const callName = canonicalizeToolNameForMatching(call.name);
|
|
2896
|
+
if (callName === normalizedName) candidates.push(call);
|
|
2897
|
+
if (candidates.length > 1) break;
|
|
2898
|
+
}
|
|
2899
|
+
if (candidates.length === 1) {
|
|
2900
|
+
candidates[0].used = true;
|
|
2901
|
+
return candidates[0].id;
|
|
2902
|
+
}
|
|
2826
2903
|
return null;
|
|
2827
2904
|
}
|
|
2828
2905
|
/**
|
|
@@ -7095,7 +7172,11 @@ async function uploadScreenshotViewsForSession(args) {
|
|
|
7095
7172
|
for (const v of args.views) {
|
|
7096
7173
|
const buf = await readFile(v.path);
|
|
7097
7174
|
const filename = path.basename(v.path);
|
|
7098
|
-
const contentType =
|
|
7175
|
+
const contentType = (() => {
|
|
7176
|
+
const mime = detectImageMimeTypeFromBuffer$1(buf);
|
|
7177
|
+
if (mime) return mime;
|
|
7178
|
+
throw new Error(`Unsupported screenshot format (expected PNG/JPEG): ${v.path}`);
|
|
7179
|
+
})();
|
|
7099
7180
|
const blob = new Blob([buf], { type: contentType });
|
|
7100
7181
|
form.append(`file:${v.id}`, blob, filename);
|
|
7101
7182
|
}
|
|
@@ -7136,6 +7217,64 @@ async function uploadScreenshotViewsForSession(args) {
|
|
|
7136
7217
|
}
|
|
7137
7218
|
return out;
|
|
7138
7219
|
}
|
|
7220
|
+
function detectImageMimeTypeFromBuffer$1(buf) {
|
|
7221
|
+
if (!buf || buf.length < 12) return null;
|
|
7222
|
+
if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71 && buf[4] === 13 && buf[5] === 10 && buf[6] === 26 && buf[7] === 10) {
|
|
7223
|
+
return "image/png";
|
|
7224
|
+
}
|
|
7225
|
+
if (buf[0] === 255 && buf[1] === 216 && buf[2] === 255) {
|
|
7226
|
+
return "image/jpeg";
|
|
7227
|
+
}
|
|
7228
|
+
if (buf[0] === 71 && buf[1] === 73 && buf[2] === 70 && buf[3] === 56 && (buf[4] === 55 || buf[4] === 57) && buf[5] === 97) {
|
|
7229
|
+
return "image/gif";
|
|
7230
|
+
}
|
|
7231
|
+
if (buf[0] === 82 && buf[1] === 73 && buf[2] === 70 && buf[3] === 70 && buf[8] === 87 && buf[9] === 69 && buf[10] === 66 && buf[11] === 80) {
|
|
7232
|
+
return "image/webp";
|
|
7233
|
+
}
|
|
7234
|
+
return null;
|
|
7235
|
+
}
|
|
7236
|
+
async function readFileHeader(filePath, bytes) {
|
|
7237
|
+
const fh = await open$1(filePath, "r");
|
|
7238
|
+
try {
|
|
7239
|
+
const maxBytes = Math.max(1, Math.floor(bytes));
|
|
7240
|
+
const buf = Buffer.allocUnsafe(maxBytes);
|
|
7241
|
+
const { bytesRead } = await fh.read(buf, 0, maxBytes, 0);
|
|
7242
|
+
return buf.subarray(0, bytesRead);
|
|
7243
|
+
} finally {
|
|
7244
|
+
try {
|
|
7245
|
+
await fh.close();
|
|
7246
|
+
} catch {
|
|
7247
|
+
}
|
|
7248
|
+
}
|
|
7249
|
+
}
|
|
7250
|
+
async function normalizeScreenshotPathExtensionToMatchBytes(filePath) {
|
|
7251
|
+
const abs = String(filePath || "").trim();
|
|
7252
|
+
if (!abs) return { path: abs, changed: false };
|
|
7253
|
+
if (!existsSync(abs)) return { path: abs, changed: false };
|
|
7254
|
+
const lower = abs.toLowerCase();
|
|
7255
|
+
const header = await readFileHeader(abs, 16);
|
|
7256
|
+
const mime = detectImageMimeTypeFromBuffer$1(header);
|
|
7257
|
+
if (!mime) {
|
|
7258
|
+
return { path: abs, changed: false, detail: "unknown_image_format" };
|
|
7259
|
+
}
|
|
7260
|
+
if (mime === "image/jpeg" && lower.endsWith(".png")) {
|
|
7261
|
+
const next = abs.replace(/\.png$/i, ".jpg");
|
|
7262
|
+
if (existsSync(next)) {
|
|
7263
|
+
throw new Error(`Screenshot already exists at normalized path: ${next}`);
|
|
7264
|
+
}
|
|
7265
|
+
await rename(abs, next);
|
|
7266
|
+
return { path: next, changed: true, detail: "renamed_png_to_jpg" };
|
|
7267
|
+
}
|
|
7268
|
+
if (mime === "image/png" && (lower.endsWith(".jpg") || lower.endsWith(".jpeg"))) {
|
|
7269
|
+
const next = abs.replace(/\.(jpg|jpeg)$/i, ".png");
|
|
7270
|
+
if (existsSync(next)) {
|
|
7271
|
+
throw new Error(`Screenshot already exists at normalized path: ${next}`);
|
|
7272
|
+
}
|
|
7273
|
+
await rename(abs, next);
|
|
7274
|
+
return { path: next, changed: true, detail: "renamed_jpg_to_png" };
|
|
7275
|
+
}
|
|
7276
|
+
return { path: abs, changed: false };
|
|
7277
|
+
}
|
|
7139
7278
|
async function startFlockbayServer(client, options) {
|
|
7140
7279
|
const handler = async (title) => {
|
|
7141
7280
|
logger.debug("[flockbayMCP] Changing title to:", title);
|
|
@@ -8665,13 +8804,13 @@ ${String(st.stdout || "").trim()}`
|
|
|
8665
8804
|
"read_images",
|
|
8666
8805
|
{
|
|
8667
8806
|
title: "Read Images",
|
|
8668
|
-
description: "Read one or more local
|
|
8807
|
+
description: "Read one or more local images by path (PNG/JPEG) and return a `{ views: [...] }` payload so the app can render them as a gallery.",
|
|
8669
8808
|
inputSchema: {
|
|
8670
|
-
paths: z.array(z.string()).describe("Image paths (absolute or relative to the session directory). PNG only."),
|
|
8809
|
+
paths: z.array(z.string()).describe("Image paths (absolute or relative to the session directory). PNG/JPG only."),
|
|
8671
8810
|
limit: z.number().int().positive().optional().describe("Max number of images to include (default 10)."),
|
|
8672
8811
|
upload: z.boolean().optional().describe("Upload images to the session screenshots store and return HTTPS URLs (default true)."),
|
|
8673
8812
|
includeBase64: z.boolean().optional().describe("Include base64 data in the payload (default false)."),
|
|
8674
|
-
includeToolImages: z.boolean().optional().describe("Include MCP image blocks so vision models can see the
|
|
8813
|
+
includeToolImages: z.boolean().optional().describe("Include MCP image blocks so vision models can see the images (default false)."),
|
|
8675
8814
|
maxBytesPerImage: z.number().int().positive().optional().describe("Max bytes per image when includeBase64=true (default 2500000).")
|
|
8676
8815
|
}
|
|
8677
8816
|
},
|
|
@@ -8703,14 +8842,16 @@ ${String(st.stdout || "").trim()}`
|
|
|
8703
8842
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
8704
8843
|
if (home) return path.join(home, p.slice(2));
|
|
8705
8844
|
}
|
|
8706
|
-
|
|
8845
|
+
const baseDir = readSessionWorkingDirectory();
|
|
8846
|
+
return path.isAbsolute(p) ? p : path.resolve(baseDir, p);
|
|
8707
8847
|
};
|
|
8708
8848
|
const idsSeen = /* @__PURE__ */ new Set();
|
|
8709
8849
|
const viewsLocal = [];
|
|
8710
8850
|
for (const inputPath of paths) {
|
|
8711
8851
|
const abs = resolvePath(inputPath);
|
|
8712
|
-
|
|
8713
|
-
|
|
8852
|
+
const lower = abs.toLowerCase();
|
|
8853
|
+
if (!lower.endsWith(".png") && !lower.endsWith(".jpg") && !lower.endsWith(".jpeg")) {
|
|
8854
|
+
throw new Error(`Only PNG/JPG images are supported: ${abs}`);
|
|
8714
8855
|
}
|
|
8715
8856
|
if (!existsSync(abs)) {
|
|
8716
8857
|
throw new Error(`Image not found: ${abs}`);
|
|
@@ -8730,7 +8871,11 @@ ${String(st.stdout || "").trim()}`
|
|
|
8730
8871
|
throw new Error(`Image too large (${st.size} bytes) to embed: ${abs}`);
|
|
8731
8872
|
}
|
|
8732
8873
|
const buf = await readFile(abs);
|
|
8733
|
-
|
|
8874
|
+
const mime = detectImageMimeTypeFromBuffer$1(buf);
|
|
8875
|
+
if (!mime) {
|
|
8876
|
+
throw new Error(`Unsupported image format (expected PNG/JPEG): ${abs}`);
|
|
8877
|
+
}
|
|
8878
|
+
viewsLocal.push({ id: unique, path: abs, base64: buf.toString("base64"), mimeType: mime });
|
|
8734
8879
|
} else {
|
|
8735
8880
|
viewsLocal.push({ id: unique, path: abs });
|
|
8736
8881
|
}
|
|
@@ -8755,7 +8900,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
8755
8900
|
viewsLocal.forEach((v, idx) => {
|
|
8756
8901
|
if (!v.base64) return;
|
|
8757
8902
|
content.push({ type: "text", text: `Image ${idx + 1}: ${v.id}` });
|
|
8758
|
-
content.push({ type: "image", data: v.base64, mimeType: "image/png" });
|
|
8903
|
+
content.push({ type: "image", data: v.base64, mimeType: v.mimeType || "image/png" });
|
|
8759
8904
|
});
|
|
8760
8905
|
}
|
|
8761
8906
|
return {
|
|
@@ -8767,7 +8912,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
8767
8912
|
return {
|
|
8768
8913
|
content: [
|
|
8769
8914
|
{ type: "text", text: `Failed to read images: ${error instanceof Error ? error.message : String(error)}` },
|
|
8770
|
-
{ type: "text", text: `Tip: pass absolute PNG paths, or relative paths from the session folder.` }
|
|
8915
|
+
{ type: "text", text: `Tip: pass absolute PNG/JPG paths, or relative paths from the session folder.` }
|
|
8771
8916
|
],
|
|
8772
8917
|
isError: true
|
|
8773
8918
|
};
|
|
@@ -8776,7 +8921,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
8776
8921
|
);
|
|
8777
8922
|
mcp.registerTool("unreal_latest_screenshots", {
|
|
8778
8923
|
title: "Latest Unreal Screenshots (Validation)",
|
|
8779
|
-
description: "Fetch the latest PNG
|
|
8924
|
+
description: "Fetch the latest screenshots (PNG/JPG) from `Saved/Screenshots/Flockbay/` (for validation) and return a `{ views: [...] }` payload so the app can display them.",
|
|
8780
8925
|
inputSchema: {
|
|
8781
8926
|
uprojectPath: z.string().describe("Absolute path to the .uproject file."),
|
|
8782
8927
|
limit: z.number().int().positive().optional().describe("Max number of screenshots to return (default 12)."),
|
|
@@ -8813,7 +8958,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
8813
8958
|
};
|
|
8814
8959
|
}
|
|
8815
8960
|
const files = await readdir(outDir);
|
|
8816
|
-
const candidates = files.filter((f) =>
|
|
8961
|
+
const candidates = files.filter((f) => /\.(png|jpg|jpeg)$/i.test(f));
|
|
8817
8962
|
if (candidates.length === 0) {
|
|
8818
8963
|
return {
|
|
8819
8964
|
content: [
|
|
@@ -9068,10 +9213,26 @@ ${String(st.stdout || "").trim()}`
|
|
|
9068
9213
|
const pluginInfoWasCached = Boolean(unrealMcpPluginInfoCache);
|
|
9069
9214
|
const pluginInfo = type !== "get_plugin_info" ? await getUnrealMcpPluginInfoBestEffort(timeoutMs) : null;
|
|
9070
9215
|
const response = await sendUnrealMcpTcpCommand({ type, params, timeoutMs });
|
|
9216
|
+
let screenshotNormalizationNote = null;
|
|
9217
|
+
if (type === "take_screenshot") {
|
|
9218
|
+
const responseObj = response && typeof response === "object" ? response : null;
|
|
9219
|
+
const candidate = responseObj && typeof responseObj.filepath === "string" ? responseObj : responseObj && responseObj.result && typeof responseObj.result === "object" && typeof responseObj.result.filepath === "string" ? responseObj.result : null;
|
|
9220
|
+
if (candidate && typeof candidate.filepath === "string") {
|
|
9221
|
+
const before = String(candidate.filepath || "").trim();
|
|
9222
|
+
if (before) {
|
|
9223
|
+
const normalized = await normalizeScreenshotPathExtensionToMatchBytes(before);
|
|
9224
|
+
if (normalized.changed) {
|
|
9225
|
+
candidate.filepath = normalized.path;
|
|
9226
|
+
screenshotNormalizationNote = `Normalized screenshot path (${normalized.detail || "extension_fixed"}): ${before} \u2192 ${normalized.path}`;
|
|
9227
|
+
}
|
|
9228
|
+
}
|
|
9229
|
+
}
|
|
9230
|
+
}
|
|
9071
9231
|
unrealEditorSupervisor.noteUnrealReachable();
|
|
9072
9232
|
return {
|
|
9073
9233
|
content: [
|
|
9074
9234
|
{ type: "text", text: `UnrealMCP command ok: ${type}` },
|
|
9235
|
+
...screenshotNormalizationNote ? [{ type: "text", text: screenshotNormalizationNote }] : [],
|
|
9075
9236
|
{ type: "text", text: JSON.stringify(response, null, 2) },
|
|
9076
9237
|
...pluginInfo && !pluginInfoWasCached ? [{ type: "text", text: formatUnrealMcpCapabilities(pluginInfo) }] : []
|
|
9077
9238
|
],
|
|
@@ -11731,6 +11892,269 @@ async function handleConnectVendor(vendor, displayName, flags) {
|
|
|
11731
11892
|
}
|
|
11732
11893
|
}
|
|
11733
11894
|
|
|
11895
|
+
function readArgValue$1(args, key) {
|
|
11896
|
+
const idx = args.indexOf(key);
|
|
11897
|
+
if (idx === -1) return null;
|
|
11898
|
+
const value = args[idx + 1];
|
|
11899
|
+
if (!value || value.startsWith("-")) return null;
|
|
11900
|
+
return value;
|
|
11901
|
+
}
|
|
11902
|
+
function splitList(value) {
|
|
11903
|
+
if (!value) return [];
|
|
11904
|
+
return value.split(/[;,]/g).map((v) => v.trim()).filter(Boolean);
|
|
11905
|
+
}
|
|
11906
|
+
function ensureDir(dir) {
|
|
11907
|
+
fs__default.mkdirSync(dir, { recursive: true });
|
|
11908
|
+
}
|
|
11909
|
+
function detectImageMimeTypeFromBuffer(buf) {
|
|
11910
|
+
if (!buf || buf.length < 12) return null;
|
|
11911
|
+
if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71 && buf[4] === 13 && buf[5] === 10 && buf[6] === 26 && buf[7] === 10) {
|
|
11912
|
+
return "image/png";
|
|
11913
|
+
}
|
|
11914
|
+
if (buf[0] === 255 && buf[1] === 216 && buf[2] === 255) return "image/jpeg";
|
|
11915
|
+
if (buf[0] === 71 && buf[1] === 73 && buf[2] === 70 && buf[3] === 56) return "image/gif";
|
|
11916
|
+
if (buf[0] === 82 && buf[1] === 73 && buf[2] === 70 && buf[3] === 70 && buf[8] === 87 && buf[9] === 69 && buf[10] === 66 && buf[11] === 80) {
|
|
11917
|
+
return "image/webp";
|
|
11918
|
+
}
|
|
11919
|
+
return null;
|
|
11920
|
+
}
|
|
11921
|
+
async function waitForUnreal(options) {
|
|
11922
|
+
const startedAt = Date.now();
|
|
11923
|
+
let lastErr = null;
|
|
11924
|
+
while (Date.now() - startedAt < options.timeoutMs) {
|
|
11925
|
+
try {
|
|
11926
|
+
const res = await sendUnrealMcpTcpCommand({ type: "ping", host: options.host, port: options.port, timeoutMs: 2e3 });
|
|
11927
|
+
const msg = typeof res?.message === "string" ? res.message : null;
|
|
11928
|
+
if (msg === "pong") return { ok: true };
|
|
11929
|
+
} catch (err) {
|
|
11930
|
+
lastErr = err instanceof Error ? err.message : String(err);
|
|
11931
|
+
}
|
|
11932
|
+
await new Promise((r) => setTimeout(r, 750));
|
|
11933
|
+
}
|
|
11934
|
+
return {
|
|
11935
|
+
ok: false,
|
|
11936
|
+
error: `Timed out waiting for UnrealMCP ping after ${options.timeoutMs}ms.${lastErr ? ` Last error: ${lastErr}` : ""}`
|
|
11937
|
+
};
|
|
11938
|
+
}
|
|
11939
|
+
function resolveUnrealEditorExe(engineRoot) {
|
|
11940
|
+
const exe = process.platform === "win32" ? path.join(engineRoot, "Engine", "Binaries", "Win64", "UnrealEditor.exe") : path.join(engineRoot, "Engine", "Binaries", process.platform === "darwin" ? "Mac" : "Linux", "UnrealEditor");
|
|
11941
|
+
return exe;
|
|
11942
|
+
}
|
|
11943
|
+
async function runRuntimeSmoke(options) {
|
|
11944
|
+
const editorExe = resolveUnrealEditorExe(options.engineRoot);
|
|
11945
|
+
if (!fs__default.existsSync(editorExe)) {
|
|
11946
|
+
return { ok: false, error: `Missing UnrealEditor executable: ${editorExe}` };
|
|
11947
|
+
}
|
|
11948
|
+
if (!fs__default.existsSync(options.projectPath)) {
|
|
11949
|
+
return { ok: false, error: `Missing .uproject: ${options.projectPath}` };
|
|
11950
|
+
}
|
|
11951
|
+
let child = null;
|
|
11952
|
+
const ping = await waitForUnreal({ host: options.host, port: options.port, timeoutMs: 2e3 }).catch((e) => ({ ok: false, error: String(e) }));
|
|
11953
|
+
if (!ping.ok && options.launchIfNeeded) {
|
|
11954
|
+
const args = [
|
|
11955
|
+
options.projectPath,
|
|
11956
|
+
"-NoSplash",
|
|
11957
|
+
"-NoSound",
|
|
11958
|
+
"-nop4"
|
|
11959
|
+
];
|
|
11960
|
+
child = spawn(editorExe, args, {
|
|
11961
|
+
stdio: "ignore",
|
|
11962
|
+
detached: false
|
|
11963
|
+
});
|
|
11964
|
+
}
|
|
11965
|
+
const ready = await waitForUnreal({ host: options.host, port: options.port, timeoutMs: options.connectTimeoutMs });
|
|
11966
|
+
if (!ready.ok) {
|
|
11967
|
+
try {
|
|
11968
|
+
child?.kill();
|
|
11969
|
+
} catch {
|
|
11970
|
+
}
|
|
11971
|
+
return ready;
|
|
11972
|
+
}
|
|
11973
|
+
try {
|
|
11974
|
+
const pluginInfo = await sendUnrealMcpTcpCommand({ type: "get_plugin_info", host: options.host, port: options.port, timeoutMs: options.timeoutMs });
|
|
11975
|
+
const createdBy = String(pluginInfo?.createdBy || "").trim();
|
|
11976
|
+
const friendlyName = String(pluginInfo?.friendlyName || "").trim();
|
|
11977
|
+
const baseDir = String(pluginInfo?.baseDir || "").trim();
|
|
11978
|
+
const schemaVersion = Number(pluginInfo?.schemaVersion);
|
|
11979
|
+
const commands = Array.isArray(pluginInfo?.commands) ? pluginInfo.commands.filter((c) => typeof c === "string") : [];
|
|
11980
|
+
if (friendlyName !== "Flockbay MCP" || createdBy !== "Respaced Inc.") {
|
|
11981
|
+
return {
|
|
11982
|
+
ok: false,
|
|
11983
|
+
error: `Unexpected plugin identity loaded by Unreal.
|
|
11984
|
+
friendlyName=${friendlyName || "(missing)"} createdBy=${createdBy || "(missing)"}
|
|
11985
|
+
baseDir=${baseDir || "(missing)"}
|
|
11986
|
+
Expected FriendlyName="Flockbay MCP" CreatedBy="Respaced Inc."`
|
|
11987
|
+
};
|
|
11988
|
+
}
|
|
11989
|
+
if (!Number.isFinite(schemaVersion) || schemaVersion <= 0) {
|
|
11990
|
+
return { ok: false, error: `Invalid schemaVersion from get_plugin_info: ${String(pluginInfo?.schemaVersion)}` };
|
|
11991
|
+
}
|
|
11992
|
+
const requireCommands = [
|
|
11993
|
+
"ping",
|
|
11994
|
+
"get_plugin_info",
|
|
11995
|
+
"list_capabilities",
|
|
11996
|
+
"get_command_schema",
|
|
11997
|
+
"get_play_in_editor_status",
|
|
11998
|
+
"play_in_editor_windowed",
|
|
11999
|
+
"stop_play_in_editor",
|
|
12000
|
+
"take_screenshot",
|
|
12001
|
+
"create_blueprint",
|
|
12002
|
+
"compile_blueprint",
|
|
12003
|
+
"map_check"
|
|
12004
|
+
];
|
|
12005
|
+
const missing = requireCommands.filter((c) => !commands.includes(c));
|
|
12006
|
+
if (missing.length > 0) {
|
|
12007
|
+
return { ok: false, error: `Missing required commands in this UnrealMCP build: ${missing.join(", ")}` };
|
|
12008
|
+
}
|
|
12009
|
+
const playStatus0 = await sendUnrealMcpTcpCommand({ type: "get_play_in_editor_status", host: options.host, port: options.port, timeoutMs: options.timeoutMs });
|
|
12010
|
+
const isPlaying = Boolean(playStatus0?.isPlaySessionInProgress);
|
|
12011
|
+
if (isPlaying) {
|
|
12012
|
+
await sendUnrealMcpTcpCommand({ type: "stop_play_in_editor", host: options.host, port: options.port, timeoutMs: options.timeoutMs });
|
|
12013
|
+
}
|
|
12014
|
+
await sendUnrealMcpTcpCommand({ type: "play_in_editor_windowed", host: options.host, port: options.port, timeoutMs: Math.max(options.timeoutMs, 2e4) });
|
|
12015
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
12016
|
+
await sendUnrealMcpTcpCommand({ type: "stop_play_in_editor", host: options.host, port: options.port, timeoutMs: Math.max(options.timeoutMs, 2e4) });
|
|
12017
|
+
const projectDir = path.dirname(options.projectPath);
|
|
12018
|
+
const shotsDir = path.join(projectDir, "Saved", "Screenshots", "Flockbay");
|
|
12019
|
+
ensureDir(shotsDir);
|
|
12020
|
+
const shotPath = path.join(shotsDir, `smoke_${Date.now()}.png`);
|
|
12021
|
+
await sendUnrealMcpTcpCommand({
|
|
12022
|
+
type: "take_screenshot",
|
|
12023
|
+
params: { filepath: shotPath },
|
|
12024
|
+
host: options.host,
|
|
12025
|
+
port: options.port,
|
|
12026
|
+
timeoutMs: options.timeoutMs
|
|
12027
|
+
});
|
|
12028
|
+
if (!fs__default.existsSync(shotPath)) return { ok: false, error: `Screenshot did not exist on disk after take_screenshot: ${shotPath}` };
|
|
12029
|
+
const bytes = fs__default.readFileSync(shotPath);
|
|
12030
|
+
const mime = detectImageMimeTypeFromBuffer(bytes);
|
|
12031
|
+
if (mime !== "image/png") {
|
|
12032
|
+
return { ok: false, error: `Screenshot bytes do not match .png extension (detected ${mime || "unknown"}): ${shotPath}` };
|
|
12033
|
+
}
|
|
12034
|
+
const bpName = `BP_Smoke_${Date.now()}`;
|
|
12035
|
+
await sendUnrealMcpTcpCommand({
|
|
12036
|
+
type: "create_blueprint",
|
|
12037
|
+
params: { name: bpName, path: "/Game/FlockbaySmoke/", parent_class: "Actor" },
|
|
12038
|
+
host: options.host,
|
|
12039
|
+
port: options.port,
|
|
12040
|
+
timeoutMs: Math.max(options.timeoutMs, 2e4)
|
|
12041
|
+
});
|
|
12042
|
+
await sendUnrealMcpTcpCommand({
|
|
12043
|
+
type: "compile_blueprint",
|
|
12044
|
+
params: { blueprint_name: bpName },
|
|
12045
|
+
host: options.host,
|
|
12046
|
+
port: options.port,
|
|
12047
|
+
timeoutMs: Math.max(options.timeoutMs, 6e4)
|
|
12048
|
+
});
|
|
12049
|
+
await sendUnrealMcpTcpCommand({
|
|
12050
|
+
type: "map_check",
|
|
12051
|
+
host: options.host,
|
|
12052
|
+
port: options.port,
|
|
12053
|
+
timeoutMs: Math.max(options.timeoutMs, 3e4)
|
|
12054
|
+
});
|
|
12055
|
+
return { ok: true };
|
|
12056
|
+
} finally {
|
|
12057
|
+
if (options.killAfter && child) {
|
|
12058
|
+
try {
|
|
12059
|
+
if (process.platform === "win32") {
|
|
12060
|
+
spawn("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore" });
|
|
12061
|
+
} else {
|
|
12062
|
+
child.kill();
|
|
12063
|
+
}
|
|
12064
|
+
} catch {
|
|
12065
|
+
}
|
|
12066
|
+
}
|
|
12067
|
+
}
|
|
12068
|
+
}
|
|
12069
|
+
async function runUnrealMcpMatrixSmoke(args) {
|
|
12070
|
+
const engineRoots = splitList(readArgValue$1(args, "--engine-roots")) || [];
|
|
12071
|
+
const engineRootSingle = readArgValue$1(args, "--engine-root");
|
|
12072
|
+
if (engineRootSingle) engineRoots.push(engineRootSingle.trim());
|
|
12073
|
+
const project = readArgValue$1(args, "--project");
|
|
12074
|
+
const host = (readArgValue$1(args, "--host") || "127.0.0.1").trim() || "127.0.0.1";
|
|
12075
|
+
const port = Number(readArgValue$1(args, "--port") || "55557");
|
|
12076
|
+
const connectTimeoutMs = Number(readArgValue$1(args, "--connect-timeout-ms") || "180000");
|
|
12077
|
+
const timeoutMs = Number(readArgValue$1(args, "--timeout-ms") || "30000");
|
|
12078
|
+
const doBuild = !args.includes("--runtime-only");
|
|
12079
|
+
const doRuntime = !args.includes("--build-only");
|
|
12080
|
+
const launch = args.includes("--launch-editor");
|
|
12081
|
+
const killAfter = args.includes("--kill-editor");
|
|
12082
|
+
if (engineRoots.length === 0) {
|
|
12083
|
+
console.error(chalk.red("Missing --engine-root or --engine-roots."));
|
|
12084
|
+
console.error(chalk.gray('Example: flockbay doctor unreal-mcp-smoke --engine-roots "C:\\\\Epic\\\\UE_5.5;C:\\\\Epic\\\\UE_5.6" --project "C:\\\\Projects\\\\MyProj\\\\MyProj.uproject" --launch-editor --kill-editor'));
|
|
12085
|
+
process.exit(1);
|
|
12086
|
+
}
|
|
12087
|
+
if (doRuntime && !project) {
|
|
12088
|
+
console.error(chalk.red("Missing --project (required for runtime smoke)."));
|
|
12089
|
+
process.exit(1);
|
|
12090
|
+
}
|
|
12091
|
+
console.log(chalk.bold("\nUnrealMCP Matrix Smoke\n"));
|
|
12092
|
+
console.log(chalk.gray(`Platform: ${process.platform}`));
|
|
12093
|
+
console.log(chalk.gray(`Host: ${host}:${port}`));
|
|
12094
|
+
console.log(chalk.gray(`Engines: ${engineRoots.join(", ")}`));
|
|
12095
|
+
if (project) console.log(chalk.gray(`Project: ${project}`));
|
|
12096
|
+
console.log(chalk.gray(`Build: ${doBuild ? "yes" : "no"} Runtime: ${doRuntime ? "yes" : "no"} Launch: ${launch ? "yes" : "no"} Kill: ${killAfter ? "yes" : "no"}`));
|
|
12097
|
+
console.log("");
|
|
12098
|
+
const failures = [];
|
|
12099
|
+
for (const engineRootRaw of engineRoots) {
|
|
12100
|
+
const engineRoot = engineRootRaw.trim();
|
|
12101
|
+
if (!engineRoot) continue;
|
|
12102
|
+
console.log(chalk.bold(`== Engine: ${engineRoot} ==`));
|
|
12103
|
+
if (doBuild) {
|
|
12104
|
+
console.log(chalk.cyan("Build: installing UnrealMCP plugin sources..."));
|
|
12105
|
+
const installed = installUnrealMcpPluginToEngine(engineRoot);
|
|
12106
|
+
if (!installed.ok) {
|
|
12107
|
+
failures.push({ engineRoot, phase: "build", error: installed.errorMessage });
|
|
12108
|
+
console.log(chalk.red(`Build: failed (install)
|
|
12109
|
+
${installed.errorMessage}
|
|
12110
|
+
`));
|
|
12111
|
+
if (!doRuntime) continue;
|
|
12112
|
+
} else {
|
|
12113
|
+
console.log(chalk.green(`Build: sources installed to ${installed.destDir}`));
|
|
12114
|
+
console.log(chalk.cyan("Build: compiling plugin via RunUAT BuildPlugin..."));
|
|
12115
|
+
const built = await buildAndInstallUnrealMcpPlugin({ engineRoot, flockbayHomeDir: configuration.flockbayHomeDir });
|
|
12116
|
+
if (!built.ok) {
|
|
12117
|
+
failures.push({ engineRoot, phase: "build", error: built.errorMessage });
|
|
12118
|
+
console.log(chalk.red(`Build: failed
|
|
12119
|
+
${built.errorMessage}
|
|
12120
|
+
`));
|
|
12121
|
+
} else {
|
|
12122
|
+
console.log(chalk.green(`Build: ok (log: ${built.buildLogPath})`));
|
|
12123
|
+
}
|
|
12124
|
+
}
|
|
12125
|
+
}
|
|
12126
|
+
if (doRuntime) {
|
|
12127
|
+
console.log(chalk.cyan("Runtime: running command smoke..."));
|
|
12128
|
+
const res = await runRuntimeSmoke({
|
|
12129
|
+
engineRoot,
|
|
12130
|
+
projectPath: project,
|
|
12131
|
+
host,
|
|
12132
|
+
port,
|
|
12133
|
+
connectTimeoutMs,
|
|
12134
|
+
timeoutMs,
|
|
12135
|
+
launchIfNeeded: launch,
|
|
12136
|
+
killAfter
|
|
12137
|
+
});
|
|
12138
|
+
if (!res.ok) {
|
|
12139
|
+
failures.push({ engineRoot, phase: "runtime", error: res.error });
|
|
12140
|
+
console.log(chalk.red(`Runtime: failed
|
|
12141
|
+
${res.error}
|
|
12142
|
+
`));
|
|
12143
|
+
} else {
|
|
12144
|
+
console.log(chalk.green("Runtime: ok\n"));
|
|
12145
|
+
}
|
|
12146
|
+
}
|
|
12147
|
+
}
|
|
12148
|
+
if (failures.length > 0) {
|
|
12149
|
+
console.error(chalk.red("\nMatrix smoke failed.\n"));
|
|
12150
|
+
for (const f of failures) {
|
|
12151
|
+
console.error(chalk.red(`- ${f.engineRoot} (${f.phase}): ${f.error}`));
|
|
12152
|
+
}
|
|
12153
|
+
process.exit(1);
|
|
12154
|
+
}
|
|
12155
|
+
console.log(chalk.green("\nMatrix smoke passed.\n"));
|
|
12156
|
+
}
|
|
12157
|
+
|
|
11734
12158
|
function readTailUtf8(filePath, maxBytes) {
|
|
11735
12159
|
try {
|
|
11736
12160
|
const stat = fs.statSync(filePath);
|
|
@@ -12091,6 +12515,16 @@ async function authAndSetupMachineIfNeeded() {
|
|
|
12091
12515
|
}
|
|
12092
12516
|
if (!args.includes("--version")) ;
|
|
12093
12517
|
if (subcommand === "doctor") {
|
|
12518
|
+
if (args[1] === "unreal-mcp-smoke") {
|
|
12519
|
+
try {
|
|
12520
|
+
await runUnrealMcpMatrixSmoke(args.slice(2));
|
|
12521
|
+
} catch (error) {
|
|
12522
|
+
console.error(chalk.red("UnrealMCP smoke failed:"), error instanceof Error ? error.message : String(error));
|
|
12523
|
+
if (process.env.DEBUG) console.error(error);
|
|
12524
|
+
process.exit(1);
|
|
12525
|
+
}
|
|
12526
|
+
return;
|
|
12527
|
+
}
|
|
12094
12528
|
if (args[1] === "clean") {
|
|
12095
12529
|
const result = await killRunawayFlockbayProcesses();
|
|
12096
12530
|
console.log(`Cleaned up ${result.killed} runaway processes`);
|
|
@@ -12202,7 +12636,7 @@ ${engineRoot}`, {
|
|
|
12202
12636
|
} else if (subcommand === "codex") {
|
|
12203
12637
|
try {
|
|
12204
12638
|
await chdirToNearestUprojectRootIfPresent();
|
|
12205
|
-
const { runCodex } = await import('./runCodex-
|
|
12639
|
+
const { runCodex } = await import('./runCodex-B7fGICdv.mjs');
|
|
12206
12640
|
let startedBy = void 0;
|
|
12207
12641
|
let sessionId = void 0;
|
|
12208
12642
|
for (let i = 1; i < args.length; i++) {
|
|
@@ -12228,7 +12662,13 @@ ${engineRoot}`, {
|
|
|
12228
12662
|
const geminiSubcommand = args[1];
|
|
12229
12663
|
if (geminiSubcommand === "model" && args[2] === "set" && args[3]) {
|
|
12230
12664
|
const modelName = args[3];
|
|
12231
|
-
const validModels = [
|
|
12665
|
+
const validModels = [
|
|
12666
|
+
"gemini-2.5-pro",
|
|
12667
|
+
"gemini-2.5-flash",
|
|
12668
|
+
"gemini-2.5-flash-lite",
|
|
12669
|
+
"gemini-3-pro-preview",
|
|
12670
|
+
"gemini-3-flash-preview"
|
|
12671
|
+
];
|
|
12232
12672
|
if (!validModels.includes(modelName)) {
|
|
12233
12673
|
console.error(`Invalid model: ${modelName}`);
|
|
12234
12674
|
console.error(`Available models: ${validModels.join(", ")}`);
|
|
@@ -12297,7 +12737,7 @@ ${engineRoot}`, {
|
|
|
12297
12737
|
}
|
|
12298
12738
|
try {
|
|
12299
12739
|
await chdirToNearestUprojectRootIfPresent();
|
|
12300
|
-
const { runGemini } = await import('./runGemini-
|
|
12740
|
+
const { runGemini } = await import('./runGemini-CK43WQk8.mjs');
|
|
12301
12741
|
let startedBy = void 0;
|
|
12302
12742
|
let sessionId = void 0;
|
|
12303
12743
|
for (let i = 1; i < args.length; i++) {
|