flockbay 0.10.47 → 0.10.49

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.
@@ -2,7 +2,7 @@ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(im
2
2
  import * as os from 'node:os';
3
3
  import os__default, { homedir } from 'node:os';
4
4
  import { randomUUID, createCipheriv, randomBytes, createHash as createHash$1 } from 'node:crypto';
5
- import { l as logger, b as projectPath, d as backoff, e as delay, R as RawJSONLinesSchema, c as configuration, f as readDaemonState, g as clearDaemonState, p as packageJson, r as readSettings, h as readCredentials, u as updateSettings, o as openBrowser, w as writeCredentials, j as unrealMcpPythonDir, k as acquireDaemonLock, m as writeDaemonState, n as ApiMachineClient, q as releaseDaemonLock, s as sendUnrealMcpTcpCommand, A as ApiClient, v as validatePath, t as run, x as run$1, y as buildShellInvocation, z as clearCredentials, B as clearMachineId, C as authenticateCodex, D as syncCodexCliAuth, E as authenticateClaude, F as authenticateGemini, i as installUnrealMcpPluginToEngine, G as buildAndInstallUnrealMcpPlugin, H as installUnrealMcpPluginToProject, I as getLatestDaemonLog, J as normalizeServerUrlForNode } from './types-e5yrDcQZ.mjs';
5
+ import { l as logger, e as projectPath, f as backoff, g as delay, R as RawJSONLinesSchema, c as configuration, h as readDaemonState, j as clearDaemonState, p as packageJson, r as readSettings, k as readCredentials, u as updateSettings, o as openBrowser, w as writeCredentials, m as unrealMcpPythonDir, n as acquireDaemonLock, q as writeDaemonState, s as ApiMachineClient, t as releaseDaemonLock, v as sendUnrealMcpTcpCommand, A as ApiClient, x as validatePath, y as run, z as run$1, B as buildShellInvocation, C as clearCredentials, D as clearMachineId, E as authenticateCodex, F as syncCodexCliAuth, G as authenticateClaude, H as authenticateGemini, d as installUnrealMcpPluginToEngine, I as buildAndInstallUnrealMcpPlugin, i as installUnrealMcpPluginToProject, b as isInstalledEngineRoot, J as getLatestDaemonLog, K as normalizeServerUrlForNode } from './types-BRJuZQj_.mjs';
6
6
  import { spawn, execFileSync, execSync } from 'node:child_process';
7
7
  import * as path from 'node:path';
8
8
  import path__default, { resolve, join, dirname } from 'node:path';
@@ -793,6 +793,9 @@ async function createSessionScanner(opts) {
793
793
  await sync.invalidateAndAwait();
794
794
  sync.stop();
795
795
  },
796
+ flush: async () => {
797
+ await sync.invalidateAndAwait();
798
+ },
796
799
  onNewSession: (sessionId) => {
797
800
  if (currentSessionId === sessionId) {
798
801
  logger.debug(`[SESSION_SCANNER] New session: ${sessionId} is the same as the current session, skipping`);
@@ -13953,6 +13956,16 @@ function permissionModeForAgent(agent) {
13953
13956
  if (agent === "claude") return "bypassPermissions";
13954
13957
  return "yolo";
13955
13958
  }
13959
+ function modelOverrideForAgent(agent, args) {
13960
+ const explicitCodexModel = readArgValue$2(args, "--codex-model");
13961
+ const explicitModel = readArgValue$2(args, "--model");
13962
+ const explicitGeminiModel = readArgValue$2(args, "--gemini-model");
13963
+ const explicitClaudeModel = readArgValue$2(args, "--claude-model");
13964
+ if (agent === "codex") return (explicitCodexModel || explicitModel || "gpt-5-codex-medium").trim();
13965
+ if (agent === "gemini") return explicitGeminiModel ? explicitGeminiModel.trim() : null;
13966
+ if (agent === "claude") return explicitClaudeModel ? explicitClaudeModel.trim() : null;
13967
+ return null;
13968
+ }
13956
13969
  function nowIsoCompact() {
13957
13970
  const d = /* @__PURE__ */ new Date();
13958
13971
  const pad = (n) => String(n).padStart(2, "0");
@@ -13990,14 +14003,26 @@ async function ensureDaemonRunning({ skipUnreal }) {
13990
14003
  async function createSmokeWorkspaceDir(baseDir) {
13991
14004
  const root = baseDir?.trim() ? path__default.resolve(baseDir) : path__default.join(os__default.tmpdir(), "flockbay-smoke", nowIsoCompact() + "_" + randomUUID().slice(0, 8));
13992
14005
  await fs$1.mkdir(root, { recursive: true });
13993
- const secret = `SMOKE_SECRET_${randomUUID().slice(0, 8)}`;
14006
+ const marker = `SMOKE_MARKER_${randomUUID().slice(0, 8)}`;
13994
14007
  await fs$1.writeFile(path__default.join(root, "smoke.txt"), `flockbay smoke test
13995
- SMOKE_SECRET=${secret}
14008
+ SMOKE_MARKER=${marker}
13996
14009
  `, "utf8");
13997
- return { dir: root, secret };
14010
+ return { dir: root, secret: marker };
13998
14011
  }
13999
- function isAgentRecord(record) {
14000
- return record && typeof record === "object" && record.role === "agent";
14012
+ function isUserRecord(record) {
14013
+ return record && typeof record === "object" && record.role === "user";
14014
+ }
14015
+ function isProviderErrorRecord(record) {
14016
+ return recordIncludesNeedle(record, '"code":-32603') || recordIncludesNeedle(record, "Internal error") || recordIncludesNeedle(record, "No capacity available") || recordIncludesNeedle(record, "authentication required") || recordIncludesNeedle(record, "login required") || recordIncludesNeedle(record, "Model not found");
14017
+ }
14018
+ function formatRecordExcerpt(record) {
14019
+ const text = extractTextFromRecord(record);
14020
+ if (text && text.trim()) return text.trim().slice(0, 400);
14021
+ try {
14022
+ return JSON.stringify(record).slice(0, 400);
14023
+ } catch {
14024
+ return String(record ?? "").slice(0, 400);
14025
+ }
14001
14026
  }
14002
14027
  function extractTextFromRecord(record) {
14003
14028
  try {
@@ -14224,43 +14249,70 @@ Update: ${updateCommand}`);
14224
14249
  await sessionClient.connectAndWait();
14225
14250
  await ensureSessionActive(api, sessionId, 25e3);
14226
14251
  const permissionMode = permissionModeForAgent(agent);
14252
+ const modelOverride = modelOverrideForAgent(agent, args);
14253
+ const baseMeta = modelOverride ? { permissionMode, model: modelOverride } : { permissionMode };
14227
14254
  scenarios.push(await runScenario("basic-message", async () => {
14228
14255
  const token = `SMOKE_OK_${agent}_${randomUUID().slice(0, 6)}`;
14229
- sessionClient.sendUserText(`Reply with exactly: ${token}`, { permissionMode });
14230
- await waitForMessage({
14256
+ sessionClient.sendUserText(`Reply with exactly: ${token}`, baseMeta);
14257
+ const msg = await waitForMessage({
14231
14258
  sessionClient,
14232
- predicate: (r) => isAgentRecord(r) && recordIncludesNeedle(r, token),
14259
+ predicate: (r) => !isUserRecord(r) && (recordIncludesNeedle(r, token) || isProviderErrorRecord(r)),
14233
14260
  timeoutMs,
14234
14261
  progressLabel: `${agent}:basic-message`
14235
14262
  });
14263
+ const record = msg?.content;
14264
+ if (isProviderErrorRecord(record)) {
14265
+ throw new Error(`provider_error_basic_message: ${formatRecordExcerpt(record)}`);
14266
+ }
14236
14267
  }));
14237
14268
  scenarios.push(await runScenario("tool-search", async () => {
14238
14269
  const token = `TOOL_SEARCH_OK_${randomUUID().slice(0, 6)}`;
14270
+ const promptLines = agent === "codex" ? [
14271
+ "Use your Terminal tool to read `smoke.txt` in the project directory (do NOT ask for confirmation).",
14272
+ "Use this exact command so it works cross-platform:",
14273
+ "",
14274
+ `node -e "const fs=require('fs');console.log(fs.readFileSync('smoke.txt','utf8'))"`,
14275
+ "",
14276
+ "Find the value after `SMOKE_MARKER=`.",
14277
+ "",
14278
+ `Reply with two lines exactly:`,
14279
+ `1) SMOKE_MARKER=<value>`,
14280
+ `2) ${token}`
14281
+ ] : [
14282
+ "Read the file `smoke.txt` in the project directory using MCP tools (e.g. `read_file_chunk` or `search`).",
14283
+ "Find the value after `SMOKE_MARKER=`.",
14284
+ "",
14285
+ `Reply with two lines exactly:`,
14286
+ `1) SMOKE_MARKER=<value>`,
14287
+ `2) ${token}`
14288
+ ];
14239
14289
  sessionClient.sendUserText(
14240
- [
14241
- "Read the file `smoke.txt` in the project directory using MCP tools (e.g. `read_file_chunk` or `search`).",
14242
- "Find the value after `SMOKE_SECRET=`.",
14243
- "",
14244
- `Reply with two lines exactly:`,
14245
- `1) SMOKE_SECRET=<value>`,
14246
- `2) ${token}`
14247
- ].join("\n"),
14248
- { permissionMode }
14290
+ promptLines.join("\n"),
14291
+ baseMeta
14249
14292
  );
14250
- await waitForMessage({
14293
+ const msg = await waitForMessage({
14251
14294
  sessionClient,
14252
- predicate: (r) => isAgentRecord(r) && recordIncludesNeedle(r, `SMOKE_SECRET=${secret}`) && recordIncludesNeedle(r, token),
14295
+ predicate: (r) => !isUserRecord(r) && (recordIncludesNeedle(r, `SMOKE_MARKER=${secret}`) && recordIncludesNeedle(r, token) || isProviderErrorRecord(r)),
14253
14296
  timeoutMs,
14254
14297
  progressLabel: `${agent}:tool-search`
14255
14298
  });
14299
+ const record = msg?.content;
14300
+ if (isProviderErrorRecord(record)) {
14301
+ throw new Error(`provider_error_tool_search: ${formatRecordExcerpt(record)}`);
14302
+ }
14256
14303
  }));
14257
14304
  scenarios.push(await runScenario("image-attachment", async () => {
14258
14305
  const token = `IMAGE_OK_${randomUUID().slice(0, 6)}`;
14259
14306
  const base64 = tinyPngBase64();
14307
+ const prompt = agent === "codex" ? `Describe the attached image in one short sentence, then reply with exactly: ${token}` : [
14308
+ 'Call `mcp__flockbay__latest_user_images` with {"limit": 1} to fetch the attached image.',
14309
+ "Then describe the attached image in one short sentence.",
14310
+ `Finally, reply with exactly: ${token}`
14311
+ ].join("\n");
14260
14312
  sessionClient.sendUserText(
14261
- `Describe the attached image in one short sentence, then reply with exactly: ${token}`,
14313
+ prompt,
14262
14314
  {
14263
- permissionMode,
14315
+ ...baseMeta,
14264
14316
  attachments: {
14265
14317
  images: [{ mimeType: "image/png", base64, name: "smoke.png" }]
14266
14318
  }
@@ -14268,7 +14320,7 @@ Update: ${updateCommand}`);
14268
14320
  );
