flockbay 0.10.21 → 0.10.23
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-Bhkn02hu.cjs → index-BtB1Sqpy.cjs} +353 -17
- package/dist/{index-By332wvJ.mjs → index-CRGcIpET.mjs} +354 -17
- 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-d2KQX2mn.mjs → runCodex-BC3IImzm.mjs} +11 -28
- package/dist/{runCodex-CH4lz1QX.cjs → runCodex-ozeIEfAo.cjs} +11 -28
- package/dist/{runGemini-DNSymY04.cjs → runGemini-B-sAKY5u.cjs} +135 -20
- package/dist/{runGemini-Cn0C7MS1.mjs → runGemini-C43IKGUU.mjs} +135 -20
- package/dist/{types-mXJc7o0P.mjs → types-CNn15BaT.mjs} +47 -6
- package/dist/{types-DeH24uWs.cjs → types-DvlwEGpS.cjs} +47 -5
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var chalk = require('chalk');
|
|
4
4
|
var os = require('node:os');
|
|
5
5
|
var node_crypto = require('node:crypto');
|
|
6
|
-
var types = require('./types-
|
|
6
|
+
var types = require('./types-DvlwEGpS.cjs');
|
|
7
7
|
var node_child_process = require('node:child_process');
|
|
8
8
|
var path = require('node:path');
|
|
9
9
|
var node_readline = require('node:readline');
|
|
@@ -408,7 +408,7 @@ const PLATFORM_SYSTEM_PROMPT = trimIdent(`
|
|
|
408
408
|
|
|
409
409
|
# Policy blocks (not user rejections)
|
|
410
410
|
|
|
411
|
-
If a tool call is **blocked by
|
|
411
|
+
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.
|
|
412
412
|
|
|
413
413
|
# Documentation Library (server-stored docs)
|
|
414
414
|
|
|
@@ -1273,7 +1273,7 @@ function buildDaemonSafeEnv(baseEnv, binPath) {
|
|
|
1273
1273
|
env[pathKey] = [...prepend, ...existingParts].join(pathSep);
|
|
1274
1274
|
return env;
|
|
1275
1275
|
}
|
|
1276
|
-
const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-
|
|
1276
|
+
const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-BtB1Sqpy.cjs', document.baseURI).href)));
|
|
1277
1277
|
const __dirname$1 = path.join(__filename$1, "..");
|
|
1278
1278
|
function getGlobalClaudeVersion(claudeExecutable) {
|
|
1279
1279
|
try {
|
|
@@ -2545,7 +2545,7 @@ class PermissionHandler {
|
|
|
2545
2545
|
const decision = args.decision;
|
|
2546
2546
|
const reason = args.reason;
|
|
2547
2547
|
const kind = decision === "approved" || decision === "approved_for_session" ? "policy_allow" : decision === "abort" && reason === "permission_prompt_required" ? "policy_prompt" : "policy_block";
|
|
2548
|
-
const summary = kind === "policy_allow" ? "Allowed." : kind === "policy_prompt" ? "Waiting for permission to run this tool." : reason ? `Blocked: ${reason}` : "Blocked by
|
|
2548
|
+
const summary = kind === "policy_allow" ? "Allowed." : kind === "policy_prompt" ? "Waiting for permission to run this tool." : reason ? `Blocked: ${reason}` : "Blocked by Policy.";
|
|
2549
2549
|
const callId = `policy:${args.toolCallId}:${node_crypto.randomUUID().slice(0, 8)}`;
|
|
2550
2550
|
const payload = {
|
|
2551
2551
|
kind,
|
|
@@ -2610,13 +2610,13 @@ class PermissionHandler {
|
|
|
2610
2610
|
if (args.reason === "docs_index_read_required") {
|
|
2611
2611
|
return {
|
|
2612
2612
|
uiReason: "read the game Documentation index before making edits.",
|
|
2613
|
-
modelMessage: "Blocked by
|
|
2613
|
+
modelMessage: "Blocked by Policy: read the game Documentation index before making edits.\nNext: call `mcp__flockbay__docs_index_read`, then retry the edit."
|
|
2614
2614
|
};
|
|
2615
2615
|
}
|
|
2616
2616
|
if (args.reason === "ledger_read_required") {
|
|
2617
2617
|
return {
|
|
2618
2618
|
uiReason: "read the ledger before making file edits.",
|
|
2619
|
-
modelMessage: "Blocked by
|
|
2619
|
+
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."
|
|
2620
2620
|
};
|
|
2621
2621
|
}
|
|
2622
2622
|
if (args.reason === "file_claim_required") {
|
|
@@ -2624,13 +2624,13 @@ class PermissionHandler {
|
|
|
2624
2624
|
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.";
|
|
2625
2625
|
return {
|
|
2626
2626
|
uiReason: display,
|
|
2627
|
-
modelMessage: `Blocked by
|
|
2627
|
+
modelMessage: `Blocked by Policy: ${display}
|
|
2628
2628
|
${next}`
|
|
2629
2629
|
};
|
|
2630
2630
|
}
|
|
2631
2631
|
return {
|
|
2632
2632
|
uiReason: "this session is in read-only mode.",
|
|
2633
|
-
modelMessage: "Blocked by
|
|
2633
|
+
modelMessage: "Blocked by Policy: this session is in read-only mode.\nNext: switch permission mode to allow edits, then retry."
|
|
2634
2634
|
};
|
|
2635
2635
|
}
|
|
2636
2636
|
enforceCoordinationGate(toolName, input) {
|
|
@@ -7195,7 +7195,7 @@ async function uploadScreenshotViewsForSession(args) {
|
|
|
7195
7195
|
const buf = await fs$2.readFile(v.path);
|
|
7196
7196
|
const filename = path.basename(v.path);
|
|
7197
7197
|
const contentType = (() => {
|
|
7198
|
-
const mime = detectImageMimeTypeFromBuffer(buf);
|
|
7198
|
+
const mime = detectImageMimeTypeFromBuffer$1(buf);
|
|
7199
7199
|
if (mime) return mime;
|
|
7200
7200
|
throw new Error(`Unsupported screenshot format (expected PNG/JPEG): ${v.path}`);
|
|
7201
7201
|
})();
|
|
@@ -7239,7 +7239,7 @@ async function uploadScreenshotViewsForSession(args) {
|
|
|
7239
7239
|
}
|
|
7240
7240
|
return out;
|
|
7241
7241
|
}
|
|
7242
|
-
function detectImageMimeTypeFromBuffer(buf) {
|
|
7242
|
+
function detectImageMimeTypeFromBuffer$1(buf) {
|
|
7243
7243
|
if (!buf || buf.length < 12) return null;
|
|
7244
7244
|
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) {
|
|
7245
7245
|
return "image/png";
|
|
@@ -7255,6 +7255,48 @@ function detectImageMimeTypeFromBuffer(buf) {
|
|
|
7255
7255
|
}
|
|
7256
7256
|
return null;
|
|
7257
7257
|
}
|
|
7258
|
+
async function readFileHeader(filePath, bytes) {
|
|
7259
|
+
const fh = await fs$2.open(filePath, "r");
|
|
7260
|
+
try {
|
|
7261
|
+
const maxBytes = Math.max(1, Math.floor(bytes));
|
|
7262
|
+
const buf = Buffer.allocUnsafe(maxBytes);
|
|
7263
|
+
const { bytesRead } = await fh.read(buf, 0, maxBytes, 0);
|
|
7264
|
+
return buf.subarray(0, bytesRead);
|
|
7265
|
+
} finally {
|
|
7266
|
+
try {
|
|
7267
|
+
await fh.close();
|
|
7268
|
+
} catch {
|
|
7269
|
+
}
|
|
7270
|
+
}
|
|
7271
|
+
}
|
|
7272
|
+
async function normalizeScreenshotPathExtensionToMatchBytes(filePath) {
|
|
7273
|
+
const abs = String(filePath || "").trim();
|
|
7274
|
+
if (!abs) return { path: abs, changed: false };
|
|
7275
|
+
if (!fs.existsSync(abs)) return { path: abs, changed: false };
|
|
7276
|
+
const lower = abs.toLowerCase();
|
|
7277
|
+
const header = await readFileHeader(abs, 16);
|
|
7278
|
+
const mime = detectImageMimeTypeFromBuffer$1(header);
|
|
7279
|
+
if (!mime) {
|
|
7280
|
+
return { path: abs, changed: false, detail: "unknown_image_format" };
|
|
7281
|
+
}
|
|
7282
|
+
if (mime === "image/jpeg" && lower.endsWith(".png")) {
|
|
7283
|
+
const next = abs.replace(/\.png$/i, ".jpg");
|
|
7284
|
+
if (fs.existsSync(next)) {
|
|
7285
|
+
throw new Error(`Screenshot already exists at normalized path: ${next}`);
|
|
7286
|
+
}
|
|
7287
|
+
await fs$2.rename(abs, next);
|
|
7288
|
+
return { path: next, changed: true, detail: "renamed_png_to_jpg" };
|
|
7289
|
+
}
|
|
7290
|
+
if (mime === "image/png" && (lower.endsWith(".jpg") || lower.endsWith(".jpeg"))) {
|
|
7291
|
+
const next = abs.replace(/\.(jpg|jpeg)$/i, ".png");
|
|
7292
|
+
if (fs.existsSync(next)) {
|
|
7293
|
+
throw new Error(`Screenshot already exists at normalized path: ${next}`);
|
|
7294
|
+
}
|
|
7295
|
+
await fs$2.rename(abs, next);
|
|
7296
|
+
return { path: next, changed: true, detail: "renamed_jpg_to_png" };
|
|
7297
|
+
}
|
|
7298
|
+
return { path: abs, changed: false };
|
|
7299
|
+
}
|
|
7258
7300
|
async function startFlockbayServer(client, options) {
|
|
7259
7301
|
const handler = async (title) => {
|
|
7260
7302
|
types.logger.debug("[flockbayMCP] Changing title to:", title);
|
|
@@ -8851,7 +8893,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
8851
8893
|
throw new Error(`Image too large (${st.size} bytes) to embed: ${abs}`);
|
|
8852
8894
|
}
|
|
8853
8895
|
const buf = await fs$2.readFile(abs);
|
|
8854
|
-
const mime = detectImageMimeTypeFromBuffer(buf);
|
|
8896
|
+
const mime = detectImageMimeTypeFromBuffer$1(buf);
|
|
8855
8897
|
if (!mime) {
|
|
8856
8898
|
throw new Error(`Unsupported image format (expected PNG/JPEG): ${abs}`);
|
|
8857
8899
|
}
|
|
@@ -8901,7 +8943,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
8901
8943
|
);
|
|
8902
8944
|
mcp.registerTool("unreal_latest_screenshots", {
|
|
8903
8945
|
title: "Latest Unreal Screenshots (Validation)",
|
|
8904
|
-
description: "Fetch the latest PNG
|
|
8946
|
+
description: "Fetch the latest screenshots (PNG/JPG) from `Saved/Screenshots/Flockbay/` (for validation) and return a `{ views: [...] }` payload so the app can display them.",
|
|
8905
8947
|
inputSchema: {
|
|
8906
8948
|
uprojectPath: z.z.string().describe("Absolute path to the .uproject file."),
|
|
8907
8949
|
limit: z.z.number().int().positive().optional().describe("Max number of screenshots to return (default 12)."),
|
|
@@ -8938,7 +8980,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
8938
8980
|
};
|
|
8939
8981
|
}
|
|
8940
8982
|
const files = await fs$2.readdir(outDir);
|
|
8941
|
-
const candidates = files.filter((f) =>
|
|
8983
|
+
const candidates = files.filter((f) => /\.(png|jpg|jpeg)$/i.test(f));
|
|
8942
8984
|
if (candidates.length === 0) {
|
|
8943
8985
|
return {
|
|
8944
8986
|
content: [
|
|
@@ -9193,10 +9235,26 @@ ${String(st.stdout || "").trim()}`
|
|
|
9193
9235
|
const pluginInfoWasCached = Boolean(unrealMcpPluginInfoCache);
|
|
9194
9236
|
const pluginInfo = type !== "get_plugin_info" ? await getUnrealMcpPluginInfoBestEffort(timeoutMs) : null;
|
|
9195
9237
|
const response = await types.sendUnrealMcpTcpCommand({ type, params, timeoutMs });
|
|
9238
|
+
let screenshotNormalizationNote = null;
|
|
9239
|
+
if (type === "take_screenshot") {
|
|
9240
|
+
const responseObj = response && typeof response === "object" ? response : null;
|
|
9241
|
+
const candidate = responseObj && typeof responseObj.filepath === "string" ? responseObj : responseObj && responseObj.result && typeof responseObj.result === "object" && typeof responseObj.result.filepath === "string" ? responseObj.result : null;
|
|
9242
|
+
if (candidate && typeof candidate.filepath === "string") {
|
|
9243
|
+
const before = String(candidate.filepath || "").trim();
|
|
9244
|
+
if (before) {
|
|
9245
|
+
const normalized = await normalizeScreenshotPathExtensionToMatchBytes(before);
|
|
9246
|
+
if (normalized.changed) {
|
|
9247
|
+
candidate.filepath = normalized.path;
|
|
9248
|
+
screenshotNormalizationNote = `Normalized screenshot path (${normalized.detail || "extension_fixed"}): ${before} \u2192 ${normalized.path}`;
|
|
9249
|
+
}
|
|
9250
|
+
}
|
|
9251
|
+
}
|
|
9252
|
+
}
|
|
9196
9253
|
unrealEditorSupervisor.noteUnrealReachable();
|
|
9197
9254
|
return {
|
|
9198
9255
|
content: [
|
|
9199
9256
|
{ type: "text", text: `UnrealMCP command ok: ${type}` },
|
|
9257
|
+
...screenshotNormalizationNote ? [{ type: "text", text: screenshotNormalizationNote }] : [],
|
|
9200
9258
|
{ type: "text", text: JSON.stringify(response, null, 2) },
|
|
9201
9259
|
...pluginInfo && !pluginInfoWasCached ? [{ type: "text", text: formatUnrealMcpCapabilities(pluginInfo) }] : []
|
|
9202
9260
|
],
|
|
@@ -11856,6 +11914,269 @@ async function handleConnectVendor(vendor, displayName, flags) {
|
|
|
11856
11914
|
}
|
|
11857
11915
|
}
|
|
11858
11916
|
|
|
11917
|
+
function readArgValue$1(args, key) {
|
|
11918
|
+
const idx = args.indexOf(key);
|
|
11919
|
+
if (idx === -1) return null;
|
|
11920
|
+
const value = args[idx + 1];
|
|
11921
|
+
if (!value || value.startsWith("-")) return null;
|
|
11922
|
+
return value;
|
|
11923
|
+
}
|
|
11924
|
+
function splitList(value) {
|
|
11925
|
+
if (!value) return [];
|
|
11926
|
+
return value.split(/[;,]/g).map((v) => v.trim()).filter(Boolean);
|
|
11927
|
+
}
|
|
11928
|
+
function ensureDir(dir) {
|
|
11929
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
11930
|
+
}
|
|
11931
|
+
function detectImageMimeTypeFromBuffer(buf) {
|
|
11932
|
+
if (!buf || buf.length < 12) return null;
|
|
11933
|
+
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) {
|
|
11934
|
+
return "image/png";
|
|
11935
|
+
}
|
|
11936
|
+
if (buf[0] === 255 && buf[1] === 216 && buf[2] === 255) return "image/jpeg";
|
|
11937
|
+
if (buf[0] === 71 && buf[1] === 73 && buf[2] === 70 && buf[3] === 56) return "image/gif";
|
|
11938
|
+
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) {
|
|
11939
|
+
return "image/webp";
|
|
11940
|
+
}
|
|
11941
|
+
return null;
|
|
11942
|
+
}
|
|
11943
|
+
async function waitForUnreal(options) {
|
|
11944
|
+
const startedAt = Date.now();
|
|
11945
|
+
let lastErr = null;
|
|
11946
|
+
while (Date.now() - startedAt < options.timeoutMs) {
|
|
11947
|
+
try {
|
|
11948
|
+
const res = await types.sendUnrealMcpTcpCommand({ type: "ping", host: options.host, port: options.port, timeoutMs: 2e3 });
|
|
11949
|
+
const msg = typeof res?.message === "string" ? res.message : null;
|
|
11950
|
+
if (msg === "pong") return { ok: true };
|
|
11951
|
+
} catch (err) {
|
|
11952
|
+
lastErr = err instanceof Error ? err.message : String(err);
|
|
11953
|
+
}
|
|
11954
|
+
await new Promise((r) => setTimeout(r, 750));
|
|
11955
|
+
}
|
|
11956
|
+
return {
|
|
11957
|
+
ok: false,
|
|
11958
|
+
error: `Timed out waiting for UnrealMCP ping after ${options.timeoutMs}ms.${lastErr ? ` Last error: ${lastErr}` : ""}`
|
|
11959
|
+
};
|
|
11960
|
+
}
|
|
11961
|
+
function resolveUnrealEditorExe(engineRoot) {
|
|
11962
|
+
const exe = process.platform === "win32" ? path.join(engineRoot, "Engine", "Binaries", "Win64", "UnrealEditor.exe") : path.join(engineRoot, "Engine", "Binaries", process.platform === "darwin" ? "Mac" : "Linux", "UnrealEditor");
|
|
11963
|
+
return exe;
|
|
11964
|
+
}
|
|
11965
|
+
async function runRuntimeSmoke(options) {
|
|
11966
|
+
const editorExe = resolveUnrealEditorExe(options.engineRoot);
|
|
11967
|
+
if (!fs.existsSync(editorExe)) {
|
|
11968
|
+
return { ok: false, error: `Missing UnrealEditor executable: ${editorExe}` };
|
|
11969
|
+
}
|
|
11970
|
+
if (!fs.existsSync(options.projectPath)) {
|
|
11971
|
+
return { ok: false, error: `Missing .uproject: ${options.projectPath}` };
|
|
11972
|
+
}
|
|
11973
|
+
let child = null;
|
|
11974
|
+
const ping = await waitForUnreal({ host: options.host, port: options.port, timeoutMs: 2e3 }).catch((e) => ({ ok: false, error: String(e) }));
|
|
11975
|
+
if (!ping.ok && options.launchIfNeeded) {
|
|
11976
|
+
const args = [
|
|
11977
|
+
options.projectPath,
|
|
11978
|
+
"-NoSplash",
|
|
11979
|
+
"-NoSound",
|
|
11980
|
+
"-nop4"
|
|
11981
|
+
];
|
|
11982
|
+
child = node_child_process.spawn(editorExe, args, {
|
|
11983
|
+
stdio: "ignore",
|
|
11984
|
+
detached: false
|
|
11985
|
+
});
|
|
11986
|
+
}
|
|
11987
|
+
const ready = await waitForUnreal({ host: options.host, port: options.port, timeoutMs: options.connectTimeoutMs });
|
|
11988
|
+
if (!ready.ok) {
|
|
11989
|
+
try {
|
|
11990
|
+
child?.kill();
|
|
11991
|
+
} catch {
|
|
11992
|
+
}
|
|
11993
|
+
return ready;
|
|
11994
|
+
}
|
|
11995
|
+
try {
|
|
11996
|
+
const pluginInfo = await types.sendUnrealMcpTcpCommand({ type: "get_plugin_info", host: options.host, port: options.port, timeoutMs: options.timeoutMs });
|
|
11997
|
+
const createdBy = String(pluginInfo?.createdBy || "").trim();
|
|
11998
|
+
const friendlyName = String(pluginInfo?.friendlyName || "").trim();
|
|
11999
|
+
const baseDir = String(pluginInfo?.baseDir || "").trim();
|
|
12000
|
+
const schemaVersion = Number(pluginInfo?.schemaVersion);
|
|
12001
|
+
const commands = Array.isArray(pluginInfo?.commands) ? pluginInfo.commands.filter((c) => typeof c === "string") : [];
|
|
12002
|
+
if (friendlyName !== "Flockbay MCP" || createdBy !== "Respaced Inc.") {
|
|
12003
|
+
return {
|
|
12004
|
+
ok: false,
|
|
12005
|
+
error: `Unexpected plugin identity loaded by Unreal.
|
|
12006
|
+
friendlyName=${friendlyName || "(missing)"} createdBy=${createdBy || "(missing)"}
|
|
12007
|
+
baseDir=${baseDir || "(missing)"}
|
|
12008
|
+
Expected FriendlyName="Flockbay MCP" CreatedBy="Respaced Inc."`
|
|
12009
|
+
};
|
|
12010
|
+
}
|
|
12011
|
+
if (!Number.isFinite(schemaVersion) || schemaVersion <= 0) {
|
|
12012
|
+
return { ok: false, error: `Invalid schemaVersion from get_plugin_info: ${String(pluginInfo?.schemaVersion)}` };
|
|
12013
|
+
}
|
|
12014
|
+
const requireCommands = [
|
|
12015
|
+
"ping",
|
|
12016
|
+
"get_plugin_info",
|
|
12017
|
+
"list_capabilities",
|
|
12018
|
+
"get_command_schema",
|
|
12019
|
+
"get_play_in_editor_status",
|
|
12020
|
+
"play_in_editor_windowed",
|
|
12021
|
+
"stop_play_in_editor",
|
|
12022
|
+
"take_screenshot",
|
|
12023
|
+
"create_blueprint",
|
|
12024
|
+
"compile_blueprint",
|
|
12025
|
+
"map_check"
|
|
12026
|
+
];
|
|
12027
|
+
const missing = requireCommands.filter((c) => !commands.includes(c));
|
|
12028
|
+
if (missing.length > 0) {
|
|
12029
|
+
return { ok: false, error: `Missing required commands in this UnrealMCP build: ${missing.join(", ")}` };
|
|
12030
|
+
}
|
|
12031
|
+
const playStatus0 = await types.sendUnrealMcpTcpCommand({ type: "get_play_in_editor_status", host: options.host, port: options.port, timeoutMs: options.timeoutMs });
|
|
12032
|
+
const isPlaying = Boolean(playStatus0?.isPlaySessionInProgress);
|
|
12033
|
+
if (isPlaying) {
|
|
12034
|
+
await types.sendUnrealMcpTcpCommand({ type: "stop_play_in_editor", host: options.host, port: options.port, timeoutMs: options.timeoutMs });
|
|
12035
|
+
}
|
|
12036
|
+
await types.sendUnrealMcpTcpCommand({ type: "play_in_editor_windowed", host: options.host, port: options.port, timeoutMs: Math.max(options.timeoutMs, 2e4) });
|
|
12037
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
12038
|
+
await types.sendUnrealMcpTcpCommand({ type: "stop_play_in_editor", host: options.host, port: options.port, timeoutMs: Math.max(options.timeoutMs, 2e4) });
|
|
12039
|
+
const projectDir = path.dirname(options.projectPath);
|
|
12040
|
+
const shotsDir = path.join(projectDir, "Saved", "Screenshots", "Flockbay");
|
|
12041
|
+
ensureDir(shotsDir);
|
|
12042
|
+
const shotPath = path.join(shotsDir, `smoke_${Date.now()}.png`);
|
|
12043
|
+
await types.sendUnrealMcpTcpCommand({
|
|
12044
|
+
type: "take_screenshot",
|
|
12045
|
+
params: { filepath: shotPath },
|
|
12046
|
+
host: options.host,
|
|
12047
|
+
port: options.port,
|
|
12048
|
+
timeoutMs: options.timeoutMs
|
|
12049
|
+
});
|
|
12050
|
+
if (!fs.existsSync(shotPath)) return { ok: false, error: `Screenshot did not exist on disk after take_screenshot: ${shotPath}` };
|
|
12051
|
+
const bytes = fs.readFileSync(shotPath);
|
|
12052
|
+
const mime = detectImageMimeTypeFromBuffer(bytes);
|
|
12053
|
+
if (mime !== "image/png") {
|
|
12054
|
+
return { ok: false, error: `Screenshot bytes do not match .png extension (detected ${mime || "unknown"}): ${shotPath}` };
|
|
12055
|
+
}
|
|
12056
|
+
const bpName = `BP_Smoke_${Date.now()}`;
|
|
12057
|
+
await types.sendUnrealMcpTcpCommand({
|
|
12058
|
+
type: "create_blueprint",
|
|
12059
|
+
params: { name: bpName, path: "/Game/FlockbaySmoke/", parent_class: "Actor" },
|
|
12060
|
+
host: options.host,
|
|
12061
|
+
port: options.port,
|
|
12062
|
+
timeoutMs: Math.max(options.timeoutMs, 2e4)
|
|
12063
|
+
});
|
|
12064
|
+
await types.sendUnrealMcpTcpCommand({
|
|
12065
|
+
type: "compile_blueprint",
|
|
12066
|
+
params: { blueprint_name: bpName },
|
|
12067
|
+
host: options.host,
|
|
12068
|
+
port: options.port,
|
|
12069
|
+
timeoutMs: Math.max(options.timeoutMs, 6e4)
|
|
12070
|
+
});
|
|
12071
|
+
await types.sendUnrealMcpTcpCommand({
|
|
12072
|
+
type: "map_check",
|
|
12073
|
+
host: options.host,
|
|
12074
|
+
port: options.port,
|
|
12075
|
+
timeoutMs: Math.max(options.timeoutMs, 3e4)
|
|
12076
|
+
});
|
|
12077
|
+
return { ok: true };
|
|
12078
|
+
} finally {
|
|
12079
|
+
if (options.killAfter && child) {
|
|
12080
|
+
try {
|
|
12081
|
+
if (process.platform === "win32") {
|
|
12082
|
+
node_child_process.spawn("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore" });
|
|
12083
|
+
} else {
|
|
12084
|
+
child.kill();
|
|
12085
|
+
}
|
|
12086
|
+
} catch {
|
|
12087
|
+
}
|
|
12088
|
+
}
|
|
12089
|
+
}
|
|
12090
|
+
}
|
|
12091
|
+
async function runUnrealMcpMatrixSmoke(args) {
|
|
12092
|
+
const engineRoots = splitList(readArgValue$1(args, "--engine-roots")) || [];
|
|
12093
|
+
const engineRootSingle = readArgValue$1(args, "--engine-root");
|
|
12094
|
+
if (engineRootSingle) engineRoots.push(engineRootSingle.trim());
|
|
12095
|
+
const project = readArgValue$1(args, "--project");
|
|
12096
|
+
const host = (readArgValue$1(args, "--host") || "127.0.0.1").trim() || "127.0.0.1";
|
|
12097
|
+
const port = Number(readArgValue$1(args, "--port") || "55557");
|
|
12098
|
+
const connectTimeoutMs = Number(readArgValue$1(args, "--connect-timeout-ms") || "180000");
|
|
12099
|
+
const timeoutMs = Number(readArgValue$1(args, "--timeout-ms") || "30000");
|
|
12100
|
+
const doBuild = !args.includes("--runtime-only");
|
|
12101
|
+
const doRuntime = !args.includes("--build-only");
|
|
12102
|
+
const launch = args.includes("--launch-editor");
|
|
12103
|
+
const killAfter = args.includes("--kill-editor");
|
|
12104
|
+
if (engineRoots.length === 0) {
|
|
12105
|
+
console.error(chalk.red("Missing --engine-root or --engine-roots."));
|
|
12106
|
+
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'));
|
|
12107
|
+
process.exit(1);
|
|
12108
|
+
}
|
|
12109
|
+
if (doRuntime && !project) {
|
|
12110
|
+
console.error(chalk.red("Missing --project (required for runtime smoke)."));
|
|
12111
|
+
process.exit(1);
|
|
12112
|
+
}
|
|
12113
|
+
console.log(chalk.bold("\nUnrealMCP Matrix Smoke\n"));
|
|
12114
|
+
console.log(chalk.gray(`Platform: ${process.platform}`));
|
|
12115
|
+
console.log(chalk.gray(`Host: ${host}:${port}`));
|
|
12116
|
+
console.log(chalk.gray(`Engines: ${engineRoots.join(", ")}`));
|
|
12117
|
+
if (project) console.log(chalk.gray(`Project: ${project}`));
|
|
12118
|
+
console.log(chalk.gray(`Build: ${doBuild ? "yes" : "no"} Runtime: ${doRuntime ? "yes" : "no"} Launch: ${launch ? "yes" : "no"} Kill: ${killAfter ? "yes" : "no"}`));
|
|
12119
|
+
console.log("");
|
|
12120
|
+
const failures = [];
|
|
12121
|
+
for (const engineRootRaw of engineRoots) {
|
|
12122
|
+
const engineRoot = engineRootRaw.trim();
|
|
12123
|
+
if (!engineRoot) continue;
|
|
12124
|
+
console.log(chalk.bold(`== Engine: ${engineRoot} ==`));
|
|
12125
|
+
if (doBuild) {
|
|
12126
|
+
console.log(chalk.cyan("Build: installing UnrealMCP plugin sources..."));
|
|
12127
|
+
const installed = types.installUnrealMcpPluginToEngine(engineRoot);
|
|
12128
|
+
if (!installed.ok) {
|
|
12129
|
+
failures.push({ engineRoot, phase: "build", error: installed.errorMessage });
|
|
12130
|
+
console.log(chalk.red(`Build: failed (install)
|
|
12131
|
+
${installed.errorMessage}
|
|
12132
|
+
`));
|
|
12133
|
+
if (!doRuntime) continue;
|
|
12134
|
+
} else {
|
|
12135
|
+
console.log(chalk.green(`Build: sources installed to ${installed.destDir}`));
|
|
12136
|
+
console.log(chalk.cyan("Build: compiling plugin via RunUAT BuildPlugin..."));
|
|
12137
|
+
const built = await types.buildAndInstallUnrealMcpPlugin({ engineRoot, flockbayHomeDir: types.configuration.flockbayHomeDir });
|
|
12138
|
+
if (!built.ok) {
|
|
12139
|
+
failures.push({ engineRoot, phase: "build", error: built.errorMessage });
|
|
12140
|
+
console.log(chalk.red(`Build: failed
|
|
12141
|
+
${built.errorMessage}
|
|
12142
|
+
`));
|
|
12143
|
+
} else {
|
|
12144
|
+
console.log(chalk.green(`Build: ok (log: ${built.buildLogPath})`));
|
|
12145
|
+
}
|
|
12146
|
+
}
|
|
12147
|
+
}
|
|
12148
|
+
if (doRuntime) {
|
|
12149
|
+
console.log(chalk.cyan("Runtime: running command smoke..."));
|
|
12150
|
+
const res = await runRuntimeSmoke({
|
|
12151
|
+
engineRoot,
|
|
12152
|
+
projectPath: project,
|
|
12153
|
+
host,
|
|
12154
|
+
port,
|
|
12155
|
+
connectTimeoutMs,
|
|
12156
|
+
timeoutMs,
|
|
12157
|
+
launchIfNeeded: launch,
|
|
12158
|
+
killAfter
|
|
12159
|
+
});
|
|
12160
|
+
if (!res.ok) {
|
|
12161
|
+
failures.push({ engineRoot, phase: "runtime", error: res.error });
|
|
12162
|
+
console.log(chalk.red(`Runtime: failed
|
|
12163
|
+
${res.error}
|
|
12164
|
+
`));
|
|
12165
|
+
} else {
|
|
12166
|
+
console.log(chalk.green("Runtime: ok\n"));
|
|
12167
|
+
}
|
|
12168
|
+
}
|
|
12169
|
+
}
|
|
12170
|
+
if (failures.length > 0) {
|
|
12171
|
+
console.error(chalk.red("\nMatrix smoke failed.\n"));
|
|
12172
|
+
for (const f of failures) {
|
|
12173
|
+
console.error(chalk.red(`- ${f.engineRoot} (${f.phase}): ${f.error}`));
|
|
12174
|
+
}
|
|
12175
|
+
process.exit(1);
|
|
12176
|
+
}
|
|
12177
|
+
console.log(chalk.green("\nMatrix smoke passed.\n"));
|
|
12178
|
+
}
|
|
12179
|
+
|
|
11859
12180
|
function readTailUtf8(filePath, maxBytes) {
|
|
11860
12181
|
try {
|
|
11861
12182
|
const stat = fs__namespace.statSync(filePath);
|
|
@@ -12216,6 +12537,16 @@ async function authAndSetupMachineIfNeeded() {
|
|
|
12216
12537
|
}
|
|
12217
12538
|
if (!args.includes("--version")) ;
|
|
12218
12539
|
if (subcommand === "doctor") {
|
|
12540
|
+
if (args[1] === "unreal-mcp-smoke") {
|
|
12541
|
+
try {
|
|
12542
|
+
await runUnrealMcpMatrixSmoke(args.slice(2));
|
|
12543
|
+
} catch (error) {
|
|
12544
|
+
console.error(chalk.red("UnrealMCP smoke failed:"), error instanceof Error ? error.message : String(error));
|
|
12545
|
+
if (process.env.DEBUG) console.error(error);
|
|
12546
|
+
process.exit(1);
|
|
12547
|
+
}
|
|
12548
|
+
return;
|
|
12549
|
+
}
|
|
12219
12550
|
if (args[1] === "clean") {
|
|
12220
12551
|
const result = await killRunawayFlockbayProcesses();
|
|
12221
12552
|
console.log(`Cleaned up ${result.killed} runaway processes`);
|
|
@@ -12327,7 +12658,7 @@ ${engineRoot}`, {
|
|
|
12327
12658
|
} else if (subcommand === "codex") {
|
|
12328
12659
|
try {
|
|
12329
12660
|
await chdirToNearestUprojectRootIfPresent();
|
|
12330
|
-
const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-
|
|
12661
|
+
const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-ozeIEfAo.cjs'); });
|
|
12331
12662
|
let startedBy = void 0;
|
|
12332
12663
|
let sessionId = void 0;
|
|
12333
12664
|
for (let i = 1; i < args.length; i++) {
|
|
@@ -12353,7 +12684,13 @@ ${engineRoot}`, {
|
|
|
12353
12684
|
const geminiSubcommand = args[1];
|
|
12354
12685
|
if (geminiSubcommand === "model" && args[2] === "set" && args[3]) {
|
|
12355
12686
|
const modelName = args[3];
|
|
12356
|
-
const validModels = [
|
|
12687
|
+
const validModels = [
|
|
12688
|
+
"gemini-2.5-pro",
|
|
12689
|
+
"gemini-2.5-flash",
|
|
12690
|
+
"gemini-2.5-flash-lite",
|
|
12691
|
+
"gemini-3-pro-preview",
|
|
12692
|
+
"gemini-3-flash-preview"
|
|
12693
|
+
];
|
|
12357
12694
|
if (!validModels.includes(modelName)) {
|
|
12358
12695
|
console.error(`Invalid model: ${modelName}`);
|
|
12359
12696
|
console.error(`Available models: ${validModels.join(", ")}`);
|
|
@@ -12422,7 +12759,7 @@ ${engineRoot}`, {
|
|
|
12422
12759
|
}
|
|
12423
12760
|
try {
|
|
12424
12761
|
await chdirToNearestUprojectRootIfPresent();
|
|
12425
|
-
const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-
|
|
12762
|
+
const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-B-sAKY5u.cjs'); });
|
|
12426
12763
|
let startedBy = void 0;
|
|
12427
12764
|
let sessionId = void 0;
|
|
12428
12765
|
for (let i = 1; i < args.length; i++) {
|
|
@@ -12762,7 +13099,6 @@ exports.autoFinalizeCoordinationWorkItem = autoFinalizeCoordinationWorkItem;
|
|
|
12762
13099
|
exports.buildProjectCapsule = buildProjectCapsule;
|
|
12763
13100
|
exports.consumeToolQuota = consumeToolQuota;
|
|
12764
13101
|
exports.detectScreenshotsForGate = detectScreenshotsForGate;
|
|
12765
|
-
exports.detectUnrealProject = detectUnrealProject;
|
|
12766
13102
|
exports.extractUserImagesMarker = extractUserImagesMarker;
|
|
12767
13103
|
exports.formatQuotaDeniedReason = formatQuotaDeniedReason;
|
|
12768
13104
|
exports.getLatestUserImages = getLatestUserImages;
|