flockbay 0.10.30 → 0.10.32
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-DGp07Mik.mjs → index-D0SVmCGD.mjs} +247 -98
- package/dist/{index-b9kiPsW3.cjs → index-v_Eyd-J9.cjs} +177 -28
- 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-B0JRo8YP.mjs → runCodex-F93OWlPh.mjs} +58 -28
- package/dist/{runCodex-Bq_-aYHm.cjs → runCodex-Jf5LkfEO.cjs} +39 -9
- package/dist/{runGemini-DO9xzjyY.mjs → runGemini-Cvo7a4eh.mjs} +54 -32
- package/dist/{runGemini-pQgdPy5x.cjs → runGemini-Du8A3Lzi.cjs} +39 -17
- package/dist/{types-DdJKBH6T.mjs → types-6KOeU7L8.mjs} +30 -30
- package/dist/{types-DPBm2ycs.cjs → types-hWvKGEw7.cjs} +2 -2
- package/package.json +1 -1
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPEditorCommands.cpp +103 -18
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import chalk from 'chalk';
|
|
2
|
-
import
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import os__default, { homedir } from 'node:os';
|
|
3
4
|
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 buildAndInstallUnrealMcpPlugin, x as getLatestDaemonLog, y as normalizeServerUrlForNode } from './types-
|
|
5
|
+
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-6KOeU7L8.mjs';
|
|
5
6
|
import { spawn, execFileSync, execSync } from 'node:child_process';
|
|
6
|
-
import
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import path__default, { resolve, join, dirname } from 'node:path';
|
|
7
9
|
import { createInterface } from 'node:readline';
|
|
8
10
|
import * as fs from 'node:fs';
|
|
9
11
|
import fs__default, { existsSync, readFileSync, mkdirSync, readdirSync, accessSync, constants, statSync, createReadStream, writeFileSync, unlinkSync } from 'node:fs';
|
|
@@ -323,6 +325,10 @@ const PLATFORM_SYSTEM_PROMPT = trimIdent(`
|
|
|
323
325
|
- The platform may detect that Unreal Editor crashed/unreachable and will automatically abort your current run and post a chat message.
|
|
324
326
|
- When that happens: STOP UnrealMCP calls, fix the project as needed, then (only when ready) relaunch Unreal Editor with \`mcp__flockbay__unreal_editor_launch\`.
|
|
325
327
|
- Do not \u201Cauto-restart\u201D in a loop. Relaunch is a deliberate action after fixes.
|
|
328
|
+
- After launching Unreal Editor, do not \u201Crapid-retry\u201D reachability checks. Boot times vary by machine and project size.
|
|
329
|
+
- Prefer readiness checks (editor health / plugin reachable) spaced by increasing waits (bounded backoff).
|
|
330
|
+
- Default example ladder for editor boot: wait ~30s, then ~45s, then ~60s between checks (cap any single wait to 60s).
|
|
331
|
+
- Total cap: at most 240s of waiting for editor boot. If still not reachable, stop waiting and ask the user to tell you when the editor finishes booting (or to share what it shows on-screen).
|
|
326
332
|
|
|
327
333
|
## B) Screenshots (via UnrealMCP)
|
|
328
334
|
Use UnrealMCP when the user asks for:
|
|
@@ -370,6 +376,24 @@ const PLATFORM_SYSTEM_PROMPT = trimIdent(`
|
|
|
370
376
|
- Batch related questions into one call when possible.
|
|
371
377
|
- If a user's request is too broad/unclear, ask him questions to clarify the scope and narrow down the work
|
|
372
378
|
|
|
379
|
+
# Timing / Waiting (realistic, increasing, bounded)
|
|
380
|
+
|
|
381
|
+
User machines vary widely (CPU, disk, GPU) and Unreal projects can be large. When you are waiting for something to become ready (editor boot, local server start, build finishing), you must use realistic waits.
|
|
382
|
+
|
|
383
|
+
- Prefer readiness checks over blind sleeps. Use sleeps only to space checks.
|
|
384
|
+
- Do not \u201Crapid-retry\u201D after starting heavyweight actions (editors, IDEs, emulators, big builds). Seconds-scale retry loops are a bad UX.
|
|
385
|
+
- Use bounded backoff:
|
|
386
|
+
- waits must increase between attempts (e.g. 10s \u2192 20s \u2192 40s \u2192 60s \u2026)
|
|
387
|
+
- cap any single wait to 60s
|
|
388
|
+
- cap total waiting to a reasonable maximum
|
|
389
|
+
|
|
390
|
+
Two tiers (keep it simple):
|
|
391
|
+
- Standard actions total cap: ~90s
|
|
392
|
+
- Heavyweight boots total cap: ~240s
|
|
393
|
+
|
|
394
|
+
If you expect something to take longer than the applicable total cap, do not pretend you can wait \u201Ca bit longer\u201D.
|
|
395
|
+
Stop waiting, explain what you\u2019re waiting for and what \u201Cready\u201D looks like, and ask the user to notify you when it completes.
|
|
396
|
+
|
|
373
397
|
# Platform-provided context blocks
|
|
374
398
|
|
|
375
399
|
The platform may prefix the user message with internal context blocks (machine/project hints, image markers, etc).
|
|
@@ -1987,9 +2011,9 @@ function resolveCandidatePath(candidate, cwd) {
|
|
|
1987
2011
|
if (!raw) return raw;
|
|
1988
2012
|
if (raw.startsWith("~/")) {
|
|
1989
2013
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
1990
|
-
if (home) return
|
|
2014
|
+
if (home) return path__default.join(home, raw.slice(2));
|
|
1991
2015
|
}
|
|
1992
|
-
return
|
|
2016
|
+
return path__default.isAbsolute(raw) ? raw : path__default.resolve(cwd, raw);
|
|
1993
2017
|
}
|
|
1994
2018
|
function isImageBlock(block) {
|
|
1995
2019
|
if (!block || typeof block !== "object") return false;
|
|
@@ -2025,7 +2049,7 @@ function extractScreenshotViewsFromText(text, cwd) {
|
|
|
2025
2049
|
const token = String(match[1] || "");
|
|
2026
2050
|
const resolved = resolveCandidatePath(token, cwd);
|
|
2027
2051
|
if (!resolved) continue;
|
|
2028
|
-
if (!resolved.includes(`${
|
|
2052
|
+
if (!resolved.includes(`${path__default.sep}Saved${path__default.sep}Screenshots${path__default.sep}Flockbay${path__default.sep}`) && !resolved.includes("Saved/Screenshots/Flockbay/") && !resolved.includes("Saved\\Screenshots\\Flockbay\\")) {
|
|
2029
2053
|
continue;
|
|
2030
2054
|
}
|
|
2031
2055
|
uniqPush(out, seen, resolved);
|
|
@@ -2035,7 +2059,7 @@ function extractScreenshotViewsFromText(text, cwd) {
|
|
|
2035
2059
|
for (const match of text.matchAll(filenameRe)) {
|
|
2036
2060
|
const filename = normalizeFilePathToken(String(match[1] || ""));
|
|
2037
2061
|
if (!filename) continue;
|
|
2038
|
-
const rel =
|
|
2062
|
+
const rel = path__default.join("Saved", "Screenshots", "Flockbay", filename);
|
|
2039
2063
|
const resolved = resolveCandidatePath(rel, cwd);
|
|
2040
2064
|
uniqPush(out, seen, resolved);
|
|
2041
2065
|
}
|
|
@@ -2304,7 +2328,7 @@ async function claudeRemote(opts) {
|
|
|
2304
2328
|
)) {
|
|
2305
2329
|
didEmitAuthDiagnostic = true;
|
|
2306
2330
|
const homeEnv = process.env.HOME ? String(process.env.HOME) : "";
|
|
2307
|
-
const homedir =
|
|
2331
|
+
const homedir = os__default.homedir();
|
|
2308
2332
|
const forcedClaudePath = (process.env.FLOCKBAY_CLAUDE_PATH || process.env.FLOCKBAY_CLAUDE_CLI_PATH || "").trim();
|
|
2309
2333
|
const pathHead = String(process.env.PATH || "").split(process.platform === "win32" ? ";" : ":").filter(Boolean).slice(0, 8).join(process.platform === "win32" ? ";" : ":");
|
|
2310
2334
|
const diag = [
|
|
@@ -2319,7 +2343,7 @@ async function claudeRemote(opts) {
|
|
|
2319
2343
|
"If you have multiple Claude installs, Flockbay remote prefers npm-global, then Homebrew, then native installer.",
|
|
2320
2344
|
"You can force a specific install by setting `FLOCKBAY_CLAUDE_PATH` to the desired `cli.js` or binary path.",
|
|
2321
2345
|
"",
|
|
2322
|
-
`Diagnostics: user=${
|
|
2346
|
+
`Diagnostics: user=${os__default.userInfo().username} platform=${process.platform} pid=${process.pid}`,
|
|
2323
2347
|
`Diagnostics: HOME(env)=${homeEnv || "(unset)"} os.homedir()=${homedir || "(unknown)"}`,
|
|
2324
2348
|
`Diagnostics: FLOCKBAY_CLAUDE_PATH=${forcedClaudePath || "(unset)"}`,
|
|
2325
2349
|
`Diagnostics: PATH(head)=${pathHead || "(unset)"}`,
|
|
@@ -2570,11 +2594,11 @@ class PermissionHandler {
|
|
|
2570
2594
|
const raw = typeof filePath === "string" ? filePath.trim() : String(filePath ?? "").trim();
|
|
2571
2595
|
if (!raw) return null;
|
|
2572
2596
|
let candidate = raw.replace(/\\/g, "/");
|
|
2573
|
-
if (
|
|
2597
|
+
if (path__default.isAbsolute(candidate)) {
|
|
2574
2598
|
const base = String(this.session.path || "").trim();
|
|
2575
2599
|
if (base) {
|
|
2576
|
-
const rel =
|
|
2577
|
-
if (rel && !rel.startsWith("..") && !
|
|
2600
|
+
const rel = path__default.relative(base, candidate);
|
|
2601
|
+
if (rel && !rel.startsWith("..") && !path__default.isAbsolute(rel)) {
|
|
2578
2602
|
candidate = rel.replace(/\\/g, "/");
|
|
2579
2603
|
}
|
|
2580
2604
|
}
|
|
@@ -3561,6 +3585,14 @@ function truncateCommitSubject(value, maxLen) {
|
|
|
3561
3585
|
if (trimmed.length <= maxLen) return trimmed;
|
|
3562
3586
|
return `${trimmed.slice(0, Math.max(0, maxLen - 1)).trimEnd()}\u2026`;
|
|
3563
3587
|
}
|
|
3588
|
+
function normalizeFsPath(p) {
|
|
3589
|
+
const resolved = path.resolve(String(p || ""));
|
|
3590
|
+
const root = path.parse(resolved).root;
|
|
3591
|
+
let normalized = resolved;
|
|
3592
|
+
if (normalized !== root) normalized = normalized.replace(/[\\/]+$/, "");
|
|
3593
|
+
if (process.platform === "win32") normalized = normalized.toLowerCase();
|
|
3594
|
+
return normalized;
|
|
3595
|
+
}
|
|
3564
3596
|
async function runGit(args, cwd, timeoutMs) {
|
|
3565
3597
|
const child = spawn("git", args, {
|
|
3566
3598
|
cwd,
|
|
@@ -3605,11 +3637,11 @@ async function runGit(args, cwd, timeoutMs) {
|
|
|
3605
3637
|
const exitCode = result.timedOut ? -1 : result.exitCode;
|
|
3606
3638
|
return { ok: !result.timedOut && exitCode === 0, stdout: out, stderr: err, exitCode };
|
|
3607
3639
|
}
|
|
3608
|
-
async function postJson(
|
|
3640
|
+
async function postJson(path2, token, body) {
|
|
3609
3641
|
const base = configuration.serverUrl.replace(/\/+$/, "");
|
|
3610
3642
|
const timeoutMsRaw = String(process.env.FLOCKBAY_COORDINATION_HTTP_TIMEOUT_MS || "").trim();
|
|
3611
3643
|
const timeoutMs = Number.isFinite(Number(timeoutMsRaw)) && Number(timeoutMsRaw) > 0 ? Number(timeoutMsRaw) : 7e3;
|
|
3612
|
-
const url = `${base}${
|
|
3644
|
+
const url = `${base}${path2}`;
|
|
3613
3645
|
const res = await fetch(url, {
|
|
3614
3646
|
method: "POST",
|
|
3615
3647
|
headers: {
|
|
@@ -3639,6 +3671,26 @@ async function autoFinalizeCoordinationWorkItem(params) {
|
|
|
3639
3671
|
if (!inRepo.ok || String(inRepo.stdout || "").trim().toLowerCase() !== "true") {
|
|
3640
3672
|
return { ok: false, error: "not_a_git_repo" };
|
|
3641
3673
|
}
|
|
3674
|
+
const topLevel = await runGit(["rev-parse", "--show-toplevel"], cwd, 1e4);
|
|
3675
|
+
if (!topLevel.ok) {
|
|
3676
|
+
return { ok: false, error: topLevel.stderr || topLevel.stdout || "git_show_toplevel_failed" };
|
|
3677
|
+
}
|
|
3678
|
+
const repoRootRaw = String(topLevel.stdout || "").trim().split("\n")[0] || "";
|
|
3679
|
+
const repoRoot = normalizeFsPath(repoRootRaw);
|
|
3680
|
+
const homeDir = normalizeFsPath(os.homedir());
|
|
3681
|
+
const fsRoot = normalizeFsPath(path.parse(repoRootRaw || repoRoot).root);
|
|
3682
|
+
if (repoRoot && repoRoot === homeDir) {
|
|
3683
|
+
return {
|
|
3684
|
+
ok: false,
|
|
3685
|
+
error: `unsafe_git_repo_root: repo root is your home directory (${repoRootRaw || repoRoot}). This usually means there's an accidental .git directory in your home folder. Fix by removing/renaming that .git and initializing git inside your project folder.`
|
|
3686
|
+
};
|
|
3687
|
+
}
|
|
3688
|
+
if (repoRoot && repoRoot === fsRoot) {
|
|
3689
|
+
return {
|
|
3690
|
+
ok: false,
|
|
3691
|
+
error: `unsafe_git_repo_root: repo root is the filesystem root (${repoRootRaw || repoRoot}). Auto-finalize refuses to run to avoid scanning massive folders.`
|
|
3692
|
+
};
|
|
3693
|
+
}
|
|
3642
3694
|
const porcelain = await runGit(["status", "--porcelain=v1", "-z"], cwd, 2e4);
|
|
3643
3695
|
if (!porcelain.ok) {
|
|
3644
3696
|
return { ok: false, error: porcelain.stderr || porcelain.stdout || "git_status_failed" };
|
|
@@ -3700,6 +3752,28 @@ async function autoFinalizeCoordinationWorkItem(params) {
|
|
|
3700
3752
|
|
|
3701
3753
|
async function claudeRemoteLauncher(session) {
|
|
3702
3754
|
logger.debug("[claudeRemoteLauncher] Starting remote launcher");
|
|
3755
|
+
const coordinationEnabled = Boolean(String(process.env.FLOCKBAY_COORDINATION_WORKSPACE_PROJECT_ID || "").trim() && String(process.env.FLOCKBAY_COORDINATION_WORK_ITEM_ID || "").trim());
|
|
3756
|
+
let lastAutoFinalizeStatus = null;
|
|
3757
|
+
const publishAutoFinalizeStatus = (next) => {
|
|
3758
|
+
if (!coordinationEnabled) return false;
|
|
3759
|
+
if (lastAutoFinalizeStatus && lastAutoFinalizeStatus.status === next.status && lastAutoFinalizeStatus.error === next.error) {
|
|
3760
|
+
return false;
|
|
3761
|
+
}
|
|
3762
|
+
lastAutoFinalizeStatus = next;
|
|
3763
|
+
try {
|
|
3764
|
+
session.client.updateMetadata((metadata) => ({
|
|
3765
|
+
...metadata || {},
|
|
3766
|
+
coordinationAutoFinalize: {
|
|
3767
|
+
status: next.status,
|
|
3768
|
+
error: next.error,
|
|
3769
|
+
updatedAt: Date.now()
|
|
3770
|
+
}
|
|
3771
|
+
}));
|
|
3772
|
+
} catch (e) {
|
|
3773
|
+
logger.debug("[remote]: Failed to publish coordination auto-finalize status", e);
|
|
3774
|
+
}
|
|
3775
|
+
return true;
|
|
3776
|
+
};
|
|
3703
3777
|
const isLikelyAuthOrUsageError = (error) => {
|
|
3704
3778
|
const message = error instanceof Error ? error.message : String(error);
|
|
3705
3779
|
return /usage limit|quota|rate limit|resource exhausted|resource_exhausted|oauth token has expired|please run \/login|unauthenticated|unauthorized|forbidden|invalid (session )?token|needs authentication|setup-token|status 401|status 403|status 429|\b401\b|\b403\b|\b429\b/i.test(message);
|
|
@@ -3993,14 +4067,19 @@ async function claudeRemoteLauncher(session) {
|
|
|
3993
4067
|
summary: lastDeliveredMessage?.message ?? null
|
|
3994
4068
|
});
|
|
3995
4069
|
if (!finalizeRes.ok) {
|
|
3996
|
-
const
|
|
3997
|
-
|
|
3998
|
-
|
|
4070
|
+
const err = String(finalizeRes.error || "").trim() || "auto_finalize_failed";
|
|
4071
|
+
if (publishAutoFinalizeStatus({ status: "error", error: err })) {
|
|
4072
|
+
messageBuffer.addMessage(`Auto-finalize failed: ${err}`, "status");
|
|
4073
|
+
}
|
|
4074
|
+
} else {
|
|
4075
|
+
publishAutoFinalizeStatus({ status: "ok", error: null });
|
|
3999
4076
|
}
|
|
4000
4077
|
} catch (err) {
|
|
4001
|
-
const
|
|
4002
|
-
|
|
4003
|
-
|
|
4078
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
4079
|
+
const errorMsg = String(m || "").trim() || "auto_finalize_failed";
|
|
4080
|
+
if (publishAutoFinalizeStatus({ status: "error", error: errorMsg })) {
|
|
4081
|
+
messageBuffer.addMessage(`Auto-finalize failed: ${errorMsg}`, "status");
|
|
4082
|
+
}
|
|
4004
4083
|
}
|
|
4005
4084
|
}
|
|
4006
4085
|
if (!pending && session.queue.size() === 0) {
|
|
@@ -5126,7 +5205,7 @@ async function loginWithClerkAndPairMachine() {
|
|
|
5126
5205
|
console.log(chalk.bold("Flockbay CLI login"));
|
|
5127
5206
|
console.log(chalk.gray(`Profile: ${configuration.profile}`));
|
|
5128
5207
|
console.log(chalk.gray(`Server: ${configuration.serverUrl}`));
|
|
5129
|
-
console.log(chalk.gray(`Machine: ${machineId} (${
|
|
5208
|
+
console.log(chalk.gray(`Machine: ${machineId} (${os__default.hostname()})`));
|
|
5130
5209
|
console.log("");
|
|
5131
5210
|
console.log("Open this link to sign in with Clerk and approve this machine:");
|
|
5132
5211
|
console.log(approveUrl);
|
|
@@ -5222,8 +5301,8 @@ function startUnrealMcpServerForDaemon(options) {
|
|
|
5222
5301
|
return { ok: false, errorMessage: `Missing Unreal MCP Python folder: ${pythonDir}` };
|
|
5223
5302
|
}
|
|
5224
5303
|
const flockbayHomeDir = options.flockbayHomeDir;
|
|
5225
|
-
const runtimeDir =
|
|
5226
|
-
const logPath =
|
|
5304
|
+
const runtimeDir = path__default.join(flockbayHomeDir, "unreal-mcp");
|
|
5305
|
+
const logPath = path__default.join(runtimeDir, "unreal_mcp.log");
|
|
5227
5306
|
try {
|
|
5228
5307
|
fs__default.mkdirSync(runtimeDir, { recursive: true });
|
|
5229
5308
|
} catch (err) {
|
|
@@ -5231,7 +5310,7 @@ function startUnrealMcpServerForDaemon(options) {
|
|
|
5231
5310
|
return { ok: false, errorMessage: `Failed to create runtime folder: ${runtimeDir}
|
|
5232
5311
|
Error: ${message}` };
|
|
5233
5312
|
}
|
|
5234
|
-
const uvProjectEnv =
|
|
5313
|
+
const uvProjectEnv = path__default.join(runtimeDir, "uv-venv");
|
|
5235
5314
|
const child = spawn("uv", ["run", "unreal_mcp_server.py"], {
|
|
5236
5315
|
cwd: pythonDir,
|
|
5237
5316
|
env: {
|
|
@@ -5492,9 +5571,9 @@ async function findNearestUprojectRoot(startDir) {
|
|
|
5492
5571
|
if (current.toLowerCase().endsWith(".uproject")) {
|
|
5493
5572
|
try {
|
|
5494
5573
|
await fs$1.access(current);
|
|
5495
|
-
return
|
|
5574
|
+
return path__default.dirname(current);
|
|
5496
5575
|
} catch {
|
|
5497
|
-
current =
|
|
5576
|
+
current = path__default.dirname(current);
|
|
5498
5577
|
}
|
|
5499
5578
|
}
|
|
5500
5579
|
for (let i = 0; i < 25; i += 1) {
|
|
@@ -5503,7 +5582,7 @@ async function findNearestUprojectRoot(startDir) {
|
|
|
5503
5582
|
if (entries.some((name) => name.toLowerCase().endsWith(".uproject"))) return current;
|
|
5504
5583
|
} catch {
|
|
5505
5584
|
}
|
|
5506
|
-
const parent =
|
|
5585
|
+
const parent = path__default.dirname(current);
|
|
5507
5586
|
if (parent === current) break;
|
|
5508
5587
|
current = parent;
|
|
5509
5588
|
}
|
|
@@ -5514,7 +5593,7 @@ async function findFirstUprojectFile(projectRoot) {
|
|
|
5514
5593
|
const entries = await fs$1.readdir(projectRoot);
|
|
5515
5594
|
const uprojects = entries.filter((name) => name.toLowerCase().endsWith(".uproject")).sort();
|
|
5516
5595
|
if (uprojects.length === 0) return null;
|
|
5517
|
-
return
|
|
5596
|
+
return path__default.join(projectRoot, uprojects[0]);
|
|
5518
5597
|
} catch {
|
|
5519
5598
|
return null;
|
|
5520
5599
|
}
|
|
@@ -6567,9 +6646,9 @@ function targetPlatform() {
|
|
|
6567
6646
|
}
|
|
6568
6647
|
function buildScriptPath(engineRoot) {
|
|
6569
6648
|
if (process.platform === "win32") {
|
|
6570
|
-
return
|
|
6649
|
+
return path__default.join(engineRoot, "Engine", "Build", "BatchFiles", "Build.bat");
|
|
6571
6650
|
}
|
|
6572
|
-
return
|
|
6651
|
+
return path__default.join(engineRoot, "Engine", "Build", "BatchFiles", "Build.sh");
|
|
6573
6652
|
}
|
|
6574
6653
|
function parseBuildIssuesFromText(text, limit) {
|
|
6575
6654
|
const issues = [];
|
|
@@ -6628,7 +6707,7 @@ async function buildUnrealProject(args) {
|
|
|
6628
6707
|
const target = args.target ?? "Editor";
|
|
6629
6708
|
const timeoutMs = Math.max(3e4, args.timeoutMs ?? 20 * 6e4);
|
|
6630
6709
|
const issuesLimit = Math.max(1, Math.min(2e3, args.issuesLimit ?? 250));
|
|
6631
|
-
if (!uprojectPath || !
|
|
6710
|
+
if (!uprojectPath || !path__default.isAbsolute(uprojectPath) || !uprojectPath.toLowerCase().endsWith(".uproject")) {
|
|
6632
6711
|
return {
|
|
6633
6712
|
ok: false,
|
|
6634
6713
|
exitCode: null,
|
|
@@ -6665,12 +6744,12 @@ async function buildUnrealProject(args) {
|
|
|
6665
6744
|
errorMessage: `Missing Unreal build script: ${script}`
|
|
6666
6745
|
};
|
|
6667
6746
|
}
|
|
6668
|
-
const projectName =
|
|
6747
|
+
const projectName = path__default.basename(uprojectPath).replace(/\.uproject$/i, "");
|
|
6669
6748
|
const targetName = target === "Editor" ? `${projectName}Editor` : projectName;
|
|
6670
6749
|
const platform = targetPlatform();
|
|
6671
|
-
const logDir = args.logDir ??
|
|
6750
|
+
const logDir = args.logDir ?? path__default.join(path__default.dirname(uprojectPath), "Saved", "Logs", "Flockbay");
|
|
6672
6751
|
fs__default.mkdirSync(logDir, { recursive: true });
|
|
6673
|
-
const logPath =
|
|
6752
|
+
const logPath = path__default.join(logDir, `flockbay_build_${Date.now()}.log`);
|
|
6674
6753
|
const buildArgs = [
|
|
6675
6754
|
targetName,
|
|
6676
6755
|
platform,
|
|
@@ -6758,9 +6837,9 @@ async function runUnrealSmokeTest(args) {
|
|
|
6758
6837
|
const timeoutMs = Math.max(5e3, args.timeoutMs ?? 3e4);
|
|
6759
6838
|
const stabilizeMs = Math.max(250, args.stabilizeMs ?? 1500);
|
|
6760
6839
|
const stopIfPlaying = args.stopIfPlaying ?? true;
|
|
6761
|
-
const screenshotDir =
|
|
6840
|
+
const screenshotDir = path__default.join(path__default.dirname(args.uprojectPath), "Saved", "Screenshots", "Flockbay");
|
|
6762
6841
|
await mkdir(screenshotDir, { recursive: true });
|
|
6763
|
-
const screenshotPath =
|
|
6842
|
+
const screenshotPath = path__default.join(screenshotDir, `Flockbay_smoke_test_${stampForFilename()}.png`);
|
|
6764
6843
|
let started = false;
|
|
6765
6844
|
let stopped = false;
|
|
6766
6845
|
try {
|
|
@@ -7085,10 +7164,10 @@ async function readUprojectEngineAssociationOrNull(uprojectPath) {
|
|
|
7085
7164
|
function isValidEngineRoot(engineRoot) {
|
|
7086
7165
|
if (!engineRoot) return false;
|
|
7087
7166
|
if (!existsSync(engineRoot)) return false;
|
|
7088
|
-
if (!existsSync(
|
|
7089
|
-
const buildVersion =
|
|
7167
|
+
if (!existsSync(path__default.join(engineRoot, "Engine"))) return false;
|
|
7168
|
+
const buildVersion = path__default.join(engineRoot, "Engine", "Build", "Build.version");
|
|
7090
7169
|
if (existsSync(buildVersion)) return true;
|
|
7091
|
-
const editorCmd = process.platform === "darwin" ?
|
|
7170
|
+
const editorCmd = process.platform === "darwin" ? path__default.join(engineRoot, "Engine", "Binaries", "Mac", "UnrealEditor-Cmd") : process.platform === "win32" ? path__default.join(engineRoot, "Engine", "Binaries", "Win64", "UnrealEditor-Cmd.exe") : process.platform === "linux" ? path__default.join(engineRoot, "Engine", "Binaries", "Linux", "UnrealEditor-Cmd") : "";
|
|
7092
7171
|
if (editorCmd && existsSync(editorCmd)) return true;
|
|
7093
7172
|
return false;
|
|
7094
7173
|
}
|
|
@@ -7109,7 +7188,7 @@ function engineRootCandidatesForVersion(platform, version) {
|
|
|
7109
7188
|
return [];
|
|
7110
7189
|
}
|
|
7111
7190
|
async function readEngineRootMajorMinorOrNull(engineRoot) {
|
|
7112
|
-
const buildVersionPath =
|
|
7191
|
+
const buildVersionPath = path__default.join(engineRoot, "Engine", "Build", "Build.version");
|
|
7113
7192
|
try {
|
|
7114
7193
|
if (existsSync(buildVersionPath)) {
|
|
7115
7194
|
const json = await readJsonFile(buildVersionPath);
|
|
@@ -7253,7 +7332,7 @@ async function uploadScreenshotViewsForSession(args) {
|
|
|
7253
7332
|
const form = new FormData();
|
|
7254
7333
|
for (const v of args.views) {
|
|
7255
7334
|
const buf = await readFile(v.path);
|
|
7256
|
-
const filename =
|
|
7335
|
+
const filename = path__default.basename(v.path);
|
|
7257
7336
|
const contentType = (() => {
|
|
7258
7337
|
const mime = detectImageMimeTypeFromBuffer$1(buf);
|
|
7259
7338
|
if (mime) return mime;
|
|
@@ -7479,7 +7558,7 @@ async function startFlockbayServer(client, options) {
|
|
|
7479
7558
|
let best = null;
|
|
7480
7559
|
for (const name of entries) {
|
|
7481
7560
|
if (!filter(name)) continue;
|
|
7482
|
-
const full =
|
|
7561
|
+
const full = path__default.join(dir, name);
|
|
7483
7562
|
let st;
|
|
7484
7563
|
try {
|
|
7485
7564
|
st = await stat(full);
|
|
@@ -7496,12 +7575,12 @@ async function startFlockbayServer(client, options) {
|
|
|
7496
7575
|
}
|
|
7497
7576
|
};
|
|
7498
7577
|
const findLatestCrashDir = async (projectRoot) => {
|
|
7499
|
-
const crashesDir =
|
|
7578
|
+
const crashesDir = path__default.join(projectRoot, "Saved", "Crashes");
|
|
7500
7579
|
try {
|
|
7501
7580
|
const entries = await readdir(crashesDir);
|
|
7502
7581
|
let best = null;
|
|
7503
7582
|
for (const name of entries) {
|
|
7504
|
-
const full =
|
|
7583
|
+
const full = path__default.join(crashesDir, name);
|
|
7505
7584
|
let st;
|
|
7506
7585
|
try {
|
|
7507
7586
|
st = await stat(full);
|
|
@@ -7527,10 +7606,10 @@ async function startFlockbayServer(client, options) {
|
|
|
7527
7606
|
}
|
|
7528
7607
|
};
|
|
7529
7608
|
const gatherCrashDiagnosticsBestEffort = async (uprojectPath) => {
|
|
7530
|
-
const projectRoot =
|
|
7609
|
+
const projectRoot = path__default.dirname(uprojectPath);
|
|
7531
7610
|
const summary = [];
|
|
7532
7611
|
const detail = { uprojectPath, projectRoot };
|
|
7533
|
-
const latestLog = await findLatestFile(
|
|
7612
|
+
const latestLog = await findLatestFile(path__default.join(projectRoot, "Saved", "Logs"), (n) => n.toLowerCase().endsWith(".log"));
|
|
7534
7613
|
if (latestLog) {
|
|
7535
7614
|
detail.projectLogPath = latestLog;
|
|
7536
7615
|
const tail = await readFileTail(latestLog, 24e3);
|
|
@@ -7557,13 +7636,13 @@ async function startFlockbayServer(client, options) {
|
|
|
7557
7636
|
const getUnrealEditorExe = (engineRoot) => {
|
|
7558
7637
|
const root = engineRoot.trim().replace(/[\\/]+$/, "");
|
|
7559
7638
|
if (process.platform === "darwin") {
|
|
7560
|
-
return
|
|
7639
|
+
return path__default.join(root, "Engine", "Binaries", "Mac", "UnrealEditor.app", "Contents", "MacOS", "UnrealEditor");
|
|
7561
7640
|
}
|
|
7562
7641
|
if (process.platform === "win32") {
|
|
7563
|
-
return
|
|
7642
|
+
return path__default.join(root, "Engine", "Binaries", "Win64", "UnrealEditor.exe");
|
|
7564
7643
|
}
|
|
7565
7644
|
if (process.platform === "linux") {
|
|
7566
|
-
return
|
|
7645
|
+
return path__default.join(root, "Engine", "Binaries", "Linux", "UnrealEditor");
|
|
7567
7646
|
}
|
|
7568
7647
|
return "";
|
|
7569
7648
|
};
|
|
@@ -7732,12 +7811,46 @@ Next: fix the issue, then relaunch via unreal_editor_relaunch_last (or unreal_ed
|
|
|
7732
7811
|
const exists = await runGit(["show-ref", "--verify", "--quiet", `refs/heads/${candidate}`], args.repoRoot, 1e4);
|
|
7733
7812
|
if (exists.ok) return candidate;
|
|
7734
7813
|
}
|
|
7814
|
+
const hasCommit = await runGit(["rev-parse", "--verify", "HEAD"], args.repoRoot, 1e4);
|
|
7815
|
+
if (!hasCommit.ok) {
|
|
7816
|
+
throw new Error(
|
|
7817
|
+
`Coordination requires an initial commit (no local branches exist yet).
|
|
7818
|
+
|
|
7819
|
+
Fix:
|
|
7820
|
+
- Initialize: git init -b main
|
|
7821
|
+
- Add a minimal .gitignore
|
|
7822
|
+
- Commit: git add .gitignore && git commit -m "chore: init coordination"`
|
|
7823
|
+
);
|
|
7824
|
+
}
|
|
7735
7825
|
const heads = await runGit(["for-each-ref", "--format=%(refname:short)", "refs/heads"], args.repoRoot, 1e4);
|
|
7736
7826
|
const available = String(heads.stdout || "").split("\n").map((l) => l.trim()).filter(Boolean).slice(0, 20).join(", ");
|
|
7737
7827
|
throw new Error(
|
|
7738
7828
|
`Invalid base branch '${"main"}' (no local branch found). Create the branch locally (or rename your default branch). Available local branches: ${available || "(none)"}`
|
|
7739
7829
|
);
|
|
7740
7830
|
};
|
|
7831
|
+
const publishCoordinationSetupStatus = (next) => {
|
|
7832
|
+
try {
|
|
7833
|
+
client?.updateMetadata?.((metadata) => ({
|
|
7834
|
+
...metadata || {},
|
|
7835
|
+
coordinationAutoFinalize: {
|
|
7836
|
+
status: next.status,
|
|
7837
|
+
error: next.error,
|
|
7838
|
+
updatedAt: Date.now()
|
|
7839
|
+
}
|
|
7840
|
+
}));
|
|
7841
|
+
} catch (err) {
|
|
7842
|
+
logger.debug("[flockbayMCP] Failed to publish coordination setup status:", err);
|
|
7843
|
+
}
|
|
7844
|
+
};
|
|
7845
|
+
const classifyCoordinationGitError = (err) => {
|
|
7846
|
+
const message = err instanceof Error ? err.message : String(err || "");
|
|
7847
|
+
const detail = String(message || "").trim() || "coordination_git_error";
|
|
7848
|
+
if (detail.startsWith("Not a git repo")) return { code: "not_a_git_repo", detail };
|
|
7849
|
+
if (detail.startsWith("Coordination requires an initial commit")) return { code: "no_initial_commit", detail };
|
|
7850
|
+
if (detail.startsWith("Invalid base branch")) return { code: "missing_base_branch", detail };
|
|
7851
|
+
if (detail.includes("unsafe_git_repo_root")) return { code: "unsafe_git_repo_root", detail };
|
|
7852
|
+
return { code: "coordination_git_error", detail };
|
|
7853
|
+
};
|
|
7741
7854
|
const preflightRefreshableFiles = async (args) => {
|
|
7742
7855
|
const out = [];
|
|
7743
7856
|
for (const filePath of args.files) {
|
|
@@ -7855,7 +7968,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
7855
7968
|
};
|
|
7856
7969
|
const toolResultsArtifactsRoot = () => {
|
|
7857
7970
|
const sessionDir = readSessionWorkingDirectory();
|
|
7858
|
-
return
|
|
7971
|
+
return path__default.join(sessionDir, ".flockbay", "artifacts", "tool-results");
|
|
7859
7972
|
};
|
|
7860
7973
|
const safePathSegment = (raw) => String(raw || "").trim().replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^_+/, "").replace(/_+$/, "") || "unknown";
|
|
7861
7974
|
const coerceTextBlocks = (content) => {
|
|
@@ -7871,9 +7984,9 @@ ${String(st.stdout || "").trim()}`
|
|
|
7871
7984
|
};
|
|
7872
7985
|
const writeToolResultArtifact = async (args) => {
|
|
7873
7986
|
const root = toolResultsArtifactsRoot();
|
|
7874
|
-
const toolDir =
|
|
7987
|
+
const toolDir = path__default.join(root, safePathSegment(args.toolName));
|
|
7875
7988
|
await mkdir(toolDir, { recursive: true });
|
|
7876
|
-
const filePath =
|
|
7989
|
+
const filePath = path__default.join(toolDir, `${safePathSegment(args.toolResultId)}.json`);
|
|
7877
7990
|
const payload = {
|
|
7878
7991
|
kind: "mcp_tool_result",
|
|
7879
7992
|
toolName: args.toolName,
|
|
@@ -7894,7 +8007,9 @@ ${String(st.stdout || "").trim()}`
|
|
|
7894
8007
|
const first = texts[0];
|
|
7895
8008
|
if (first && !/^\s*[{[]/.test(first)) headLines.push(first);
|
|
7896
8009
|
headLines.push(`Evidence: toolResultId=${args.artifact.id}`);
|
|
7897
|
-
headLines.push(
|
|
8010
|
+
headLines.push(
|
|
8011
|
+
`Tip: use tool_result_query (preferred) to fetch small slices; avoid cat/reading full .flockbay tool-result JSON unless necessary.`
|
|
8012
|
+
);
|
|
7898
8013
|
const modelContent = [{ type: "text", text: headLines.join("\n") }];
|
|
7899
8014
|
if (args.passthroughContent && Array.isArray(args.fullResult?.content)) {
|
|
7900
8015
|
for (const block of args.fullResult.content) {
|
|
@@ -7917,24 +8032,21 @@ ${String(st.stdout || "").trim()}`
|
|
|
7917
8032
|
};
|
|
7918
8033
|
const resolveToolResultArtifactPath = async (args) => {
|
|
7919
8034
|
const root = toolResultsArtifactsRoot();
|
|
7920
|
-
const resolvedRoot =
|
|
8035
|
+
const resolvedRoot = path__default.resolve(root);
|
|
7921
8036
|
const toolResultId = String(args.toolResultId || "").trim();
|
|
7922
8037
|
if (!toolResultId) return { ok: false, error: "Missing toolResultId." };
|
|
7923
|
-
const rawPath = String(args.artifactPath || "").trim();
|
|
7924
8038
|
const toolName = String(args.toolName || "").trim();
|
|
7925
8039
|
const safeId = safePathSegment(toolResultId);
|
|
7926
8040
|
let filePath = "";
|
|
7927
|
-
if (
|
|
7928
|
-
filePath =
|
|
7929
|
-
} else if (toolName) {
|
|
7930
|
-
filePath = path.join(root, safePathSegment(toolName), `${safeId}.json`);
|
|
8041
|
+
if (toolName) {
|
|
8042
|
+
filePath = path__default.join(root, safePathSegment(toolName), `${safeId}.json`);
|
|
7931
8043
|
} else {
|
|
7932
8044
|
if (!existsSync(root)) return { ok: false, error: `Tool results root not found: ${root}` };
|
|
7933
8045
|
const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
|
|
7934
8046
|
const matches = [];
|
|
7935
8047
|
for (const entry of entries) {
|
|
7936
8048
|
if (!entry.isDirectory()) continue;
|
|
7937
|
-
const candidate =
|
|
8049
|
+
const candidate = path__default.join(root, entry.name, `${safeId}.json`);
|
|
7938
8050
|
if (existsSync(candidate)) matches.push(candidate);
|
|
7939
8051
|
}
|
|
7940
8052
|
if (matches.length === 0) return { ok: false, error: `tool result not found: ${toolResultId}` };
|
|
@@ -7946,8 +8058,8 @@ ${String(st.stdout || "").trim()}`
|
|
|
7946
8058
|
}
|
|
7947
8059
|
filePath = matches[0];
|
|
7948
8060
|
}
|
|
7949
|
-
const resolvedFile =
|
|
7950
|
-
if (!resolvedFile.startsWith(resolvedRoot +
|
|
8061
|
+
const resolvedFile = path__default.resolve(filePath);
|
|
8062
|
+
if (!resolvedFile.startsWith(resolvedRoot + path__default.sep) && resolvedFile !== resolvedRoot) {
|
|
7951
8063
|
return { ok: false, error: `Refusing to read outside tool-results root.` };
|
|
7952
8064
|
}
|
|
7953
8065
|
if (!existsSync(resolvedFile)) return { ok: false, error: `tool result artifact not found` };
|
|
@@ -8510,15 +8622,13 @@ ${String(st.stdout || "").trim()}`
|
|
|
8510
8622
|
description: "Read a previously-stored MCP tool result artifact produced by Flockbay MCP (full tool input/output JSON). Use this when you explicitly need details beyond the default minimal tool observation.",
|
|
8511
8623
|
inputSchema: {
|
|
8512
8624
|
toolName: z.string().optional().describe("Tool name (folder under .flockbay/artifacts/tool-results)."),
|
|
8513
|
-
toolResultId: z.string().describe("toolResultId from a prior tool observation.")
|
|
8514
|
-
artifactPath: z.string().optional().describe("Optional absolute artifact path (advanced).")
|
|
8625
|
+
toolResultId: z.string().describe("toolResultId from a prior tool observation.")
|
|
8515
8626
|
}
|
|
8516
8627
|
},
|
|
8517
8628
|
async (args) => runWithMcpToolCard("tool_result_read", args, async () => {
|
|
8518
8629
|
const toolName = typeof args?.toolName === "string" ? String(args.toolName).trim() : null;
|
|
8519
8630
|
const toolResultId = typeof args?.toolResultId === "string" ? String(args.toolResultId).trim() : "";
|
|
8520
|
-
const
|
|
8521
|
-
const resolved = await resolveToolResultArtifactPath({ toolName, toolResultId, artifactPath });
|
|
8631
|
+
const resolved = await resolveToolResultArtifactPath({ toolName, toolResultId });
|
|
8522
8632
|
if (!resolved.ok) return textToolResult(resolved.error, true);
|
|
8523
8633
|
const raw = await readFile(resolved.path, "utf8");
|
|
8524
8634
|
return {
|
|
@@ -8537,18 +8647,16 @@ ${String(st.stdout || "").trim()}`
|
|
|
8537
8647
|
inputSchema: {
|
|
8538
8648
|
toolName: z.string().optional().describe("Tool name (folder under .flockbay/artifacts/tool-results)."),
|
|
8539
8649
|
toolResultId: z.string().describe("toolResultId from a prior tool observation."),
|
|
8540
|
-
artifactPath: z.string().optional().describe("Optional absolute artifact path (advanced)."),
|
|
8541
8650
|
paths: z.array(z.string()).min(1).describe('JSON pointer paths ("/output/result/location") or dot-paths ("output.result.location").')
|
|
8542
8651
|
}
|
|
8543
8652
|
},
|
|
8544
8653
|
async (args) => runWithMcpToolCard("tool_result_query", args, async () => {
|
|
8545
8654
|
const toolName = typeof args?.toolName === "string" ? String(args.toolName).trim() : null;
|
|
8546
8655
|
const toolResultId = typeof args?.toolResultId === "string" ? String(args.toolResultId).trim() : "";
|
|
8547
|
-
const artifactPath = typeof args?.artifactPath === "string" ? String(args.artifactPath).trim() : null;
|
|
8548
8656
|
const paths = Array.isArray(args?.paths) ? args.paths.map((p) => String(p || "").trim()).filter(Boolean) : [];
|
|
8549
8657
|
if (!toolResultId) return textToolResult("Missing toolResultId.", true);
|
|
8550
8658
|
if (paths.length === 0) return textToolResult("Missing paths[] (provide one or more JSON paths).", true);
|
|
8551
|
-
const resolved = await resolveToolResultArtifactPath({ toolName, toolResultId
|
|
8659
|
+
const resolved = await resolveToolResultArtifactPath({ toolName, toolResultId });
|
|
8552
8660
|
if (!resolved.ok) return textToolResult(resolved.error, true);
|
|
8553
8661
|
let json;
|
|
8554
8662
|
try {
|
|
@@ -8633,8 +8741,29 @@ ${String(st.stdout || "").trim()}`
|
|
|
8633
8741
|
return { content: [{ type: "text", text: "Missing files[]" }], isError: true };
|
|
8634
8742
|
}
|
|
8635
8743
|
const sessionCwd = readSessionWorkingDirectory();
|
|
8636
|
-
|
|
8637
|
-
|
|
8744
|
+
let repoRoot = "";
|
|
8745
|
+
let baseBranch = "";
|
|
8746
|
+
try {
|
|
8747
|
+
repoRoot = await resolveGitRepoRoot(sessionCwd);
|
|
8748
|
+
baseBranch = await resolveCoordinationBaseBranch({ projectId, repoRoot });
|
|
8749
|
+
publishCoordinationSetupStatus({ status: "ok", error: null });
|
|
8750
|
+
} catch (err) {
|
|
8751
|
+
const classified = classifyCoordinationGitError(err);
|
|
8752
|
+
publishCoordinationSetupStatus({ status: "error", error: classified.code });
|
|
8753
|
+
return textToolResult(
|
|
8754
|
+
JSON.stringify(
|
|
8755
|
+
{
|
|
8756
|
+
success: false,
|
|
8757
|
+
error: classified.code,
|
|
8758
|
+
detail: classified.detail,
|
|
8759
|
+
hint: classified.code === "not_a_git_repo" ? "Initialize git in this project root (git init -b main) and add an initial commit, then retry." : classified.code === "no_initial_commit" ? "Create an initial commit so a base branch exists, then retry." : classified.code === "missing_base_branch" ? "Create/rename your base branch (main/master) locally, then retry." : "Fix the git repo and retry."
|
|
8760
|
+
},
|
|
8761
|
+
null,
|
|
8762
|
+
2
|
|
8763
|
+
),
|
|
8764
|
+
true
|
|
8765
|
+
);
|
|
8766
|
+
}
|
|
8638
8767
|
const guard = client?.coordinationLeaseGuard;
|
|
8639
8768
|
const needsRefresh = files.filter((f) => {
|
|
8640
8769
|
try {
|
|
@@ -8868,8 +8997,28 @@ ${String(st.stdout || "").trim()}`
|
|
|
8868
8997
|
return { content: [{ type: "text", text: "Missing files[]" }], isError: true };
|
|
8869
8998
|
}
|
|
8870
8999
|
const sessionCwd = readSessionWorkingDirectory();
|
|
8871
|
-
|
|
8872
|
-
|
|
9000
|
+
let repoRoot = "";
|
|
9001
|
+
let baseBranch = "";
|
|
9002
|
+
try {
|
|
9003
|
+
repoRoot = await resolveGitRepoRoot(sessionCwd);
|
|
9004
|
+
baseBranch = await resolveCoordinationBaseBranch({ projectId, repoRoot });
|
|
9005
|
+
publishCoordinationSetupStatus({ status: "ok", error: null });
|
|
9006
|
+
} catch (err) {
|
|
9007
|
+
const classified = classifyCoordinationGitError(err);
|
|
9008
|
+
publishCoordinationSetupStatus({ status: "error", error: classified.code });
|
|
9009
|
+
return textToolResult(
|
|
9010
|
+
JSON.stringify(
|
|
9011
|
+
{
|
|
9012
|
+
success: false,
|
|
9013
|
+
error: classified.code,
|
|
9014
|
+
detail: classified.detail
|
|
9015
|
+
},
|
|
9016
|
+
null,
|
|
9017
|
+
2
|
|
9018
|
+
),
|
|
9019
|
+
true
|
|
9020
|
+
);
|
|
9021
|
+
}
|
|
8873
9022
|
const guard = client?.coordinationLeaseGuard;
|
|
8874
9023
|
const needsRefresh = files.filter((f) => {
|
|
8875
9024
|
try {
|
|
@@ -9015,10 +9164,10 @@ ${String(st.stdout || "").trim()}`
|
|
|
9015
9164
|
if (!p) return p;
|
|
9016
9165
|
if (p.startsWith("~/")) {
|
|
9017
9166
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
9018
|
-
if (home) return
|
|
9167
|
+
if (home) return path__default.join(home, p.slice(2));
|
|
9019
9168
|
}
|
|
9020
9169
|
const baseDir = readSessionWorkingDirectory();
|
|
9021
|
-
return
|
|
9170
|
+
return path__default.isAbsolute(p) ? p : path__default.resolve(baseDir, p);
|
|
9022
9171
|
};
|
|
9023
9172
|
const idsSeen = /* @__PURE__ */ new Set();
|
|
9024
9173
|
const viewsLocal = [];
|
|
@@ -9031,7 +9180,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
9031
9180
|
if (!existsSync(abs)) {
|
|
9032
9181
|
throw new Error(`Image not found: ${abs}`);
|
|
9033
9182
|
}
|
|
9034
|
-
const filename =
|
|
9183
|
+
const filename = path__default.basename(abs);
|
|
9035
9184
|
let id = deriveScreenshotViewIdFromFilename(filename);
|
|
9036
9185
|
if (!id) id = filename.replace(/\.[^.]+$/, "");
|
|
9037
9186
|
let unique = id;
|
|
@@ -9108,9 +9257,9 @@ ${String(st.stdout || "").trim()}`
|
|
|
9108
9257
|
const limit = args.limit ?? 12;
|
|
9109
9258
|
const includeBase64 = args.includeBase64 ?? false;
|
|
9110
9259
|
const maxBytesPerImage = args.maxBytesPerImage ?? 25e5;
|
|
9111
|
-
const outDir =
|
|
9260
|
+
const outDir = path__default.join(path__default.dirname(uprojectPath), "Saved", "Screenshots", "Flockbay");
|
|
9112
9261
|
try {
|
|
9113
|
-
if (!uprojectPath || !uprojectPath.toLowerCase().endsWith(".uproject") || !
|
|
9262
|
+
if (!uprojectPath || !uprojectPath.toLowerCase().endsWith(".uproject") || !path__default.isAbsolute(uprojectPath)) {
|
|
9114
9263
|
return {
|
|
9115
9264
|
content: [{ type: "text", text: `Invalid uprojectPath (must be an absolute path to *.uproject): ${String(uprojectPath)}` }],
|
|
9116
9265
|
isError: true
|
|
@@ -9145,7 +9294,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
9145
9294
|
}
|
|
9146
9295
|
const withTimes = await Promise.all(
|
|
9147
9296
|
candidates.map(async (name) => {
|
|
9148
|
-
const fullPath =
|
|
9297
|
+
const fullPath = path__default.join(outDir, name);
|
|
9149
9298
|
const st = await stat(fullPath);
|
|
9150
9299
|
return { name, path: fullPath, mtimeMs: st.mtimeMs };
|
|
9151
9300
|
})
|
|
@@ -9242,7 +9391,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
9242
9391
|
const uprojectPath = typeof args?.uprojectPath === "string" ? String(args.uprojectPath).trim() : "";
|
|
9243
9392
|
const engineRootArg = typeof args?.engineRoot === "string" ? String(args.engineRoot).trim() : "";
|
|
9244
9393
|
const extraArgs = Array.isArray(args?.extraArgs) ? args.extraArgs : [];
|
|
9245
|
-
if (!uprojectPath || !uprojectPath.toLowerCase().endsWith(".uproject") || !
|
|
9394
|
+
if (!uprojectPath || !uprojectPath.toLowerCase().endsWith(".uproject") || !path__default.isAbsolute(uprojectPath)) {
|
|
9246
9395
|
return {
|
|
9247
9396
|
content: [{ type: "text", text: `Invalid uprojectPath (must be an absolute path to *.uproject): ${String(uprojectPath)}` }],
|
|
9248
9397
|
isError: true
|
|
@@ -9330,7 +9479,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
9330
9479
|
const uprojectPath = typeof args?.uprojectPath === "string" ? String(args.uprojectPath).trim() : "";
|
|
9331
9480
|
const engineRootArg = typeof args?.engineRoot === "string" ? String(args.engineRoot).trim() : "";
|
|
9332
9481
|
const timeoutMs = typeof args?.timeoutMs === "number" ? args.timeoutMs : void 0;
|
|
9333
|
-
if (!uprojectPath || !uprojectPath.toLowerCase().endsWith(".uproject") || !
|
|
9482
|
+
if (!uprojectPath || !uprojectPath.toLowerCase().endsWith(".uproject") || !path__default.isAbsolute(uprojectPath)) {
|
|
9334
9483
|
return {
|
|
9335
9484
|
content: [{ type: "text", text: `Invalid uprojectPath (must be an absolute path to *.uproject): ${String(uprojectPath)}` }],
|
|
9336
9485
|
isError: true
|
|
@@ -10566,7 +10715,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
10566
10715
|
const stabilizeMs = typeof args?.stabilizeMs === "number" ? args.stabilizeMs : void 0;
|
|
10567
10716
|
const timeoutMs = typeof args?.timeoutMs === "number" ? args.timeoutMs : void 0;
|
|
10568
10717
|
const stopIfPlaying = typeof args?.stopIfPlaying === "boolean" ? args.stopIfPlaying : void 0;
|
|
10569
|
-
if (!uprojectPath || !uprojectPath.toLowerCase().endsWith(".uproject") || !
|
|
10718
|
+
if (!uprojectPath || !uprojectPath.toLowerCase().endsWith(".uproject") || !path__default.isAbsolute(uprojectPath)) {
|
|
10570
10719
|
return {
|
|
10571
10720
|
content: [{ type: "text", text: `Invalid uprojectPath (must be an absolute path to *.uproject): ${String(uprojectPath)}` }],
|
|
10572
10721
|
isError: true
|
|
@@ -10943,7 +11092,7 @@ async function buildProjectCapsule(opts) {
|
|
|
10943
11092
|
if (!detection.projectRoot) return null;
|
|
10944
11093
|
const projectRoot = detection.projectRoot;
|
|
10945
11094
|
const uprojectFile = detection.uprojectFile;
|
|
10946
|
-
const readmePath =
|
|
11095
|
+
const readmePath = path__default.join(projectRoot, ".flockbay", "README.md");
|
|
10947
11096
|
const readmeRaw = await readTextFileIfExists(readmePath);
|
|
10948
11097
|
const maxReadmeChars = typeof opts.maxReadmeChars === "number" ? opts.maxReadmeChars : DEFAULT_MAX_README_CHARS;
|
|
10949
11098
|
const readme = readmeRaw ? truncateWithNote(readmeRaw, maxReadmeChars) : null;
|
|
@@ -11005,14 +11154,14 @@ async function runClaude(credentials, options = {}) {
|
|
|
11005
11154
|
let metadata = {
|
|
11006
11155
|
path: workingDirectory,
|
|
11007
11156
|
projectRootPath: coordinationProjectRootPath,
|
|
11008
|
-
host:
|
|
11157
|
+
host: os__default.hostname(),
|
|
11009
11158
|
version: packageJson.version,
|
|
11010
|
-
os:
|
|
11159
|
+
os: os__default.platform(),
|
|
11011
11160
|
machineId,
|
|
11012
11161
|
workspaceProjectId: coordinationWorkspaceProjectId,
|
|
11013
11162
|
featureId: coordinationFeatureId,
|
|
11014
11163
|
workItemId: coordinationWorkItemId,
|
|
11015
|
-
homeDir:
|
|
11164
|
+
homeDir: os__default.homedir(),
|
|
11016
11165
|
flockbayHomeDir: configuration.flockbayHomeDir,
|
|
11017
11166
|
flockbayLibDir: projectPath(),
|
|
11018
11167
|
flockbayToolsDir: resolve(projectPath(), "tools", "unpacked"),
|
|
@@ -11944,8 +12093,8 @@ ${authUrl}
|
|
|
11944
12093
|
}
|
|
11945
12094
|
|
|
11946
12095
|
async function syncCodexCliAuth(tokens) {
|
|
11947
|
-
const authFilePath =
|
|
11948
|
-
const authDir =
|
|
12096
|
+
const authFilePath = path__default.join(os__default.homedir(), ".codex", "auth.json");
|
|
12097
|
+
const authDir = path__default.dirname(authFilePath);
|
|
11949
12098
|
let previousAccountId;
|
|
11950
12099
|
let previousApiKey = null;
|
|
11951
12100
|
try {
|
|
@@ -12127,10 +12276,10 @@ async function ensureDaemonRunning({ skipUnreal }) {
|
|
|
12127
12276
|
throw new Error("daemon_start_timeout");
|
|
12128
12277
|
}
|
|
12129
12278
|
async function createSmokeWorkspaceDir(baseDir) {
|
|
12130
|
-
const root = baseDir?.trim() ?
|
|
12279
|
+
const root = baseDir?.trim() ? path__default.resolve(baseDir) : path__default.join(os__default.tmpdir(), "flockbay-smoke", nowIsoCompact() + "_" + randomUUID().slice(0, 8));
|
|
12131
12280
|
await fs$1.mkdir(root, { recursive: true });
|
|
12132
12281
|
const secret = `SMOKE_SECRET_${randomUUID().slice(0, 8)}`;
|
|
12133
|
-
await fs$1.writeFile(
|
|
12282
|
+
await fs$1.writeFile(path__default.join(root, "smoke.txt"), `flockbay smoke test
|
|
12134
12283
|
SMOKE_SECRET=${secret}
|
|
12135
12284
|
`, "utf8");
|
|
12136
12285
|
return { dir: root, secret };
|
|
@@ -12463,8 +12612,8 @@ async function handleSmokeTestCommand(args) {
|
|
|
12463
12612
|
}
|
|
12464
12613
|
const allOk = results.every((r) => r.ok);
|
|
12465
12614
|
if (outPath) {
|
|
12466
|
-
const abs =
|
|
12467
|
-
await fs$1.mkdir(
|
|
12615
|
+
const abs = path__default.resolve(outPath);
|
|
12616
|
+
await fs$1.mkdir(path__default.dirname(abs), { recursive: true });
|
|
12468
12617
|
await fs$1.writeFile(abs, JSON.stringify({ ok: allOk, results }, null, 2) + "\n", "utf8");
|
|
12469
12618
|
console.log(chalk.gray(`Wrote: ${abs}`));
|
|
12470
12619
|
}
|
|
@@ -12518,7 +12667,7 @@ async function waitForUnreal(options) {
|
|
|
12518
12667
|
};
|
|
12519
12668
|
}
|
|
12520
12669
|
function resolveUnrealEditorExe(engineRoot) {
|
|
12521
|
-
const exe = process.platform === "win32" ?
|
|
12670
|
+
const exe = process.platform === "win32" ? path__default.join(engineRoot, "Engine", "Binaries", "Win64", "UnrealEditor.exe") : path__default.join(engineRoot, "Engine", "Binaries", process.platform === "darwin" ? "Mac" : "Linux", "UnrealEditor");
|
|
12522
12671
|
return exe;
|
|
12523
12672
|
}
|
|
12524
12673
|
async function runRuntimeSmoke(options) {
|
|
@@ -12595,10 +12744,10 @@ Expected FriendlyName="Flockbay MCP" CreatedBy="Respaced Inc."`
|
|
|
12595
12744
|
await sendUnrealMcpTcpCommand({ type: "play_in_editor_windowed", host: options.host, port: options.port, timeoutMs: Math.max(options.timeoutMs, 2e4) });
|
|
12596
12745
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
12597
12746
|
await sendUnrealMcpTcpCommand({ type: "stop_play_in_editor", host: options.host, port: options.port, timeoutMs: Math.max(options.timeoutMs, 2e4) });
|
|
12598
|
-
const projectDir =
|
|
12599
|
-
const shotsDir =
|
|
12747
|
+
const projectDir = path__default.dirname(options.projectPath);
|
|
12748
|
+
const shotsDir = path__default.join(projectDir, "Saved", "Screenshots", "Flockbay");
|
|
12600
12749
|
ensureDir(shotsDir);
|
|
12601
|
-
const shotPath =
|
|
12750
|
+
const shotPath = path__default.join(shotsDir, `smoke_${Date.now()}.png`);
|
|
12602
12751
|
await sendUnrealMcpTcpCommand({
|
|
12603
12752
|
type: "take_screenshot",
|
|
12604
12753
|
params: { filepath: shotPath },
|
|
@@ -13248,7 +13397,7 @@ ${engineRoot}`, {
|
|
|
13248
13397
|
} else if (subcommand === "codex") {
|
|
13249
13398
|
try {
|
|
13250
13399
|
await chdirToNearestUprojectRootIfPresent();
|
|
13251
|
-
const { runCodex } = await import('./runCodex-
|
|
13400
|
+
const { runCodex } = await import('./runCodex-F93OWlPh.mjs');
|
|
13252
13401
|
let startedBy = void 0;
|
|
13253
13402
|
let sessionId = void 0;
|
|
13254
13403
|
for (let i = 1; i < args.length; i++) {
|
|
@@ -13350,7 +13499,7 @@ ${engineRoot}`, {
|
|
|
13350
13499
|
}
|
|
13351
13500
|
try {
|
|
13352
13501
|
await chdirToNearestUprojectRootIfPresent();
|
|
13353
|
-
const { runGemini } = await import('./runGemini-
|
|
13502
|
+
const { runGemini } = await import('./runGemini-Cvo7a4eh.mjs');
|
|
13354
13503
|
let startedBy = void 0;
|
|
13355
13504
|
let sessionId = void 0;
|
|
13356
13505
|
for (let i = 1; i < args.length; i++) {
|