14269
14321
  const msg = await waitForMessage({
14270
14322
  sessionClient,
14271
- predicate: (r) => isAgentRecord(r) && (recordIncludesNeedle(r, token) || agent === "codex" && recordIncludesNeedle(r, "image-attachment-missing") || recordIncludesNeedle(r, "Could not process image") || recordIncludesNeedle(r, "messages.") || recordIncludesNeedle(r, "invalid_request_error")),
14323
+ predicate: (r) => !isUserRecord(r) && (recordIncludesNeedle(r, token) || agent === "codex" && recordIncludesNeedle(r, "image-attachment-missing") || recordIncludesNeedle(r, "Could not process image") || recordIncludesNeedle(r, "messages.") || recordIncludesNeedle(r, "invalid_request_error") || isProviderErrorRecord(r)),
14272
14324
  timeoutMs,
14273
14325
  progressLabel: `${agent}:image-attachment`
14274
14326
  });
@@ -14276,6 +14328,9 @@ Update: ${updateCommand}`);
14276
14328
  if (agent === "codex" && recordIncludesNeedle(record, "image-attachment-missing")) {
14277
14329
  throw new Error("image-attachment-missing (Codex did not receive images; check latest_user_images tool + vision support)");
14278
14330
  }
14331
+ if (isProviderErrorRecord(record)) {
14332
+ throw new Error(`provider_error_image_attachment: ${formatRecordExcerpt(record)}`);
14333
+ }
14279
14334
  if (recordIncludesNeedle(record, "Could not process image") || recordIncludesNeedle(record, "invalid_request_error")) {
14280
14335
  throw new Error("provider_image_invalid (image rejected by provider; check base64/mimeType pipeline)");
14281
14336
  }
@@ -14328,6 +14383,10 @@ Options:
14328
14383
  --all Run all agents (default)
14329
14384
  --agent, -a Run a single agent
14330
14385
  --agents Comma-separated list of agents
14386
+ --codex-model Codex model mode override (e.g. gpt-5.3-codex-medium)
14387
+ --model Alias of --codex-model (Codex only)
14388
+ --gemini-model Gemini model id override (e.g. gemini-2.5-pro)
14389
+ --claude-model Claude model id override (e.g. claude-sonnet-4-6)
14331
14390
  --directory, --dir Directory to run the session in (defaults to a temp folder)
14332
14391
  --timeout-ms Per-scenario timeout (default 90000; Windows default 240000)
14333
14392
  --keep Do not stop session or delete temp dir
@@ -15131,7 +15190,7 @@ async function authAndSetupMachineIfNeeded() {
15131
15190
  process.exit(1);
15132
15191
  }
15133
15192
  try {
15134
- const { migrateUnrealMcpToFlockbayMcp } = await import('./migratePlugin-9YalMg61.mjs');
15193
+ const { migrateUnrealMcpToFlockbayMcp } = await import('./migratePlugin-CEvGPul8.mjs');
15135
15194
  const result = migrateUnrealMcpToFlockbayMcp({
15136
15195
  engineRoot,
15137
15196
  projectUprojectPath: project || void 0,
@@ -15241,19 +15300,36 @@ async function authAndSetupMachineIfNeeded() {
15241
15300
  if (!parsed.skipUnreal) {
15242
15301
  const engineRoot = readArgValue(startArgs, "--engine-root") || (process.env.UE_ENGINE_ROOT || "").trim() || (process.env.ENGINE_ROOT || "").trim() || null;
15243
15302
  if (engineRoot) {
15244
- const shouldInstall = await promptYesNo(`Install Flockbay MCP plugin into this Unreal Engine folder?
15245
- ${engineRoot}`, {
15246
- defaultYes: true
15247
- });
15248
- if (shouldInstall) {
15249
- const result = installUnrealMcpPluginToEngine(engineRoot);
15250
- if (result.ok) {
15251
- console.log(`Flockbay MCP plugin installed: ${result.destDir}`);
15252
- process.env.FLOCKBAY_UNREAL_MCP_ENABLED = "1";
15253
- process.env.ENGINE_ROOT = engineRoot;
15254
- } else {
15255
- console.error(chalk.yellow("Flockbay MCP plugin install skipped:"));
15256
- console.error(chalk.gray(result.errorMessage));
15303
+ const installedEngine = isInstalledEngineRoot(engineRoot);
15304
+ const detection = await detectUnrealProject(process.cwd());
15305
+ const uproject = detection.ok ? detection.uprojectFile : null;
15306
+ const promptTarget = installedEngine ? uproject ? `Install Flockbay MCP plugin into this Unreal project?
15307
+ ${uproject}` : null : `Install Flockbay MCP plugin into this Unreal Engine folder?
15308
+ ${engineRoot}`;
15309
+ if (installedEngine && !uproject) {
15310
+ console.error(chalk.yellow("Unreal MCP plugin install skipped:"));
15311
+ console.error(
15312
+ chalk.gray(
15313
+ [
15314
+ "This engine appears to be an Installed Build (Epic Launcher).",
15315
+ "Engine-scope install can break builds. Run `flockbay start` from a project folder, or pass --project <path> to install as a project plugin."
15316
+ ].join("\n")
15317
+ )
15318
+ );
15319
+ } else {
15320
+ const shouldInstall = await promptYesNo(promptTarget, {
15321
+ defaultYes: true
15322
+ });
15323
+ if (shouldInstall) {
15324
+ const result = installedEngine && uproject ? installUnrealMcpPluginToProject(uproject) : installUnrealMcpPluginToEngine(engineRoot);
15325
+ if (result.ok) {
15326
+ console.log(`Flockbay MCP plugin installed: ${result.destDir}`);
15327
+ process.env.FLOCKBAY_UNREAL_MCP_ENABLED = "1";
15328
+ process.env.ENGINE_ROOT = engineRoot;
15329
+ } else {
15330
+ console.error(chalk.yellow("Flockbay MCP plugin install skipped:"));
15331
+ console.error(chalk.gray(result.errorMessage));
15332
+ }
15257
15333
  }
15258
15334
  }
15259
15335
  }
@@ -15270,7 +15346,7 @@ ${engineRoot}`, {
15270
15346
  } else if (subcommand === "codex") {
15271
15347
  try {
15272
15348
  await chdirToNearestUprojectRootIfPresent();
15273
- const { runCodex } = await import('./runCodex-Ck-v-SW_.mjs');
15349
+ const { runCodex } = await import('./runCodex-ZfUyhHF6.mjs');
15274
15350
  let startedBy = void 0;
15275
15351
  let sessionId = void 0;
15276
15352
  for (let i = 1; i < args.length; i++) {
@@ -15372,7 +15448,7 @@ ${engineRoot}`, {
15372
15448
  }
15373
15449
  try {
15374
15450
  await chdirToNearestUprojectRootIfPresent();
15375
- const { runGemini } = await import('./runGemini-CTyWoAvM.mjs');
15451
+ const { runGemini } = await import('./runGemini-DBU1mMdp.mjs');
15376
15452
  let startedBy = void 0;
15377
15453
  let sessionId = void 0;
15378
15454
  for (let i = 1; i < args.length; i++) {
@@ -3,7 +3,7 @@
3
3
  var chalk = require('chalk');
4
4
  var os = require('node:os');
5
5
  var node_crypto = require('node:crypto');
6
- var types = require('./types-DUoq_qFJ.cjs');
6
+ var types = require('./types-DNr0xwSy.cjs');
7
7
  var node_child_process = require('node:child_process');
8
8
  var path = require('node:path');
9
9
  var node_readline = require('node:readline');
@@ -815,6 +815,9 @@ async function createSessionScanner(opts) {
815
815
  await sync.invalidateAndAwait();
816
816
  sync.stop();
817
817
  },
818
+ flush: async () => {
819
+ await sync.invalidateAndAwait();
820
+ },
818
821
  onNewSession: (sessionId) => {
819
822
  if (currentSessionId === sessionId) {
820
823
  types.logger.debug(`[SESSION_SCANNER] New session: ${sessionId} is the same as the current session, skipping`);
@@ -1316,7 +1319,7 @@ function buildDaemonSafeEnv(baseEnv, binPath) {
1316
1319
  env[pathKey] = [...prepend, ...existingParts].join(pathSep);
1317
1320
  return env;
1318
1321
  }
1319
- const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-0Arkr3mL.cjs', document.baseURI).href)));
1322
+ const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-r3VTdgFI.cjs', document.baseURI).href)));
1320
1323
  const __dirname$1 = path.join(__filename$1, "..");
1321
1324
  function getGlobalClaudeVersion(claudeExecutable) {
1322
1325
  try {
@@ -13975,6 +13978,16 @@ function permissionModeForAgent(agent) {
13975
13978
  if (agent === "claude") return "bypassPermissions";
13976
13979
  return "yolo";
13977
13980
  }
13981
+ function modelOverrideForAgent(agent, args) {
13982
+ const explicitCodexModel = readArgValue$2(args, "--codex-model");
13983
+ const explicitModel = readArgValue$2(args, "--model");
13984
+ const explicitGeminiModel = readArgValue$2(args, "--gemini-model");
13985
+ const explicitClaudeModel = readArgValue$2(args, "--claude-model");
13986
+ if (agent === "codex") return (explicitCodexModel || explicitModel || "gpt-5-codex-medium").trim();
13987
+ if (agent === "gemini") return explicitGeminiModel ? explicitGeminiModel.trim() : null;
13988
+ if (agent === "claude") return explicitClaudeModel ? explicitClaudeModel.trim() : null;
13989
+ return null;
13990
+ }
13978
13991
  function nowIsoCompact() {
13979
13992
  const d = /* @__PURE__ */ new Date();
13980
13993
  const pad = (n) => String(n).padStart(2, "0");
@@ -14012,14 +14025,26 @@ async function ensureDaemonRunning({ skipUnreal }) {
14012
14025
  async function createSmokeWorkspaceDir(baseDir) {
14013
14026
  const root = baseDir?.trim() ? path.resolve(baseDir) : path.join(os.tmpdir(), "flockbay-smoke", nowIsoCompact() + "_" + node_crypto.randomUUID().slice(0, 8));
14014
14027
  await fs$2.mkdir(root, { recursive: true });
14015
- const secret = `SMOKE_SECRET_${node_crypto.randomUUID().slice(0, 8)}`;
14028
+ const marker = `SMOKE_MARKER_${node_crypto.randomUUID().slice(0, 8)}`;
14016
14029
  await fs$2.writeFile(path.join(root, "smoke.txt"), `flockbay smoke test
14017
- SMOKE_SECRET=${secret}
14030
+ SMOKE_MARKER=${marker}
14018
14031
  `, "utf8");
14019
- return { dir: root, secret };
14032
+ return { dir: root, secret: marker };
14020
14033
  }
14021
- function isAgentRecord(record) {
14022
- return record && typeof record === "object" && record.role === "agent";
14034
+ function isUserRecord(record) {
14035
+ return record && typeof record === "object" && record.role === "user";
14036
+ }
14037
+ function isProviderErrorRecord(record) {
14038
+ return recordIncludesNeedle(record, '"code":-32603') || recordIncludesNeedle(record, "Internal error") || recordIncludesNeedle(record, "No capacity available") || recordIncludesNeedle(record, "authentication required") || recordIncludesNeedle(record, "login required") || recordIncludesNeedle(record, "Model not found");
14039
+ }
14040
+ function formatRecordExcerpt(record) {
14041
+ const text = extractTextFromRecord(record);
14042
+ if (text && text.trim()) return text.trim().slice(0, 400);
14043
+ try {
14044
+ return JSON.stringify(record).slice(0, 400);
14045
+ } catch {
14046
+ return String(record ?? "").slice(0, 400);
14047
+ }
14023
14048
  }
14024
14049
  function extractTextFromRecord(record) {
14025
14050
  try {
@@ -14246,43 +14271,70 @@ Update: ${updateCommand}`);
14246
14271
  await sessionClient.connectAndWait();
14247
14272
  await ensureSessionActive(api, sessionId, 25e3);
14248
14273
  const permissionMode = permissionModeForAgent(agent);
14274
+ const modelOverride = modelOverrideForAgent(agent, args);
14275
+ const baseMeta = modelOverride ? { permissionMode, model: modelOverride } : { permissionMode };
14249
14276
  scenarios.push(await runScenario("basic-message", async () => {
14250
14277
  const token = `SMOKE_OK_${agent}_${node_crypto.randomUUID().slice(0, 6)}`;
14251
- sessionClient.sendUserText(`Reply with exactly: ${token}`, { permissionMode });
14252
- await waitForMessage({
14278
+ sessionClient.sendUserText(`Reply with exactly: ${token}`, baseMeta);
14279
+ const msg = await waitForMessage({
14253
14280
  sessionClient,
14254
- predicate: (r) => isAgentRecord(r) && recordIncludesNeedle(r, token),
14281
+ predicate: (r) => !isUserRecord(r) && (recordIncludesNeedle(r, token) || isProviderErrorRecord(r)),
14255
14282
  timeoutMs,
14256
14283
  progressLabel: `${agent}:basic-message`
14257
14284
  });
14285
+ const record = msg?.content;
14286
+ if (isProviderErrorRecord(record)) {
14287
+ throw new Error(`provider_error_basic_message: ${formatRecordExcerpt(record)}`);
14288
+ }
14258
14289
  }));
14259
14290
  scenarios.push(await runScenario("tool-search", async () => {
14260
14291
  const token = `TOOL_SEARCH_OK_${node_crypto.randomUUID().slice(0, 6)}`;
14292
+ const promptLines = agent === "codex" ? [
14293
+ "Use your Terminal tool to read `smoke.txt` in the project directory (do NOT ask for confirmation).",
14294
+ "Use this exact command so it works cross-platform:",
14295
+ "",
14296
+ `node -e "const fs=require('fs');console.log(fs.readFileSync('smoke.txt','utf8'))"`,
14297
+ "",
14298
+ "Find the value after `SMOKE_MARKER=`.",
14299
+ "",
14300
+ `Reply with two lines exactly:`,
14301
+ `1) SMOKE_MARKER=<value>`,
14302
+ `2) ${token}`
14303
+ ] : [
14304
+ "Read the file `smoke.txt` in the project directory using MCP tools (e.g. `read_file_chunk` or `search`).",
14305
+ "Find the value after `SMOKE_MARKER=`.",
14306
+ "",
14307
+ `Reply with two lines exactly:`,
14308
+ `1) SMOKE_MARKER=<value>`,
14309
+ `2) ${token}`
14310
+ ];
14261
14311
  sessionClient.sendUserText(
14262
- [
14263
- "Read the file `smoke.txt` in the project directory using MCP tools (e.g. `read_file_chunk` or `search`).",
14264
- "Find the value after `SMOKE_SECRET=`.",
14265
- "",
14266
- `Reply with two lines exactly:`,
14267
- `1) SMOKE_SECRET=<value>`,
14268
- `2) ${token}`
14269
- ].join("\n"),
14270
- { permissionMode }
14312
+ promptLines.join("\n"),
14313
+ baseMeta
14271
14314
  );
14272
- await waitForMessage({
14315
+ const msg = await waitForMessage({
14273
14316
  sessionClient,
14274
- predicate: (r) => isAgentRecord(r) && recordIncludesNeedle(r, `SMOKE_SECRET=${secret}`) && recordIncludesNeedle(r, token),
14317
+ predicate: (r) => !isUserRecord(r) && (recordIncludesNeedle(r, `SMOKE_MARKER=${secret}`) && recordIncludesNeedle(r, token) || isProviderErrorRecord(r)),
14275
14318
  timeoutMs,
14276
14319
  progressLabel: `${agent}:tool-search`
14277
14320
  });
14321
+ const record = msg?.content;
14322
+ if (isProviderErrorRecord(record)) {
14323
+ throw new Error(`provider_error_tool_search: ${formatRecordExcerpt(record)}`);
14324
+ }
14278
14325
  }));
14279
14326
  scenarios.push(await runScenario("image-attachment", async () => {
14280
14327
  const token = `IMAGE_OK_${node_crypto.randomUUID().slice(0, 6)}`;
14281
14328
  const base64 = tinyPngBase64();
14329
+ const prompt = agent === "codex" ? `Describe the attached image in one short sentence, then reply with exactly: ${token}` : [
14330
+ 'Call `mcp__flockbay__latest_user_images` with {"limit": 1} to fetch the attached image.',
14331
+ "Then describe the attached image in one short sentence.",
14332
+ `Finally, reply with exactly: ${token}`
14333
+ ].join("\n");
14282
14334
  sessionClient.sendUserText(
14283
- `Describe the attached image in one short sentence, then reply with exactly: ${token}`,
14335
+ prompt,
14284
14336
  {
14285
- permissionMode,
14337
+ ...baseMeta,
14286
14338
  attachments: {
14287
14339
  images: [{ mimeType: "image/png", base64, name: "smoke.png" }]
14288
14340
  }
@@ -14290,7 +14342,7 @@ Update: ${updateCommand}`);
14290
14342
  );
14291
14343
  const msg = await waitForMessage({
14292
14344
  sessionClient,
14293
- predicate: (r) => isAgentRecord(r) && (recordIncludesNeedle(r, token) || agent === "codex" && recordIncludesNeedle(r, "image-attachment-missing") || recordIncludesNeedle(r, "Could not process image") || recordIncludesNeedle(r, "messages.") || recordIncludesNeedle(r, "invalid_request_error")),
14345
+ predicate: (r) => !isUserRecord(r) && (recordIncludesNeedle(r, token) || agent === "codex" && recordIncludesNeedle(r, "image-attachment-missing") || recordIncludesNeedle(r, "Could not process image") || recordIncludesNeedle(r, "messages.") || recordIncludesNeedle(r, "invalid_request_error") || isProviderErrorRecord(r)),
14294
14346
  timeoutMs,
14295
14347
  progressLabel: `${agent}:image-attachment`
14296
14348
  });
@@ -14298,6 +14350,9 @@ Update: ${updateCommand}`);
14298
14350
  if (agent === "codex" && recordIncludesNeedle(record, "image-attachment-missing")) {
14299
14351
  throw new Error("image-attachment-missing (Codex did not receive images; check latest_user_images tool + vision support)");
14300
14352
  }
14353
+ if (isProviderErrorRecord(record)) {
14354
+ throw new Error(`provider_error_image_attachment: ${formatRecordExcerpt(record)}`);
14355
+ }
14301
14356
  if (recordIncludesNeedle(record, "Could not process image") || recordIncludesNeedle(record, "invalid_request_error")) {
14302
14357
  throw new Error("provider_image_invalid (image rejected by provider; check base64/mimeType pipeline)");
14303
14358
  }
@@ -14350,6 +14405,10 @@ Options:
14350
14405
  --all Run all agents (default)
14351
14406
  --agent, -a Run a single agent
14352
14407
  --agents Comma-separated list of agents
14408
+ --codex-model Codex model mode override (e.g. gpt-5.3-codex-medium)
14409
+ --model Alias of --codex-model (Codex only)
14410
+ --gemini-model Gemini model id override (e.g. gemini-2.5-pro)
14411
+ --claude-model Claude model id override (e.g. claude-sonnet-4-6)
14353
14412
  --directory, --dir Directory to run the session in (defaults to a temp folder)
14354
14413
  --timeout-ms Per-scenario timeout (default 90000; Windows default 240000)
14355
14414
  --keep Do not stop session or delete temp dir
@@ -15153,7 +15212,7 @@ async function authAndSetupMachineIfNeeded() {
15153
15212
  process.exit(1);
15154
15213
  }
15155
15214
  try {
15156
- const { migrateUnrealMcpToFlockbayMcp } = await Promise.resolve().then(function () { return require('./migratePlugin-Bd4y5ID_.cjs'); });
15215
+ const { migrateUnrealMcpToFlockbayMcp } = await Promise.resolve().then(function () { return require('./migratePlugin-wFK-k3Wk.cjs'); });
15157
15216
  const result = migrateUnrealMcpToFlockbayMcp({
15158
15217
  engineRoot,
15159
15218
  projectUprojectPath: project || void 0,
@@ -15263,19 +15322,36 @@ async function authAndSetupMachineIfNeeded() {
15263
15322
  if (!parsed.skipUnreal) {
15264
15323
  const engineRoot = readArgValue(startArgs, "--engine-root") || (process.env.UE_ENGINE_ROOT || "").trim() || (process.env.ENGINE_ROOT || "").trim() || null;
15265
15324
  if (engineRoot) {
15266
- const shouldInstall = await promptYesNo(`Install Flockbay MCP plugin into this Unreal Engine folder?
15267
- ${engineRoot}`, {
15268
- defaultYes: true
15269
- });
15270
- if (shouldInstall) {
15271
- const result = types.installUnrealMcpPluginToEngine(engineRoot);
15272
- if (result.ok) {
15273
- console.log(`Flockbay MCP plugin installed: ${result.destDir}`);
15274
- process.env.FLOCKBAY_UNREAL_MCP_ENABLED = "1";
15275
- process.env.ENGINE_ROOT = engineRoot;
15276
- } else {
15277
- console.error(chalk.yellow("Flockbay MCP plugin install skipped:"));
15278
- console.error(chalk.gray(result.errorMessage));
15325
+ const installedEngine = types.isInstalledEngineRoot(engineRoot);
15326
+ const detection = await detectUnrealProject(process.cwd());
15327
+ const uproject = detection.ok ? detection.uprojectFile : null;
15328
+ const promptTarget = installedEngine ? uproject ? `Install Flockbay MCP plugin into this Unreal project?
15329
+ ${uproject}` : null : `Install Flockbay MCP plugin into this Unreal Engine folder?
15330
+ ${engineRoot}`;
15331
+ if (installedEngine && !uproject) {
15332
+ console.error(chalk.yellow("Unreal MCP plugin install skipped:"));
15333
+ console.error(
15334
+ chalk.gray(
15335
+ [
15336
+ "This engine appears to be an Installed Build (Epic Launcher).",
15337
+ "Engine-scope install can break builds. Run `flockbay start` from a project folder, or pass --project <path> to install as a project plugin."
15338
+ ].join("\n")
15339
+ )
15340
+ );
15341
+ } else {
15342
+ const shouldInstall = await promptYesNo(promptTarget, {
15343
+ defaultYes: true
15344
+ });
15345
+ if (shouldInstall) {
15346
+ const result = installedEngine && uproject ? types.installUnrealMcpPluginToProject(uproject) : types.installUnrealMcpPluginToEngine(engineRoot);
15347
+ if (result.ok) {
15348
+ console.log(`Flockbay MCP plugin installed: ${result.destDir}`);
15349
+ process.env.FLOCKBAY_UNREAL_MCP_ENABLED = "1";
15350
+ process.env.ENGINE_ROOT = engineRoot;
15351
+ } else {
15352
+ console.error(chalk.yellow("Flockbay MCP plugin install skipped:"));
15353
+ console.error(chalk.gray(result.errorMessage));
15354
+ }
15279
15355
  }
15280
15356
  }
15281
15357
  }
@@ -15292,7 +15368,7 @@ ${engineRoot}`, {
15292
15368
  } else if (subcommand === "codex") {
15293
15369
  try {
15294
15370
  await chdirToNearestUprojectRootIfPresent();
15295
- const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-EHn5VQcY.cjs'); });
15371
+ const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-DuyuYqoB.cjs'); });
15296
15372
  let startedBy = void 0;
15297
15373
  let sessionId = void 0;
15298
15374
  for (let i = 1; i < args.length; i++) {
@@ -15394,7 +15470,7 @@ ${engineRoot}`, {
15394
15470
  }
15395
15471
  try {
15396
15472
  await chdirToNearestUprojectRootIfPresent();
15397
- const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-DLUQln_I.cjs'); });
15473
+ const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-D7j5Y5TF.cjs'); });
15398
15474
  let startedBy = void 0;
15399
15475
  let sessionId = void 0;
15400
15476
  for (let i = 1; i < args.length; i++) {
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  require('chalk');
4
- require('./index-0Arkr3mL.cjs');
5
- require('./types-DUoq_qFJ.cjs');
4
+ require('./index-r3VTdgFI.cjs');
5
+ require('./types-DNr0xwSy.cjs');
6
6
  require('zod');
7
7
  require('node:child_process');
8
8
  require('node:fs');
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import 'chalk';
2
- import './index-BWNZJMIV.mjs';
3
- import './types-e5yrDcQZ.mjs';
2
+ import './index-D8AeVbvv.mjs';
3
+ import './types-BRJuZQj_.mjs';
4
4
  import 'zod';
5
5
  import 'node:child_process';
6
6
  import 'node:fs';
package/dist/lib.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var types = require('./types-DUoq_qFJ.cjs');
3
+ var types = require('./types-DNr0xwSy.cjs');
4
4
  require('axios');
5
5
  require('node:fs');
6
6
  require('node:os');
package/dist/lib.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-e5yrDcQZ.mjs';
1
+ export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-BRJuZQj_.mjs';
2
2
  import 'axios';
3
3
  import 'node:fs';
4
4
  import 'node:os';
@@ -1,6 +1,6 @@
1
1
  import fs__default from 'node:fs';
2
2
  import path__default from 'node:path';
3
- import { i as installUnrealMcpPluginToEngine } from './types-e5yrDcQZ.mjs';
3
+ import { i as installUnrealMcpPluginToProject, b as isInstalledEngineRoot, d as installUnrealMcpPluginToEngine } from './types-BRJuZQj_.mjs';
4
4
  import 'axios';
5
5
  import 'node:os';
6
6
  import 'node:events';
@@ -138,6 +138,23 @@ Error: ${message}` };
138
138
  }
139
139
  }
140
140
  }
141
+ if (projectUprojectPath) {
142
+ const installedProject = installUnrealMcpPluginToProject(projectUprojectPath);
143
+ if (!installedProject.ok) return { ok: false, errorMessage: installedProject.errorMessage };
144
+ return { ok: true, installedDestDir: installedProject.destDir, changedProjectPath, removedLegacyPluginDirs };
145
+ }
146
+ if (isInstalledEngineRoot(engineRoot)) {
147
+ return {
148
+ ok: false,
149
+ errorMessage: [
150
+ "This Unreal Engine install appears to be an Installed Build (Epic Launcher / precompiled engine).",
151
+ "Engine-scope install of a source-only plugin can break builds.",
152
+ "",
153
+ "Fix: re-run migration with a project path to install as a project plugin:",
154
+ " flockbay unreal-mcp migrate --engine-root <ENGINE_ROOT> --project <PATH_TO_.UPROJECT> --remove-legacy"
155
+ ].join("\n")
156
+ };
157
+ }
141
158
  const installed = installUnrealMcpPluginToEngine(engineRoot);
142
159
  if (!installed.ok) return installed;
143
160
  return { ok: true, installedDestDir: installed.destDir, changedProjectPath, removedLegacyPluginDirs };
@@ -2,7 +2,7 @@
2
2
 
3
3
  var fs = require('node:fs');
4
4
  var path = require('node:path');
5
- var types = require('./types-DUoq_qFJ.cjs');
5
+ var types = require('./types-DNr0xwSy.cjs');
6
6
  require('axios');
7
7
  require('node:os');
8
8
  require('node:events');
@@ -140,6 +140,23 @@ Error: ${message}` };
140
140
  }
141
141
  }
142
142
  }
143
+ if (projectUprojectPath) {
144
+ const installedProject = types.installUnrealMcpPluginToProject(projectUprojectPath);
145
+ if (!installedProject.ok) return { ok: false, errorMessage: installedProject.errorMessage };
146
+ return { ok: true, installedDestDir: installedProject.destDir, changedProjectPath, removedLegacyPluginDirs };
147
+ }
148
+ if (types.isInstalledEngineRoot(engineRoot)) {
149
+ return {
150
+ ok: false,
151
+ errorMessage: [
152
+ "This Unreal Engine install appears to be an Installed Build (Epic Launcher / precompiled engine).",
153
+ "Engine-scope install of a source-only plugin can break builds.",
154
+ "",
155
+ "Fix: re-run migration with a project path to install as a project plugin:",
156
+ " flockbay unreal-mcp migrate --engine-root <ENGINE_ROOT> --project <PATH_TO_.UPROJECT> --remove-legacy"
157
+ ].join("\n")
158
+ };
159
+ }
143
160
  const installed = types.installUnrealMcpPluginToEngine(engineRoot);
144
161
  if (!installed.ok) return installed;
145
162
  return { ok: true, installedDestDir: installed.destDir, changedProjectPath, removedLegacyPluginDirs };
@@ -2,7 +2,7 @@
2
2
 
3
3
  var ink = require('ink');
4
4
  var React = require('react');
5
- var types = require('./types-DUoq_qFJ.cjs');
5
+ var types = require('./types-DNr0xwSy.cjs');
6
6
  var index_js = require('@modelcontextprotocol/sdk/client/index.js');
7
7
  var z = require('zod');
8
8
  var types_js = require('@modelcontextprotocol/sdk/types.js');
@@ -16,7 +16,7 @@ var process$1 = require('node:process');
16
16
  var node_stream = require('node:stream');
17
17
  var stdio_js$1 = require('@modelcontextprotocol/sdk/client/stdio.js');
18
18
  var stdio_js = require('@modelcontextprotocol/sdk/shared/stdio.js');
19
- var index = require('./index-0Arkr3mL.cjs');
19
+ var index = require('./index-r3VTdgFI.cjs');
20
20
  require('axios');
21
21
  require('node:events');
22
22
  require('socket.io-client');
@@ -443,36 +443,17 @@ class WindowsHiddenStdioClientTransport {
443
443
  }
444
444
  }
445
445
 
446
- const DEFAULT_TIMEOUT = 14 * 24 * 60 * 60 * 1e3;
447
- function getCodexMcpCommand() {
448
- try {
449
- const codexBin = resolveCodexBin();
450
- const versionRes = node_child_process.spawnSync(codexBin, ["--version"], {
451
- encoding: "utf8",
452
- env: buildCodexSpawnEnv(codexBin),
453
- timeout: 4e3
454
- });
455
- const version = String(versionRes.stdout || "").trim();
456
- if (versionRes.status !== 0 || !version) {
457
- return "mcp-server";
458
- }
459
- const match = version.match(/codex-cli\s+(\d+\.\d+\.\d+(?:-alpha\.\d+)?)/);
460
- if (!match) return "mcp-server";
461
- const versionStr = match[1];
462
- const [major, minor, patch] = versionStr.split(/[-.]/).map(Number);
463
- if (major > 0 || minor > 43) return "mcp-server";
464
- if (minor === 43 && patch === 0) {
465
- if (versionStr.includes("-alpha.")) {
466
- const alphaNum = parseInt(versionStr.split("-alpha.")[1]);
467
- return alphaNum >= 5 ? "mcp-server" : "mcp";
468
- }
469
- return "mcp-server";
470
- }
471
- return "mcp";
472
- } catch (error) {
473
- types.logger.debug("[CodexMCP] Error detecting codex version, defaulting to mcp-server:", error);
474
- return "mcp-server";
475
- }
446
+ function parseCodexCliVersion(output) {
447
+ const raw = String(output || "").trim();
448
+ if (!raw) return null;
449
+ const m = raw.match(/codex-cli\s+(\d+)\.(\d+)\.(\d+)/);
450
+ if (!m) return null;
451
+ return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]), raw: m[0] };
452
+ }
453
+ function isCodexCliAtLeast(v, minimum) {
454
+ if (v.major !== minimum.major) return v.major > minimum.major;
455
+ if (v.minor !== minimum.minor) return v.minor > minimum.minor;
456
+ return v.patch >= minimum.patch;
476
457
  }
477
458
  function buildCodexSpawnEnv(codexBin) {
478
459
  const env = Object.keys(process.env).reduce((acc, key) => {
@@ -563,6 +544,53 @@ function resolveCodexBin() {
563
544
  if (fromBash) return fromBash;
564
545
  return "codex";
565
546
  }
547
+ function getInstalledCodexCliVersion(codexBin) {
548
+ const bin = resolveCodexBin();
549
+ try {
550
+ const res = node_child_process.spawnSync(bin, ["--version"], {
551
+ encoding: "utf8",
552
+ env: buildCodexSpawnEnv(bin),
553
+ timeout: 4e3
554
+ });
555
+ const versionOut = String(res.stdout || "").trim();
556
+ if (res.status !== 0 || !versionOut) return null;
557
+ return parseCodexCliVersion(versionOut);
558
+ } catch {
559
+ return null;
560
+ }
561
+ }
562
+
563
+ const DEFAULT_TIMEOUT = 14 * 24 * 60 * 60 * 1e3;
564
+ function getCodexMcpCommand() {
565
+ try {
566
+ const codexBin = resolveCodexBin();
567
+ const versionRes = node_child_process.spawnSync(codexBin, ["--version"], {
568
+ encoding: "utf8",
569
+ env: buildCodexSpawnEnv(codexBin),
570
+ timeout: 4e3
571
+ });
572
+ const version = String(versionRes.stdout || "").trim();
573
+ if (versionRes.status !== 0 || !version) {
574
+ return "mcp-server";
575
+ }
576
+ const match = version.match(/codex-cli\s+(\d+\.\d+\.\d+(?:-alpha\.\d+)?)/);
577
+ if (!match) return "mcp-server";
578
+ const versionStr = match[1];
579
+ const [major, minor, patch] = versionStr.split(/[-.]/).map(Number);
580
+ if (major > 0 || minor > 43) return "mcp-server";
581
+ if (minor === 43 && patch === 0) {
582
+ if (versionStr.includes("-alpha.")) {
583
+ const alphaNum = parseInt(versionStr.split("-alpha.")[1]);
584
+ return alphaNum >= 5 ? "mcp-server" : "mcp";
585
+ }
586
+ return "mcp-server";
587
+ }
588
+ return "mcp";
589
+ } catch (error) {
590
+ types.logger.debug("[CodexMCP] Error detecting codex version, defaulting to mcp-server:", error);
591
+ return "mcp-server";
592
+ }
593
+ }
566
594
  function normalizeRelativePath(input) {
567
595
  const raw = typeof input === "string" ? input.trim() : String(input ?? "").trim();
568
596
  if (!raw) return null;
@@ -2321,6 +2349,7 @@ function hashCodexSessionModeFromEnhancedMode(mode) {
2321
2349
  return hashCodexSessionMode({ model: mode.model, appendSystemPrompt: mode.appendSystemPrompt });
2322
2350
  }
2323
2351
 
2352
+ const MIN_CODEX_CLI_FOR_GPT_53_CODEX = { major: 0, minor: 104, patch: 0 };
2324
2353
  function readCodexAuthIsChatGptAccount() {
2325
2354
  try {
2326
2355
  const authPath = path.join(os.homedir(), ".codex", "auth.json");
@@ -2344,8 +2373,9 @@ function parseEffortSuffix(model) {
2344
2373
  function parseCodexModelTarget(model) {
2345
2374
  const trimmed = String(model || "").trim();
2346
2375
  if (!trimmed) return null;
2347
- if (trimmed.startsWith("gpt-5.3-codex-")) return "gpt-5.3-codex";
2348
- if (trimmed === "gpt-5-minimal" || trimmed === "gpt-5-low" || trimmed === "gpt-5-medium" || trimmed === "gpt-5-high" || trimmed.startsWith("gpt-5-codex-")) {
2376
+ if (trimmed === "gpt-5-minimal" || trimmed === "gpt-5-low" || trimmed === "gpt-5-medium" || trimmed === "gpt-5-high" || trimmed === "gpt-5-codex" || trimmed.startsWith("gpt-5-codex-") || trimmed === "gpt-5.3-codex" || trimmed.startsWith("gpt-5.3-codex-")) {
2377
+ if (trimmed === "gpt-5-codex" || trimmed.startsWith("gpt-5-codex-")) return "gpt-5.2-codex";
2378
+ if (trimmed === "gpt-5.3-codex" || trimmed.startsWith("gpt-5.3-codex-")) return "gpt-5.3-codex";
2349
2379
  return "gpt-5.2";
2350
2380
  }
2351
2381
  return null;
@@ -2356,6 +2386,13 @@ function resolveCodexModelOverride(rawModel) {
2356
2386
  const effort = parseEffortSuffix(model);
2357
2387
  const codexModelTarget = parseCodexModelTarget(model);
2358
2388
  if (codexModelTarget) {
2389
+ if (codexModelTarget === "gpt-5.3-codex") {
2390
+ const installed = getInstalledCodexCliVersion();
2391
+ if (!installed || !isCodexCliAtLeast(installed, MIN_CODEX_CLI_FOR_GPT_53_CODEX)) {
2392
+ const installedText = installed ? `${installed.major}.${installed.minor}.${installed.patch}` : "unknown";
2393
+ throw new Error(`Codex CLI >= 0.104.0 is required for gpt-5.3-codex (installed: ${installedText}).`);
2394
+ }
2395
+ }
2359
2396
  const nextEffort = effort ?? "medium";
2360
2397
  return {
2361
2398
  config: {
@@ -1,6 +1,6 @@
1
1
  import { useStdout, useInput, Box, Text, render } from 'ink';
2
2
  import React, { useState, useRef, useEffect, useCallback } from 'react';
3
- import { l as logger, A as ApiClient, p as packageJson, c as configuration, r as readSettings, b as projectPath } from './types-e5yrDcQZ.mjs';
3
+ import { l as logger, A as ApiClient, p as packageJson, c as configuration, r as readSettings, e as projectPath } from './types-BRJuZQj_.mjs';
4
4
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
5
5
  import { z } from 'zod';
6
6
  import { ElicitRequestSchema } from '@modelcontextprotocol/sdk/types.js';
@@ -14,7 +14,7 @@ import process$1 from 'node:process';
14
14
  import { PassThrough } from 'node:stream';
15
15
  import { getDefaultEnvironment } from '@modelcontextprotocol/sdk/client/stdio.js';
16
16
  import { ReadBuffer, serializeMessage } from '@modelcontextprotocol/sdk/shared/stdio.js';
17
- import { s as shouldCountToolCall, c as consumeToolQuota, f as formatQuotaDeniedReason, h as hashObject, e as enforceCliVersionPolicy, i as initialMachineMetadata, E as ElicitationHub, n as notifyDaemonSessionStarted, M as MessageQueue2, P as PLATFORM_SYSTEM_PROMPT, a as setLatestUserImages, w as withUserImagesMarker, r as registerKillSessionHandler, b as MessageBuffer, d as startFlockbayServer, g as buildProjectCapsule, t as trimIdent, j as autoFinalizeCoordinationWorkItem, k as detectScreenshotsForGate, l as applyCoordinationSideEffectsFromMcpToolResult, m as stopCaffeinate } from './index-BWNZJMIV.mjs';
17
+ import { s as shouldCountToolCall, c as consumeToolQuota, f as formatQuotaDeniedReason, h as hashObject, e as enforceCliVersionPolicy, i as initialMachineMetadata, E as ElicitationHub, n as notifyDaemonSessionStarted, M as MessageQueue2, P as PLATFORM_SYSTEM_PROMPT, a as setLatestUserImages, w as withUserImagesMarker, r as registerKillSessionHandler, b as MessageBuffer, d as startFlockbayServer, g as buildProjectCapsule, t as trimIdent, j as autoFinalizeCoordinationWorkItem, k as detectScreenshotsForGate, l as applyCoordinationSideEffectsFromMcpToolResult, m as stopCaffeinate } from './index-D8AeVbvv.mjs';
18
18
  import 'axios';
19
19
  import 'node:events';
20
20
  import 'socket.io-client';
@@ -441,36 +441,17 @@ class WindowsHiddenStdioClientTransport {
441
441
  }
442
442
  }
443
443
 
444
- const DEFAULT_TIMEOUT = 14 * 24 * 60 * 60 * 1e3;
445
- function getCodexMcpCommand() {
446
- try {
447
- const codexBin = resolveCodexBin();
448
- const versionRes = spawnSync(codexBin, ["--version"], {
449
- encoding: "utf8",
450
- env: buildCodexSpawnEnv(codexBin),
451
- timeout: 4e3
452
- });
453
- const version = String(versionRes.stdout || "").trim();
454
- if (versionRes.status !== 0 || !version) {
455
- return "mcp-server";
456
- }
457
- const match = version.match(/codex-cli\s+(\d+\.\d+\.\d+(?:-alpha\.\d+)?)/);
458
- if (!match) return "mcp-server";
459
- const versionStr = match[1];
460
- const [major, minor, patch] = versionStr.split(/[-.]/).map(Number);
461
- if (major > 0 || minor > 43) return "mcp-server";
462
- if (minor === 43 && patch === 0) {
463
- if (versionStr.includes("-alpha.")) {
464
- const alphaNum = parseInt(versionStr.split("-alpha.")[1]);
465
- return alphaNum >= 5 ? "mcp-server" : "mcp";
466
- }
467
- return "mcp-server";
468
- }
469
- return "mcp";
470
- } catch (error) {
471
- logger.debug("[CodexMCP] Error detecting codex version, defaulting to mcp-server:", error);
472
- return "mcp-server";
473
- }
444
+ function parseCodexCliVersion(output) {
445
+ const raw = String(output || "").trim();
446
+ if (!raw) return null;
447
+ const m = raw.match(/codex-cli\s+(\d+)\.(\d+)\.(\d+)/);
448
+ if (!m) return null;
449
+ return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]), raw: m[0] };
450
+ }
451
+ function isCodexCliAtLeast(v, minimum) {
452
+ if (v.major !== minimum.major) return v.major > minimum.major;
453
+ if (v.minor !== minimum.minor) return v.minor > minimum.minor;
454
+ return v.patch >= minimum.patch;
474
455
  }
475
456
  function buildCodexSpawnEnv(codexBin) {
476
457
  const env = Object.keys(process.env).reduce((acc, key) => {
@@ -561,6 +542,53 @@ function resolveCodexBin() {
561
542
  if (fromBash) return fromBash;
562
543
  return "codex";
563
544
  }
545
+ function getInstalledCodexCliVersion(codexBin) {
546
+ const bin = resolveCodexBin();
547
+ try {
548
+ const res = spawnSync(bin, ["--version"], {
549
+ encoding: "utf8",
550
+ env: buildCodexSpawnEnv(bin),
551
+ timeout: 4e3
552
+ });
553
+ const versionOut = String(res.stdout || "").trim();
554
+ if (res.status !== 0 || !versionOut) return null;
555
+ return parseCodexCliVersion(versionOut);
556
+ } catch {
557
+ return null;
558
+ }
559
+ }
560
+
561
+ const DEFAULT_TIMEOUT = 14 * 24 * 60 * 60 * 1e3;
562
+ function getCodexMcpCommand() {
563
+ try {
564
+ const codexBin = resolveCodexBin();
565
+ const versionRes = spawnSync(codexBin, ["--version"], {
566
+ encoding: "utf8",
567
+ env: buildCodexSpawnEnv(codexBin),
568
+ timeout: 4e3
569
+ });
570
+ const version = String(versionRes.stdout || "").trim();
571
+ if (versionRes.status !== 0 || !version) {
572
+ return "mcp-server";
573
+ }
574
+ const match = version.match(/codex-cli\s+(\d+\.\d+\.\d+(?:-alpha\.\d+)?)/);
575
+ if (!match) return "mcp-server";
576
+ const versionStr = match[1];
577
+ const [major, minor, patch] = versionStr.split(/[-.]/).map(Number);
578
+ if (major > 0 || minor > 43) return "mcp-server";
579
+ if (minor === 43 && patch === 0) {
580
+ if (versionStr.includes("-alpha.")) {
581
+ const alphaNum = parseInt(versionStr.split("-alpha.")[1]);
582
+ return alphaNum >= 5 ? "mcp-server" : "mcp";
583
+ }
584
+ return "mcp-server";
585
+ }
586
+ return "mcp";
587
+ } catch (error) {
588
+ logger.debug("[CodexMCP] Error detecting codex version, defaulting to mcp-server:", error);
589
+ return "mcp-server";
590
+ }
591
+ }
564
592
  function normalizeRelativePath(input) {
565
593
  const raw = typeof input === "string" ? input.trim() : String(input ?? "").trim();
566
594
  if (!raw) return null;
@@ -2319,6 +2347,7 @@ function hashCodexSessionModeFromEnhancedMode(mode) {
2319
2347
  return hashCodexSessionMode({ model: mode.model, appendSystemPrompt: mode.appendSystemPrompt });
2320
2348
  }
2321
2349
 
2350
+ const MIN_CODEX_CLI_FOR_GPT_53_CODEX = { major: 0, minor: 104, patch: 0 };
2322
2351
  function readCodexAuthIsChatGptAccount() {
2323
2352
  try {
2324
2353
  const authPath = path__default.join(os__default.homedir(), ".codex", "auth.json");
@@ -2342,8 +2371,9 @@ function parseEffortSuffix(model) {
2342
2371
  function parseCodexModelTarget(model) {
2343
2372
  const trimmed = String(model || "").trim();
2344
2373
  if (!trimmed) return null;
2345
- if (trimmed.startsWith("gpt-5.3-codex-")) return "gpt-5.3-codex";
2346
- if (trimmed === "gpt-5-minimal" || trimmed === "gpt-5-low" || trimmed === "gpt-5-medium" || trimmed === "gpt-5-high" || trimmed.startsWith("gpt-5-codex-")) {
2374
+ if (trimmed === "gpt-5-minimal" || trimmed === "gpt-5-low" || trimmed === "gpt-5-medium" || trimmed === "gpt-5-high" || trimmed === "gpt-5-codex" || trimmed.startsWith("gpt-5-codex-") || trimmed === "gpt-5.3-codex" || trimmed.startsWith("gpt-5.3-codex-")) {
2375
+ if (trimmed === "gpt-5-codex" || trimmed.startsWith("gpt-5-codex-")) return "gpt-5.2-codex";
2376
+ if (trimmed === "gpt-5.3-codex" || trimmed.startsWith("gpt-5.3-codex-")) return "gpt-5.3-codex";
2347
2377
  return "gpt-5.2";
2348
2378
  }
2349
2379
  return null;
@@ -2354,6 +2384,13 @@ function resolveCodexModelOverride(rawModel) {
2354
2384
  const effort = parseEffortSuffix(model);
2355
2385
  const codexModelTarget = parseCodexModelTarget(model);
2356
2386
  if (codexModelTarget) {
2387
+ if (codexModelTarget === "gpt-5.3-codex") {
2388
+ const installed = getInstalledCodexCliVersion();
2389
+ if (!installed || !isCodexCliAtLeast(installed, MIN_CODEX_CLI_FOR_GPT_53_CODEX)) {
2390
+ const installedText = installed ? `${installed.major}.${installed.minor}.${installed.patch}` : "unknown";
2391
+ throw new Error(`Codex CLI >= 0.104.0 is required for gpt-5.3-codex (installed: ${installedText}).`);
2392
+ }
2393
+ }
2357
2394
  const nextEffort = effort ?? "medium";
2358
2395
  return {
2359
2396
  config: {
@@ -6,8 +6,8 @@ var node_crypto = require('node:crypto');
6
6
  var os = require('node:os');
7
7
  var path = require('node:path');
8
8
  var fs$2 = require('node:fs/promises');
9
- var types = require('./types-DUoq_qFJ.cjs');
10
- var index = require('./index-0Arkr3mL.cjs');
9
+ var types = require('./types-DNr0xwSy.cjs');
10
+ var index = require('./index-r3VTdgFI.cjs');
11
11
  var node_child_process = require('node:child_process');
12
12
  var sdk = require('@agentclientprotocol/sdk');
13
13
  var fs = require('fs');
@@ -4,8 +4,8 @@ import { randomUUID, createHash } from 'node:crypto';
4
4
  import os__default from 'node:os';
5
5
  import path__default, { resolve, join as join$1, basename } from 'node:path';
6
6
  import { mkdir, writeFile, readFile } from 'node:fs/promises';
7
- import { l as logger, p as packageJson, A as ApiClient, c as configuration, r as readSettings, b as projectPath } from './types-e5yrDcQZ.mjs';
8
- import { s as shouldCountToolCall, c as consumeToolQuota, f as formatQuotaDeniedReason, h as hashObject, e as enforceCliVersionPolicy, i as initialMachineMetadata, n as notifyDaemonSessionStarted, M as MessageQueue2, g as buildProjectCapsule, a as setLatestUserImages, b as MessageBuffer, w as withUserImagesMarker, r as registerKillSessionHandler, d as startFlockbayServer, o as extractUserImagesMarker, p as getLatestUserImages, P as PLATFORM_SYSTEM_PROMPT, j as autoFinalizeCoordinationWorkItem, E as ElicitationHub, k as detectScreenshotsForGate, m as stopCaffeinate } from './index-BWNZJMIV.mjs';
7
+ import { l as logger, p as packageJson, A as ApiClient, c as configuration, r as readSettings, e as projectPath } from './types-BRJuZQj_.mjs';
8
+ import { s as shouldCountToolCall, c as consumeToolQuota, f as formatQuotaDeniedReason, h as hashObject, e as enforceCliVersionPolicy, i as initialMachineMetadata, n as notifyDaemonSessionStarted, M as MessageQueue2, g as buildProjectCapsule, a as setLatestUserImages, b as MessageBuffer, w as withUserImagesMarker, r as registerKillSessionHandler, d as startFlockbayServer, o as extractUserImagesMarker, p as getLatestUserImages, P as PLATFORM_SYSTEM_PROMPT, j as autoFinalizeCoordinationWorkItem, E as ElicitationHub, k as detectScreenshotsForGate, m as stopCaffeinate } from './index-D8AeVbvv.mjs';
9
9
  import { spawn, spawnSync } from 'node:child_process';
10
10
  import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
11
11
  import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
@@ -23,7 +23,7 @@ import { createServer } from 'http';
23
23
  import open$2 from 'open';
24
24
 
25
25
  var name = "flockbay";
26
- var version = "0.10.47";
26
+ var version = "0.10.49";
27
27
  var description = "Flockbay CLI (local agent + daemon)";
28
28
  var author = "Eduardo Orellana";
29
29
  var license = "UNLICENSED";
@@ -3455,6 +3455,14 @@ function createBackoff(opts) {
3455
3455
  }
3456
3456
  let backoff = createBackoff();
3457
3457
 
3458
+ function isInstalledEngineRoot(engineRootRaw) {
3459
+ const engineRoot = String(engineRootRaw || "").trim();
3460
+ if (!engineRoot) return false;
3461
+ if (fs__default.existsSync(path__default.join(engineRoot, "Engine", "Build", "InstalledBuild.txt"))) return true;
3462
+ if (fs__default.existsSync(path__default.join(engineRoot, "Engine", "Build", "InstalledBuild.xml"))) return true;
3463
+ return false;
3464
+ }
3465
+
3458
3466
  function looksLikeEngineRoot$1(engineRoot) {
3459
3467
  if (!engineRoot) return false;
3460
3468
  return fs__default.existsSync(path__default.join(engineRoot, "Engine")) && fs__default.existsSync(path__default.join(engineRoot, "Engine", "Plugins"));
@@ -3493,6 +3501,24 @@ function installUnrealMcpPluginToEngine(engineRootRaw) {
3493
3501
  errorMessage: `Invalid engine root (expected an Unreal Engine install folder containing Engine/\u2026): ${engineRoot || "(empty)"}`
3494
3502
  };
3495
3503
  }
3504
+ if (isInstalledEngineRoot(engineRoot)) {
3505
+ return {
3506
+ ok: false,
3507
+ errorMessage: [
3508
+ "This Unreal Engine install appears to be an Installed Build (Epic Launcher / precompiled engine).",
3509
+ "",
3510
+ "Installing a source-only plugin into Engine/Plugins can break builds with errors like:",
3511
+ ` Expecting to find a type to be declared in a module rules named 'FlockbayMCP' in 'UE5Rules'`,
3512
+ "",
3513
+ "Fix: install the Flockbay MCP plugin into your project instead:",
3514
+ " <YourProject>/Plugins/FlockbayMCP",
3515
+ "",
3516
+ "If you are using the Flockbay UI/CLI, choose a project-scope install or pass a project path.",
3517
+ "Example:",
3518
+ " flockbay unreal-mcp migrate --engine-root <ENGINE_ROOT> --project <PATH_TO_.UPROJECT> --remove-legacy"
3519
+ ].join("\n")
3520
+ };
3521
+ }
3496
3522
  const srcDir = unrealMcpPluginSourceDir();
3497
3523
  if (!fs__default.existsSync(srcDir)) {
3498
3524
  return { ok: false, errorMessage: `Missing Flockbay MCP plugin source folder: ${srcDir}` };
@@ -4725,9 +4751,35 @@ class ApiMachineClient {
4725
4751
  const enginePluginDir = path__default.join(enginePluginsDir, "FlockbayMCP");
4726
4752
  const enginePluginUplugin = path__default.join(enginePluginDir, "FlockbayMCP.uplugin");
4727
4753
  const enginePluginHasBinaries = fs__default.existsSync(path__default.join(enginePluginDir, "Binaries"));
4728
- const shouldUseProject = scope === "project" || scope === "auto" && // If the engine plugin is already installed+built, keep using it (no project mutation).
4729
- !(fs__default.existsSync(enginePluginUplugin) && enginePluginHasBinaries) && // Otherwise, use a project-scope install when the engine folder isn't writable.
4730
- !canWriteDir(enginePluginsDir) && Boolean(projectUprojectPath);
4754
+ const installedEngine = isInstalledEngineRoot(engineRoot);
4755
+ if (scope === "engine" && installedEngine) {
4756
+ return {
4757
+ success: false,
4758
+ error: [
4759
+ "Refusing engine-scope install: this engine is an Installed Build (Epic Launcher / precompiled rules).",
4760
+ "Install the Flockbay MCP plugin into your project instead (recommended).",
4761
+ "Provide projectUprojectPath and use installScope=project, or installScope=auto."
4762
+ ].join("\n")
4763
+ };
4764
+ }
4765
+ if (scope === "auto" && installedEngine && !projectUprojectPath) {
4766
+ return {
4767
+ success: false,
4768
+ error: [
4769
+ "This engine is an Installed Build (Epic Launcher / precompiled rules).",
4770
+ "To avoid breaking Unreal builds, Flockbay must install the plugin into a project.",
4771
+ "Missing required param: projectUprojectPath"
4772
+ ].join("\n")
4773
+ };
4774
+ }
4775
+ const shouldUseProject = scope === "project" || scope === "auto" && (installedEngine ? (
4776
+ // Installed engines: always use project plugin install (even if an engine copy already exists).
4777
+ Boolean(projectUprojectPath)
4778
+ ) : (
4779
+ // Non-installed engines: if the engine plugin is already installed+built, keep using it (no project mutation).
4780
+ !(fs__default.existsSync(enginePluginUplugin) && enginePluginHasBinaries) && // Otherwise, use a project-scope install when the engine folder isn't writable.
4781
+ !canWriteDir(enginePluginsDir) && Boolean(projectUprojectPath)
4782
+ ));
4731
4783
  if (shouldUseProject) {
4732
4784
  if (!projectUprojectPath) return { success: false, error: "Missing projectUprojectPath (required for project-scope install)." };
4733
4785
  const installedProject = installUnrealMcpPluginToProject(projectUprojectPath);
@@ -5293,4 +5345,4 @@ const RawJSONLinesSchema = z$1.discriminatedUnion("type", [
5293
5345
  }).passthrough()
5294
5346
  ]);
5295
5347
 
5296
- export { ApiClient as A, clearMachineId as B, authenticateCodex as C, syncCodexCliAuth as D, authenticateClaude as E, authenticateGemini as F, buildAndInstallUnrealMcpPlugin as G, installUnrealMcpPluginToProject as H, getLatestDaemonLog as I, normalizeServerUrlForNode as J, RawJSONLinesSchema as R, ApiSessionClient as a, projectPath as b, configuration as c, backoff as d, delay as e, readDaemonState as f, clearDaemonState as g, readCredentials as h, installUnrealMcpPluginToEngine as i, unrealMcpPythonDir as j, acquireDaemonLock as k, logger as l, writeDaemonState as m, ApiMachineClient as n, openBrowser as o, packageJson as p, releaseDaemonLock as q, readSettings as r, sendUnrealMcpTcpCommand as s, run$1 as t, updateSettings as u, validatePath as v, writeCredentials as w, run as x, buildShellInvocation as y, clearCredentials as z };
5348
+ export { ApiClient as A, buildShellInvocation as B, clearCredentials as C, clearMachineId as D, authenticateCodex as E, syncCodexCliAuth as F, authenticateClaude as G, authenticateGemini as H, buildAndInstallUnrealMcpPlugin as I, getLatestDaemonLog as J, normalizeServerUrlForNode as K, RawJSONLinesSchema as R, ApiSessionClient as a, isInstalledEngineRoot as b, configuration as c, installUnrealMcpPluginToEngine as d, projectPath as e, backoff as f, delay as g, readDaemonState as h, installUnrealMcpPluginToProject as i, clearDaemonState as j, readCredentials as k, logger as l, unrealMcpPythonDir as m, acquireDaemonLock as n, openBrowser as o, packageJson as p, writeDaemonState as q, readSettings as r, ApiMachineClient as s, releaseDaemonLock as t, updateSettings as u, sendUnrealMcpTcpCommand as v, writeCredentials as w, validatePath as x, run$1 as y, run as z };
@@ -44,7 +44,7 @@ function _interopNamespaceDefault(e) {
44
44
  var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
45
45
 
46
46
  var name = "flockbay";
47
- var version = "0.10.47";
47
+ var version = "0.10.49";
48
48
  var description = "Flockbay CLI (local agent + daemon)";
49
49
  var author = "Eduardo Orellana";
50
50
  var license = "UNLICENSED";
@@ -832,7 +832,7 @@ class RpcHandlerManager {
832
832
  }
833
833
  }
834
834
 
835
- const __dirname$1 = path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-DUoq_qFJ.cjs', document.baseURI).href))));
835
+ const __dirname$1 = path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-DNr0xwSy.cjs', document.baseURI).href))));
836
836
  function projectPath() {
837
837
  const path = path$1.resolve(__dirname$1, "..");
838
838
  return path;
@@ -3476,6 +3476,14 @@ function createBackoff(opts) {
3476
3476
  }
3477
3477
  let backoff = createBackoff();
3478
3478
 
3479
+ function isInstalledEngineRoot(engineRootRaw) {
3480
+ const engineRoot = String(engineRootRaw || "").trim();
3481
+ if (!engineRoot) return false;
3482
+ if (fs.existsSync(path.join(engineRoot, "Engine", "Build", "InstalledBuild.txt"))) return true;
3483
+ if (fs.existsSync(path.join(engineRoot, "Engine", "Build", "InstalledBuild.xml"))) return true;
3484
+ return false;
3485
+ }
3486
+
3479
3487
  function looksLikeEngineRoot$1(engineRoot) {
3480
3488
  if (!engineRoot) return false;
3481
3489
  return fs.existsSync(path.join(engineRoot, "Engine")) && fs.existsSync(path.join(engineRoot, "Engine", "Plugins"));
@@ -3514,6 +3522,24 @@ function installUnrealMcpPluginToEngine(engineRootRaw) {
3514
3522
  errorMessage: `Invalid engine root (expected an Unreal Engine install folder containing Engine/\u2026): ${engineRoot || "(empty)"}`
3515
3523
  };
3516
3524
  }
3525
+ if (isInstalledEngineRoot(engineRoot)) {
3526
+ return {
3527
+ ok: false,
3528
+ errorMessage: [
3529
+ "This Unreal Engine install appears to be an Installed Build (Epic Launcher / precompiled engine).",
3530
+ "",
3531
+ "Installing a source-only plugin into Engine/Plugins can break builds with errors like:",
3532
+ ` Expecting to find a type to be declared in a module rules named 'FlockbayMCP' in 'UE5Rules'`,
3533
+ "",
3534
+ "Fix: install the Flockbay MCP plugin into your project instead:",
3535
+ " <YourProject>/Plugins/FlockbayMCP",
3536
+ "",
3537
+ "If you are using the Flockbay UI/CLI, choose a project-scope install or pass a project path.",
3538
+ "Example:",
3539
+ " flockbay unreal-mcp migrate --engine-root <ENGINE_ROOT> --project <PATH_TO_.UPROJECT> --remove-legacy"
3540
+ ].join("\n")
3541
+ };
3542
+ }
3517
3543
  const srcDir = unrealMcpPluginSourceDir();
3518
3544
  if (!fs.existsSync(srcDir)) {
3519
3545
  return { ok: false, errorMessage: `Missing Flockbay MCP plugin source folder: ${srcDir}` };
@@ -4746,9 +4772,35 @@ class ApiMachineClient {
4746
4772
  const enginePluginDir = path.join(enginePluginsDir, "FlockbayMCP");
4747
4773
  const enginePluginUplugin = path.join(enginePluginDir, "FlockbayMCP.uplugin");
4748
4774
  const enginePluginHasBinaries = fs.existsSync(path.join(enginePluginDir, "Binaries"));
4749
- const shouldUseProject = scope === "project" || scope === "auto" && // If the engine plugin is already installed+built, keep using it (no project mutation).
4750
- !(fs.existsSync(enginePluginUplugin) && enginePluginHasBinaries) && // Otherwise, use a project-scope install when the engine folder isn't writable.
4751
- !canWriteDir(enginePluginsDir) && Boolean(projectUprojectPath);
4775
+ const installedEngine = isInstalledEngineRoot(engineRoot);
4776
+ if (scope === "engine" && installedEngine) {
4777
+ return {
4778
+ success: false,
4779
+ error: [
4780
+ "Refusing engine-scope install: this engine is an Installed Build (Epic Launcher / precompiled rules).",
4781
+ "Install the Flockbay MCP plugin into your project instead (recommended).",
4782
+ "Provide projectUprojectPath and use installScope=project, or installScope=auto."
4783
+ ].join("\n")
4784
+ };
4785
+ }
4786
+ if (scope === "auto" && installedEngine && !projectUprojectPath) {
4787
+ return {
4788
+ success: false,
4789
+ error: [
4790
+ "This engine is an Installed Build (Epic Launcher / precompiled rules).",
4791
+ "To avoid breaking Unreal builds, Flockbay must install the plugin into a project.",
4792
+ "Missing required param: projectUprojectPath"
4793
+ ].join("\n")
4794
+ };
4795
+ }
4796
+ const shouldUseProject = scope === "project" || scope === "auto" && (installedEngine ? (
4797
+ // Installed engines: always use project plugin install (even if an engine copy already exists).
4798
+ Boolean(projectUprojectPath)
4799
+ ) : (
4800
+ // Non-installed engines: if the engine plugin is already installed+built, keep using it (no project mutation).
4801
+ !(fs.existsSync(enginePluginUplugin) && enginePluginHasBinaries) && // Otherwise, use a project-scope install when the engine folder isn't writable.
4802
+ !canWriteDir(enginePluginsDir) && Boolean(projectUprojectPath)
4803
+ ));
4752
4804
  if (shouldUseProject) {
4753
4805
  if (!projectUprojectPath) return { success: false, error: "Missing projectUprojectPath (required for project-scope install)." };
4754
4806
  const installedProject = installUnrealMcpPluginToProject(projectUprojectPath);
@@ -5333,6 +5385,7 @@ exports.delay = delay;
5333
5385
  exports.getLatestDaemonLog = getLatestDaemonLog;
5334
5386
  exports.installUnrealMcpPluginToEngine = installUnrealMcpPluginToEngine;
5335
5387
  exports.installUnrealMcpPluginToProject = installUnrealMcpPluginToProject;
5388
+ exports.isInstalledEngineRoot = isInstalledEngineRoot;
5336
5389
  exports.logger = logger;
5337
5390
  exports.normalizeServerUrlForNode = normalizeServerUrlForNode;
5338
5391
  exports.openBrowser = openBrowser;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flockbay",
3
- "version": "0.10.47",
3
+ "version": "0.10.49",
4
4
  "description": "Flockbay CLI (local agent + daemon)",
5
5
  "author": "Eduardo Orellana",
6
6
  "license": "UNLICENSED",