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.
@@ -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 os, { homedir } from 'node:os';
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-DdJKBH6T.mjs';
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 path, { resolve, join, dirname } from 'node:path';
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 path.join(home, raw.slice(2));
2014
+ if (home) return path__default.join(home, raw.slice(2));
1991
2015
  }
1992
- return path.isAbsolute(raw) ? raw : path.resolve(cwd, raw);
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(`${path.sep}Saved${path.sep}Screenshots${path.sep}Flockbay${path.sep}`) && !resolved.includes("Saved/Screenshots/Flockbay/") && !resolved.includes("Saved\\Screenshots\\Flockbay\\")) {
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 = path.join("Saved", "Screenshots", "Flockbay", filename);
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 = os.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=${os.userInfo().username} platform=${process.platform} pid=${process.pid}`,
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 (path.isAbsolute(candidate)) {
2597
+ if (path__default.isAbsolute(candidate)) {
2574
2598
  const base = String(this.session.path || "").trim();
2575
2599
  if (base) {
2576
- const rel = path.relative(base, candidate);
2577
- if (rel && !rel.startsWith("..") && !path.isAbsolute(rel)) {
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(path, token, body) {
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}${path}`;
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 msg = `Auto-finalize failed: ${finalizeRes.error}`;
3997
- messageBuffer.addMessage(msg, "status");
3998
- session.client.sendSessionEvent({ type: "message", message: msg });
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 msg = `Auto-finalize failed: ${err instanceof Error ? err.message : String(err)}`;
4002
- messageBuffer.addMessage(msg, "status");
4003
- session.client.sendSessionEvent({ type: "message", message: msg });
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} (${os.hostname()})`));
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 = path.join(flockbayHomeDir, "unreal-mcp");
5226
- const logPath = path.join(runtimeDir, "unreal_mcp.log");
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 = path.join(runtimeDir, "uv-venv");
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 path.dirname(current);
5574
+ return path__default.dirname(current);
5496
5575
  } catch {
5497
- current = path.dirname(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 = path.dirname(current);
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 path.join(projectRoot, uprojects[0]);
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 path.join(engineRoot, "Engine", "Build", "BatchFiles", "Build.bat");
6649
+ return path__default.join(engineRoot, "Engine", "Build", "BatchFiles", "Build.bat");
6571
6650
  }
6572
- return path.join(engineRoot, "Engine", "Build", "BatchFiles", "Build.sh");
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 || !path.isAbsolute(uprojectPath) || !uprojectPath.toLowerCase().endsWith(".uproject")) {
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 = path.basename(uprojectPath).replace(/\.uproject$/i, "");
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 ?? path.join(path.dirname(uprojectPath), "Saved", "Logs", "Flockbay");
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 = path.join(logDir, `flockbay_build_${Date.now()}.log`);
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 = path.join(path.dirname(args.uprojectPath), "Saved", "Screenshots", "Flockbay");
6840
+ const screenshotDir = path__default.join(path__default.dirname(args.uprojectPath), "Saved", "Screenshots", "Flockbay");
6762
6841
  await mkdir(screenshotDir, { recursive: true });
6763
- const screenshotPath = path.join(screenshotDir, `Flockbay_smoke_test_${stampForFilename()}.png`);
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(path.join(engineRoot, "Engine"))) return false;
7089
- const buildVersion = path.join(engineRoot, "Engine", "Build", "Build.version");
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" ? path.join(engineRoot, "Engine", "Binaries", "Mac", "UnrealEditor-Cmd") : process.platform === "win32" ? path.join(engineRoot, "Engine", "Binaries", "Win64", "UnrealEditor-Cmd.exe") : process.platform === "linux" ? path.join(engineRoot, "Engine", "Binaries", "Linux", "UnrealEditor-Cmd") : "";
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 = path.join(engineRoot, "Engine", "Build", "Build.version");
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 = path.basename(v.path);
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 = path.join(dir, name);
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 = path.join(projectRoot, "Saved", "Crashes");
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 = path.join(crashesDir, name);
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 = path.dirname(uprojectPath);
7609
+ const projectRoot = path__default.dirname(uprojectPath);
7531
7610
  const summary = [];
7532
7611
  const detail = { uprojectPath, projectRoot };
7533
- const latestLog = await findLatestFile(path.join(projectRoot, "Saved", "Logs"), (n) => n.toLowerCase().endsWith(".log"));
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 path.join(root, "Engine", "Binaries", "Mac", "UnrealEditor.app", "Contents", "MacOS", "UnrealEditor");
7639
+ return path__default.join(root, "Engine", "Binaries", "Mac", "UnrealEditor.app", "Contents", "MacOS", "UnrealEditor");
7561
7640
  }
7562
7641
  if (process.platform === "win32") {
7563
- return path.join(root, "Engine", "Binaries", "Win64", "UnrealEditor.exe");
7642
+ return path__default.join(root, "Engine", "Binaries", "Win64", "UnrealEditor.exe");
7564
7643
  }
7565
7644
  if (process.platform === "linux") {
7566
- return path.join(root, "Engine", "Binaries", "Linux", "UnrealEditor");
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 path.join(sessionDir, ".flockbay", "artifacts", "tool-results");
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 = path.join(root, safePathSegment(args.toolName));
7987
+ const toolDir = path__default.join(root, safePathSegment(args.toolName));
7875
7988
  await mkdir(toolDir, { recursive: true });
7876
- const filePath = path.join(toolDir, `${safePathSegment(args.toolResultId)}.json`);
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(`Tip: use tool_result_query/tool_result_read for details (avoid re-reading full tool JSON unless necessary).`);
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 = path.resolve(root);
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 (rawPath) {
7928
- filePath = rawPath;
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 = path.join(root, entry.name, `${safeId}.json`);
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 = path.resolve(filePath);
7950
- if (!resolvedFile.startsWith(resolvedRoot + path.sep) && resolvedFile !== 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 artifactPath = typeof args?.artifactPath === "string" ? String(args.artifactPath).trim() : null;
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, artifactPath });
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
- const repoRoot = await resolveGitRepoRoot(sessionCwd);
8637
- const baseBranch = await resolveCoordinationBaseBranch({ projectId, repoRoot });
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
- const repoRoot = await resolveGitRepoRoot(sessionCwd);
8872
- const baseBranch = await resolveCoordinationBaseBranch({ projectId, repoRoot });
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 path.join(home, p.slice(2));
9167
+ if (home) return path__default.join(home, p.slice(2));
9019
9168
  }
9020
9169
  const baseDir = readSessionWorkingDirectory();
9021
- return path.isAbsolute(p) ? p : path.resolve(baseDir, p);
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 = path.basename(abs);
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 = path.join(path.dirname(uprojectPath), "Saved", "Screenshots", "Flockbay");
9260
+ const outDir = path__default.join(path__default.dirname(uprojectPath), "Saved", "Screenshots", "Flockbay");
9112
9261
  try {
9113
- if (!uprojectPath || !uprojectPath.toLowerCase().endsWith(".uproject") || !path.isAbsolute(uprojectPath)) {
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 = path.join(outDir, name);
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") || !path.isAbsolute(uprojectPath)) {
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") || !path.isAbsolute(uprojectPath)) {
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") || !path.isAbsolute(uprojectPath)) {
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 = path.join(projectRoot, ".flockbay", "README.md");
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: os.hostname(),
11157
+ host: os__default.hostname(),
11009
11158
  version: packageJson.version,
11010
- os: os.platform(),
11159
+ os: os__default.platform(),
11011
11160
  machineId,
11012
11161
  workspaceProjectId: coordinationWorkspaceProjectId,
11013
11162
  featureId: coordinationFeatureId,
11014
11163
  workItemId: coordinationWorkItemId,
11015
- homeDir: os.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 = path.join(os.homedir(), ".codex", "auth.json");
11948
- const authDir = path.dirname(authFilePath);
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() ? path.resolve(baseDir) : path.join(os.tmpdir(), "flockbay-smoke", nowIsoCompact() + "_" + randomUUID().slice(0, 8));
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(path.join(root, "smoke.txt"), `flockbay smoke test
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 = path.resolve(outPath);
12467
- await fs$1.mkdir(path.dirname(abs), { recursive: true });
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" ? path.join(engineRoot, "Engine", "Binaries", "Win64", "UnrealEditor.exe") : path.join(engineRoot, "Engine", "Binaries", process.platform === "darwin" ? "Mac" : "Linux", "UnrealEditor");
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 = path.dirname(options.projectPath);
12599
- const shotsDir = path.join(projectDir, "Saved", "Screenshots", "Flockbay");
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 = path.join(shotsDir, `smoke_${Date.now()}.png`);
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-B0JRo8YP.mjs');
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-DO9xzjyY.mjs');
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++) {