flockbay 0.10.17 → 0.10.20
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/codex/flockbayMcpStdioBridge.cjs +8 -0
- package/dist/codex/flockbayMcpStdioBridge.mjs +8 -0
- package/dist/{index-CHm9r89K.mjs → index-CX0Z8pmz.mjs} +292 -25
- package/dist/{index-BxBuBx7C.cjs → index-D_mglYG0.cjs} +292 -25
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +2 -8
- package/dist/lib.d.mts +2 -8
- package/dist/lib.mjs +1 -1
- package/dist/{runCodex-DudVDqNh.mjs → runCodex-Biis9GFw.mjs} +9 -3
- package/dist/{runCodex-DuCGwO2K.cjs → runCodex-CXJW0tzo.cjs} +9 -3
- package/dist/{runGemini-Ddu8UCOS.mjs → runGemini-BSH4b0wu.mjs} +2 -2
- package/dist/{runGemini-B25LZ4Cw.cjs → runGemini-FOBXtEU6.cjs} +2 -2
- package/dist/{types-CGQhv7Z-.cjs → types-BYHCKlu_.cjs} +4 -3
- package/dist/{types-DuhcLxar.mjs → types-C4QeUggl.mjs} +3 -3
- package/package.json +1 -1
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPCommandSchema.cpp +10 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPEditorCommands.cpp +178 -7
|
@@ -614,6 +614,14 @@ async function main() {
|
|
|
614
614
|
"Unreal Editor: Launch Project",
|
|
615
615
|
"Launch Unreal Editor for a given .uproject (no auto-restart). If the editor later crashes or becomes unreachable, Flockbay will abort the current agent run and report it in the chat."
|
|
616
616
|
);
|
|
617
|
+
forwardTool(
|
|
618
|
+
"unreal_editor_relaunch_last",
|
|
619
|
+
{
|
|
620
|
+
extraArgs: z.z.array(z.z.string()).optional().describe("Optional replacement UnrealEditor command-line args (advanced).")
|
|
621
|
+
},
|
|
622
|
+
"Unreal Editor: Relaunch Last Project",
|
|
623
|
+
"Relaunch the last Unreal project previously launched via unreal_editor_launch in this session (no auto-restart). Use this after a crash once you\u2019ve fixed files."
|
|
624
|
+
);
|
|
617
625
|
forwardTool(
|
|
618
626
|
"unreal_headless_screenshot",
|
|
619
627
|
{
|
|
@@ -612,6 +612,14 @@ async function main() {
|
|
|
612
612
|
"Unreal Editor: Launch Project",
|
|
613
613
|
"Launch Unreal Editor for a given .uproject (no auto-restart). If the editor later crashes or becomes unreachable, Flockbay will abort the current agent run and report it in the chat."
|
|
614
614
|
);
|
|
615
|
+
forwardTool(
|
|
616
|
+
"unreal_editor_relaunch_last",
|
|
617
|
+
{
|
|
618
|
+
extraArgs: z.array(z.string()).optional().describe("Optional replacement UnrealEditor command-line args (advanced).")
|
|
619
|
+
},
|
|
620
|
+
"Unreal Editor: Relaunch Last Project",
|
|
621
|
+
"Relaunch the last Unreal project previously launched via unreal_editor_launch in this session (no auto-restart). Use this after a crash once you\u2019ve fixed files."
|
|
622
|
+
);
|
|
615
623
|
forwardTool(
|
|
616
624
|
"unreal_headless_screenshot",
|
|
617
625
|
{
|
|
@@ -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 getLatestDaemonLog } from './types-
|
|
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 getLatestDaemonLog, x as normalizeServerUrlForNode } from './types-C4QeUggl.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,
|
|
11
|
+
import fs$1, { readFile, access as access$1, mkdir, readdir, stat } 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';
|
|
@@ -4581,7 +4581,13 @@ async function checkIfDaemonRunningAndCleanupStaleState() {
|
|
|
4581
4581
|
return false;
|
|
4582
4582
|
}
|
|
4583
4583
|
}
|
|
4584
|
-
|
|
4584
|
+
const lockPid = readDaemonLockPid();
|
|
4585
|
+
if (!lockPid) return false;
|
|
4586
|
+
if (!isProcessAlive(lockPid)) {
|
|
4587
|
+
await cleanupDaemonState();
|
|
4588
|
+
return false;
|
|
4589
|
+
}
|
|
4590
|
+
return true;
|
|
4585
4591
|
}
|
|
4586
4592
|
async function isDaemonRunningCurrentCliVersion() {
|
|
4587
4593
|
logger.debug("[DAEMON CONTROL] Checking if daemon is running same version");
|
|
@@ -4637,7 +4643,43 @@ async function stopDaemon() {
|
|
|
4637
4643
|
await cleanupDaemonState();
|
|
4638
4644
|
return;
|
|
4639
4645
|
}
|
|
4640
|
-
|
|
4646
|
+
const lockPid = readDaemonLockPid();
|
|
4647
|
+
if (!lockPid) {
|
|
4648
|
+
logger.debug("No daemon state found");
|
|
4649
|
+
return;
|
|
4650
|
+
}
|
|
4651
|
+
if (!isProcessAlive(lockPid)) {
|
|
4652
|
+
logger.debug("[DAEMON RUN] Daemon lock PID not running, cleaning up state");
|
|
4653
|
+
await cleanupDaemonState();
|
|
4654
|
+
return;
|
|
4655
|
+
}
|
|
4656
|
+
const cmd = readProcessCommand(lockPid);
|
|
4657
|
+
const looksLikeDaemon = looksLikeFlockbayDaemonCommand(cmd, configuration.profile);
|
|
4658
|
+
if (!looksLikeDaemon) {
|
|
4659
|
+
logger.debug("[DAEMON RUN] Daemon lock is held by an unexpected process; refusing to kill", {
|
|
4660
|
+
lockPid,
|
|
4661
|
+
cmd: cmd || null
|
|
4662
|
+
});
|
|
4663
|
+
return;
|
|
4664
|
+
}
|
|
4665
|
+
logger.debug(`[DAEMON RUN] Stopping daemon by lock PID ${lockPid} (state file missing)`);
|
|
4666
|
+
try {
|
|
4667
|
+
process.kill(lockPid, "SIGTERM");
|
|
4668
|
+
} catch (error) {
|
|
4669
|
+
logger.debug("[DAEMON RUN] Failed to SIGTERM lock PID", { lockPid, error });
|
|
4670
|
+
}
|
|
4671
|
+
try {
|
|
4672
|
+
await waitForProcessDeath(lockPid, 2e3);
|
|
4673
|
+
} catch {
|
|
4674
|
+
}
|
|
4675
|
+
if (isProcessAlive(lockPid)) {
|
|
4676
|
+
try {
|
|
4677
|
+
process.kill(lockPid, "SIGKILL");
|
|
4678
|
+
} catch (error) {
|
|
4679
|
+
logger.debug("[DAEMON RUN] Failed to SIGKILL lock PID", { lockPid, error });
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
await cleanupDaemonState();
|
|
4641
4683
|
} catch (error) {
|
|
4642
4684
|
logger.debug("Error stopping daemon", error);
|
|
4643
4685
|
}
|
|
@@ -4654,6 +4696,39 @@ async function waitForProcessDeath(pid, timeout) {
|
|
|
4654
4696
|
}
|
|
4655
4697
|
throw new Error("Process did not die within timeout");
|
|
4656
4698
|
}
|
|
4699
|
+
function isProcessAlive(pid) {
|
|
4700
|
+
try {
|
|
4701
|
+
process.kill(pid, 0);
|
|
4702
|
+
return true;
|
|
4703
|
+
} catch {
|
|
4704
|
+
return false;
|
|
4705
|
+
}
|
|
4706
|
+
}
|
|
4707
|
+
function readDaemonLockPid() {
|
|
4708
|
+
try {
|
|
4709
|
+
const raw = readFileSync$1(configuration.daemonLockFile, "utf-8").trim();
|
|
4710
|
+
const pid = Number(raw);
|
|
4711
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
4712
|
+
} catch {
|
|
4713
|
+
return null;
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
function readProcessCommand(pid) {
|
|
4717
|
+
try {
|
|
4718
|
+
return String(execFileSync("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8" }) || "").trim();
|
|
4719
|
+
} catch {
|
|
4720
|
+
return null;
|
|
4721
|
+
}
|
|
4722
|
+
}
|
|
4723
|
+
function looksLikeFlockbayDaemonCommand(command, profile) {
|
|
4724
|
+
const cmd = String(command || "").trim();
|
|
4725
|
+
if (!cmd) return false;
|
|
4726
|
+
if (!cmd.includes("flockbay")) return false;
|
|
4727
|
+
if (!cmd.includes("daemon")) return false;
|
|
4728
|
+
if (!cmd.includes("start-sync")) return false;
|
|
4729
|
+
if (!cmd.includes("--profile")) return false;
|
|
4730
|
+
return cmd.includes(profile);
|
|
4731
|
+
}
|
|
4657
4732
|
|
|
4658
4733
|
async function findAllFlockbayProcesses() {
|
|
4659
4734
|
try {
|
|
@@ -4938,8 +5013,8 @@ async function loginWithClerkAndPairMachine() {
|
|
|
4938
5013
|
logger.debug("[AUTH] Starting Clerk-based CLI login + machine pairing");
|
|
4939
5014
|
const settings = await updateSettings(async (s) => {
|
|
4940
5015
|
const machineId2 = s.machineId || randomUUID();
|
|
4941
|
-
const serverUrl2 =
|
|
4942
|
-
const webappUrl =
|
|
5016
|
+
const serverUrl2 = configuration.serverUrl;
|
|
5017
|
+
const webappUrl = configuration.webappUrl;
|
|
4943
5018
|
return { ...s, machineId: machineId2, serverUrl: serverUrl2, webappUrl };
|
|
4944
5019
|
});
|
|
4945
5020
|
const serverUrl = configuration.serverUrl.replace(/\/+$/, "");
|
|
@@ -7133,7 +7208,8 @@ async function startFlockbayServer(client, options) {
|
|
|
7133
7208
|
lastReachableAtMs: 0,
|
|
7134
7209
|
lastIssueAtMs: 0,
|
|
7135
7210
|
lastIssueKey: "",
|
|
7136
|
-
launched: null
|
|
7211
|
+
launched: null,
|
|
7212
|
+
lastLaunch: null
|
|
7137
7213
|
};
|
|
7138
7214
|
const emitIssue = (event) => {
|
|
7139
7215
|
const key = `${event.kind}:${event.severity}:${event.message}`;
|
|
@@ -7158,6 +7234,93 @@ async function startFlockbayServer(client, options) {
|
|
|
7158
7234
|
}
|
|
7159
7235
|
}
|
|
7160
7236
|
};
|
|
7237
|
+
const tailText = (text, maxChars) => {
|
|
7238
|
+
const t = String(text || "").trim();
|
|
7239
|
+
if (!t) return "";
|
|
7240
|
+
if (t.length <= maxChars) return t;
|
|
7241
|
+
return t.slice(t.length - maxChars);
|
|
7242
|
+
};
|
|
7243
|
+
const findLatestFile = async (dir, filter) => {
|
|
7244
|
+
try {
|
|
7245
|
+
const entries = await readdir(dir);
|
|
7246
|
+
let best = null;
|
|
7247
|
+
for (const name of entries) {
|
|
7248
|
+
if (!filter(name)) continue;
|
|
7249
|
+
const full = path.join(dir, name);
|
|
7250
|
+
let st;
|
|
7251
|
+
try {
|
|
7252
|
+
st = await stat(full);
|
|
7253
|
+
} catch {
|
|
7254
|
+
continue;
|
|
7255
|
+
}
|
|
7256
|
+
if (!st?.isFile?.()) continue;
|
|
7257
|
+
const mtimeMs = Number(st.mtimeMs || 0);
|
|
7258
|
+
if (!best || mtimeMs > best.mtimeMs) best = { path: full, mtimeMs };
|
|
7259
|
+
}
|
|
7260
|
+
return best?.path ?? null;
|
|
7261
|
+
} catch {
|
|
7262
|
+
return null;
|
|
7263
|
+
}
|
|
7264
|
+
};
|
|
7265
|
+
const findLatestCrashDir = async (projectRoot) => {
|
|
7266
|
+
const crashesDir = path.join(projectRoot, "Saved", "Crashes");
|
|
7267
|
+
try {
|
|
7268
|
+
const entries = await readdir(crashesDir);
|
|
7269
|
+
let best = null;
|
|
7270
|
+
for (const name of entries) {
|
|
7271
|
+
const full = path.join(crashesDir, name);
|
|
7272
|
+
let st;
|
|
7273
|
+
try {
|
|
7274
|
+
st = await stat(full);
|
|
7275
|
+
} catch {
|
|
7276
|
+
continue;
|
|
7277
|
+
}
|
|
7278
|
+
if (!st?.isDirectory?.()) continue;
|
|
7279
|
+
const mtimeMs = Number(st.mtimeMs || 0);
|
|
7280
|
+
if (!best || mtimeMs > best.mtimeMs) best = { path: full, mtimeMs };
|
|
7281
|
+
}
|
|
7282
|
+
return best?.path ?? null;
|
|
7283
|
+
} catch {
|
|
7284
|
+
return null;
|
|
7285
|
+
}
|
|
7286
|
+
};
|
|
7287
|
+
const readFileTail = async (filePath, maxBytes) => {
|
|
7288
|
+
try {
|
|
7289
|
+
const buf = await readFile(filePath);
|
|
7290
|
+
const slice = buf.length > maxBytes ? buf.subarray(buf.length - maxBytes) : buf;
|
|
7291
|
+
return slice.toString("utf8");
|
|
7292
|
+
} catch {
|
|
7293
|
+
return null;
|
|
7294
|
+
}
|
|
7295
|
+
};
|
|
7296
|
+
const gatherCrashDiagnosticsBestEffort = async (uprojectPath) => {
|
|
7297
|
+
const projectRoot = path.dirname(uprojectPath);
|
|
7298
|
+
const summary = [];
|
|
7299
|
+
const detail = { uprojectPath, projectRoot };
|
|
7300
|
+
const latestLog = await findLatestFile(path.join(projectRoot, "Saved", "Logs"), (n) => n.toLowerCase().endsWith(".log"));
|
|
7301
|
+
if (latestLog) {
|
|
7302
|
+
detail.projectLogPath = latestLog;
|
|
7303
|
+
const tail = await readFileTail(latestLog, 24e3);
|
|
7304
|
+
if (tail) {
|
|
7305
|
+
detail.projectLogTail = tailText(tail, 12e3);
|
|
7306
|
+
summary.push(`Latest log: ${latestLog}`);
|
|
7307
|
+
}
|
|
7308
|
+
}
|
|
7309
|
+
const latestCrashDir = await findLatestCrashDir(projectRoot);
|
|
7310
|
+
if (latestCrashDir) {
|
|
7311
|
+
detail.latestCrashDir = latestCrashDir;
|
|
7312
|
+
const crashContext = await findLatestFile(latestCrashDir, (n) => n.toLowerCase().includes("crashcontext") && n.toLowerCase().endsWith(".xml"));
|
|
7313
|
+
if (crashContext) {
|
|
7314
|
+
detail.crashContextPath = crashContext;
|
|
7315
|
+
const tail = await readFileTail(crashContext, 24e3);
|
|
7316
|
+
if (tail) {
|
|
7317
|
+
detail.crashContextTail = tailText(tail, 12e3);
|
|
7318
|
+
summary.push(`CrashContext: ${crashContext}`);
|
|
7319
|
+
}
|
|
7320
|
+
}
|
|
7321
|
+
}
|
|
7322
|
+
return { detail, summary };
|
|
7323
|
+
};
|
|
7161
7324
|
const getUnrealEditorExe = (engineRoot) => {
|
|
7162
7325
|
const root = engineRoot.trim().replace(/[\\/]+$/, "");
|
|
7163
7326
|
if (process.platform === "darwin") {
|
|
@@ -7213,7 +7376,7 @@ ${res.stderr}`;
|
|
|
7213
7376
|
kind: "unreachable",
|
|
7214
7377
|
severity: "warning",
|
|
7215
7378
|
detectedAtMs: now,
|
|
7216
|
-
message: "Unreal Editor is no longer reachable (it was reachable earlier). It may have crashed or been closed.",
|
|
7379
|
+
message: "Unreal Editor is no longer reachable (it was reachable earlier). It may have crashed or been closed.\nNext: fix the issue, then relaunch via unreal_editor_relaunch_last (if you launched from this session) or unreal_editor_launch.",
|
|
7217
7380
|
detail: {
|
|
7218
7381
|
lastReachableAtMs: state.lastReachableAtMs
|
|
7219
7382
|
}
|
|
@@ -7245,20 +7408,35 @@ ${res.stderr}`;
|
|
|
7245
7408
|
engineRoot,
|
|
7246
7409
|
startedAtMs: Date.now()
|
|
7247
7410
|
};
|
|
7411
|
+
state.lastLaunch = {
|
|
7412
|
+
uprojectPath,
|
|
7413
|
+
engineRoot,
|
|
7414
|
+
extraArgs,
|
|
7415
|
+
startedAtMs: Date.now()
|
|
7416
|
+
};
|
|
7248
7417
|
child.on("exit", (code, signal) => {
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
|
|
7418
|
+
void (async () => {
|
|
7419
|
+
const now = Date.now();
|
|
7420
|
+
const exitCode = typeof code === "number" ? code : null;
|
|
7421
|
+
const sig = typeof signal === "string" ? signal : null;
|
|
7422
|
+
const isCrash = sig !== null || exitCode !== null && exitCode !== 0;
|
|
7423
|
+
state.launched = null;
|
|
7424
|
+
if (!isCrash) return;
|
|
7425
|
+
const diag = await gatherCrashDiagnosticsBestEffort(uprojectPath).catch(() => ({ detail: {}, summary: [] }));
|
|
7426
|
+
const msgParts = [
|
|
7427
|
+
`Unreal Editor process exited unexpectedly (code=${exitCode ?? "null"} signal=${sig ?? "null"}).`,
|
|
7428
|
+
`Project: ${uprojectPath}`,
|
|
7429
|
+
...diag.summary,
|
|
7430
|
+
`Next: fix the issue, then relaunch via unreal_editor_relaunch_last (or unreal_editor_launch).`
|
|
7431
|
+
];
|
|
7432
|
+
emitIssue({
|
|
7433
|
+
kind: "process_exit",
|
|
7434
|
+
severity: "crash",
|
|
7435
|
+
detectedAtMs: now,
|
|
7436
|
+
message: msgParts.filter(Boolean).join("\n"),
|
|
7437
|
+
detail: { exitCode, signal: sig, pid, uprojectPath, ...diag.detail }
|
|
7438
|
+
});
|
|
7439
|
+
})();
|
|
7262
7440
|
});
|
|
7263
7441
|
child.on("error", (err) => {
|
|
7264
7442
|
const now = Date.now();
|
|
@@ -7267,7 +7445,9 @@ ${res.stderr}`;
|
|
|
7267
7445
|
kind: "process_exit",
|
|
7268
7446
|
severity: "crash",
|
|
7269
7447
|
detectedAtMs: now,
|
|
7270
|
-
message: `Failed to launch Unreal Editor: ${err instanceof Error ? err.message : String(err)}
|
|
7448
|
+
message: `Failed to launch Unreal Editor: ${err instanceof Error ? err.message : String(err)}
|
|
7449
|
+
Project: ${uprojectPath}
|
|
7450
|
+
Next: fix the issue, then relaunch via unreal_editor_relaunch_last (or unreal_editor_launch).`,
|
|
7271
7451
|
detail: { pid, uprojectPath }
|
|
7272
7452
|
});
|
|
7273
7453
|
});
|
|
@@ -7277,6 +7457,12 @@ ${res.stderr}`;
|
|
|
7277
7457
|
noteUnrealActivity,
|
|
7278
7458
|
noteUnrealReachable,
|
|
7279
7459
|
launchEditor,
|
|
7460
|
+
relaunchLast: async (extraArgs) => {
|
|
7461
|
+
const last = state.lastLaunch;
|
|
7462
|
+
if (!last) throw new Error("No known prior Unreal launch in this session. Use unreal_editor_launch with an explicit uprojectPath.");
|
|
7463
|
+
const mergedArgs = Array.isArray(extraArgs) && extraArgs.length > 0 ? extraArgs.filter((a) => typeof a === "string" && a.trim()) : last.extraArgs;
|
|
7464
|
+
return launchEditor({ uprojectPath: last.uprojectPath, engineRoot: last.engineRoot, extraArgs: mergedArgs });
|
|
7465
|
+
},
|
|
7280
7466
|
stop: () => {
|
|
7281
7467
|
try {
|
|
7282
7468
|
interval.unref();
|
|
@@ -8783,6 +8969,32 @@ ${String(st.stdout || "").trim()}`
|
|
|
8783
8969
|
isError: false
|
|
8784
8970
|
};
|
|
8785
8971
|
}));
|
|
8972
|
+
mcp.registerTool("unreal_editor_relaunch_last", {
|
|
8973
|
+
title: "Unreal Editor: Relaunch Last Project",
|
|
8974
|
+
description: "Relaunch the last Unreal project previously launched via unreal_editor_launch in this session (no auto-restart). Use this after a crash once you\u2019ve fixed files. If it crashes again, Flockbay will abort and report again.",
|
|
8975
|
+
inputSchema: {
|
|
8976
|
+
extraArgs: z.array(z.string()).optional().describe("Optional replacement UnrealEditor command-line args (advanced).")
|
|
8977
|
+
}
|
|
8978
|
+
}, async (args) => runWithMcpToolCard("unreal_editor_relaunch_last", args, async () => {
|
|
8979
|
+
const extraArgs = Array.isArray(args?.extraArgs) ? args.extraArgs : void 0;
|
|
8980
|
+
unrealEditorSupervisor.noteUnrealActivity();
|
|
8981
|
+
try {
|
|
8982
|
+
const launched = await unrealEditorSupervisor.relaunchLast(extraArgs);
|
|
8983
|
+
return {
|
|
8984
|
+
content: [
|
|
8985
|
+
{ type: "text", text: `Relaunched Unreal Editor (last project).` },
|
|
8986
|
+
{ type: "text", text: JSON.stringify({ pid: launched.pid, exePath: launched.exePath }, null, 2) },
|
|
8987
|
+
{ type: "text", text: "Next: wait for the editor to finish loading, then re-run UnrealMCP tools." }
|
|
8988
|
+
],
|
|
8989
|
+
isError: false
|
|
8990
|
+
};
|
|
8991
|
+
} catch (err) {
|
|
8992
|
+
return {
|
|
8993
|
+
content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
|
|
8994
|
+
isError: true
|
|
8995
|
+
};
|
|
8996
|
+
}
|
|
8997
|
+
}));
|
|
8786
8998
|
mcp.registerTool("unreal_build_project", {
|
|
8787
8999
|
title: "Unreal Build Project (UBT)",
|
|
8788
9000
|
description: "Build the project via Unreal Build Tool (via Engine/Build/BatchFiles/Build.*). Returns structured errors (file/line) and a log path for deep debugging.",
|
|
@@ -10208,6 +10420,7 @@ Fix: ${res.hint}` : "";
|
|
|
10208
10420
|
"unreal_headless_screenshot",
|
|
10209
10421
|
"unreal_latest_screenshots",
|
|
10210
10422
|
"unreal_editor_launch",
|
|
10423
|
+
"unreal_editor_relaunch_last",
|
|
10211
10424
|
"unreal_build_project",
|
|
10212
10425
|
"unreal_mcp_command",
|
|
10213
10426
|
"unreal_mcp_list_capabilities",
|
|
@@ -11575,6 +11788,51 @@ async function reauthForCurrentServerKeepingMachineId() {
|
|
|
11575
11788
|
await clearCredentials();
|
|
11576
11789
|
await loginWithClerkAndPairMachine();
|
|
11577
11790
|
}
|
|
11791
|
+
function isLocalDevServerUrl(url) {
|
|
11792
|
+
try {
|
|
11793
|
+
const u = new URL(String(url || "").trim());
|
|
11794
|
+
const host = u.hostname.toLowerCase();
|
|
11795
|
+
return host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host.endsWith(".localhost");
|
|
11796
|
+
} catch {
|
|
11797
|
+
return false;
|
|
11798
|
+
}
|
|
11799
|
+
}
|
|
11800
|
+
async function isServerReachable(url) {
|
|
11801
|
+
const base = String(url || "").trim().replace(/\/+$/, "");
|
|
11802
|
+
if (!base) return false;
|
|
11803
|
+
try {
|
|
11804
|
+
const res = await fetch(`${base}/healthz`, { method: "GET", signal: AbortSignal.timeout(1200) });
|
|
11805
|
+
return res.ok;
|
|
11806
|
+
} catch {
|
|
11807
|
+
return false;
|
|
11808
|
+
}
|
|
11809
|
+
}
|
|
11810
|
+
async function ensureProdServerWhenLocalDevUnreachable() {
|
|
11811
|
+
if ((process.env.FLOCKBAY_SERVER_URL || "").trim()) return;
|
|
11812
|
+
const settings = await readSettings().catch(() => null);
|
|
11813
|
+
const configured = String(settings?.serverUrl || "").trim();
|
|
11814
|
+
if (!configured) return;
|
|
11815
|
+
const normalized = normalizeServerUrlForNode(configured);
|
|
11816
|
+
if (!isLocalDevServerUrl(normalized)) return;
|
|
11817
|
+
const reachable = await isServerReachable(normalized);
|
|
11818
|
+
if (reachable) return;
|
|
11819
|
+
const shouldSwitch = await promptYesNo(
|
|
11820
|
+
`This profile is configured to use a local server (${normalized}), but it isn't reachable.
|
|
11821
|
+
|
|
11822
|
+
Switch this profile to production (https://api.flockbay.com) and re-authenticate?`,
|
|
11823
|
+
{ defaultYes: true }
|
|
11824
|
+
);
|
|
11825
|
+
if (!shouldSwitch) return;
|
|
11826
|
+
const nextWebappUrl = String(settings?.webappUrl || "").trim() || "https://flockbay.com";
|
|
11827
|
+
await updateSettings((s) => ({
|
|
11828
|
+
...s,
|
|
11829
|
+
serverUrl: "https://api.flockbay.com",
|
|
11830
|
+
webappUrl: nextWebappUrl
|
|
11831
|
+
}));
|
|
11832
|
+
configuration.serverUrl = "https://api.flockbay.com";
|
|
11833
|
+
configuration.webappUrl = nextWebappUrl;
|
|
11834
|
+
await reauthForCurrentServerKeepingMachineId();
|
|
11835
|
+
}
|
|
11578
11836
|
function openUrlBestEffort(url) {
|
|
11579
11837
|
const u = String(url || "").trim();
|
|
11580
11838
|
if (!u) return;
|
|
@@ -11693,11 +11951,19 @@ async function startDaemonDetachedOrExit(opts) {
|
|
|
11693
11951
|
}
|
|
11694
11952
|
console.error("");
|
|
11695
11953
|
console.error(chalk.red("Daemon is running but not connected to the server."));
|
|
11954
|
+
console.error(chalk.gray(`Profile: ${configuration.profile}`));
|
|
11955
|
+
console.error(chalk.gray(`Server: ${configuration.serverUrl}`));
|
|
11696
11956
|
if (typeof status?.connection?.lastHttpUpsertError === "string" && status.connection.lastHttpUpsertError.trim()) {
|
|
11697
11957
|
console.error(chalk.gray(`Last upsert error: ${status.connection.lastHttpUpsertError.trim()}`));
|
|
11698
11958
|
}
|
|
11699
11959
|
if (lastConnectError) console.error(chalk.gray(`Last connect error: ${lastConnectError}`));
|
|
11700
|
-
|
|
11960
|
+
if (isLocalDevServerUrl(configuration.serverUrl)) {
|
|
11961
|
+
console.error(chalk.gray("Tip: if you meant to run against production, set the server URL or use a prod profile:"));
|
|
11962
|
+
console.error(chalk.gray(" FLOCKBAY_SERVER_URL=https://api.flockbay.com flockbay start"));
|
|
11963
|
+
console.error(chalk.gray(" flockbay start --profile prod"));
|
|
11964
|
+
} else {
|
|
11965
|
+
console.error(chalk.gray("Tip: if the backend is restarting, wait a moment and re-run `flockbay start`."));
|
|
11966
|
+
}
|
|
11701
11967
|
process.exit(1);
|
|
11702
11968
|
}
|
|
11703
11969
|
}
|
|
@@ -11901,6 +12167,7 @@ async function authAndSetupMachineIfNeeded() {
|
|
|
11901
12167
|
return;
|
|
11902
12168
|
}
|
|
11903
12169
|
try {
|
|
12170
|
+
await ensureProdServerWhenLocalDevUnreachable();
|
|
11904
12171
|
await ensureMachineAuthOrLogin();
|
|
11905
12172
|
const skipUnreal = startArgs.includes("--skip-unreal");
|
|
11906
12173
|
if (!skipUnreal) {
|
|
@@ -11935,7 +12202,7 @@ ${engineRoot}`, {
|
|
|
11935
12202
|
} else if (subcommand === "codex") {
|
|
11936
12203
|
try {
|
|
11937
12204
|
await chdirToNearestUprojectRootIfPresent();
|
|
11938
|
-
const { runCodex } = await import('./runCodex-
|
|
12205
|
+
const { runCodex } = await import('./runCodex-Biis9GFw.mjs');
|
|
11939
12206
|
let startedBy = void 0;
|
|
11940
12207
|
let sessionId = void 0;
|
|
11941
12208
|
for (let i = 1; i < args.length; i++) {
|
|
@@ -12030,7 +12297,7 @@ ${engineRoot}`, {
|
|
|
12030
12297
|
}
|
|
12031
12298
|
try {
|
|
12032
12299
|
await chdirToNearestUprojectRootIfPresent();
|
|
12033
|
-
const { runGemini } = await import('./runGemini-
|
|
12300
|
+
const { runGemini } = await import('./runGemini-BSH4b0wu.mjs');
|
|
12034
12301
|
let startedBy = void 0;
|
|
12035
12302
|
let sessionId = void 0;
|
|
12036
12303
|
for (let i = 1; i < args.length; i++) {
|