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.
@@ -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-DeH24uWs.cjs');
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 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.
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-Bhkn02hu.cjs', document.baseURI).href)));
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 policy.";
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 policy: read the game Documentation index before making edits.\nNext: call `mcp__flockbay__docs_index_read`, then retry the edit."
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 policy: read the ledger before making file edits.\nNext: call `mcp__flockbay__ledger_read` (or `mcp__flockbay__coordination_ledger_snapshot`), then retry the edit."
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 policy: ${display}
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 policy: this session is in read-only mode.\nNext: switch permission mode to allow edits, then retry."
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 screenshots from `Saved/Screenshots/Flockbay/` (for validation) and return a `{ views: [...] }` payload so the app can display them.",
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) => f.toLowerCase().endsWith(".png"));
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-CH4lz1QX.cjs'); });
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 = ["gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite"];
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-DNSymY04.cjs'); });
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;