flockbay 0.10.19 → 0.10.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/codex/flockbayMcpStdioBridge.cjs +8 -0
- package/dist/codex/flockbayMcpStdioBridge.mjs +8 -0
- package/dist/{index-5jfGXWTy.mjs → index-CX0Z8pmz.mjs} +158 -20
- package/dist/{index-BiUf5vLX.cjs → index-D_mglYG0.cjs} +158 -20
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.mjs +1 -1
- package/dist/{runCodex-DwsaTF4s.mjs → runCodex-Biis9GFw.mjs} +9 -3
- package/dist/{runCodex-Bh3-ebwT.cjs → runCodex-CXJW0tzo.cjs} +9 -3
- package/dist/{runGemini-qA5dD13X.mjs → runGemini-BSH4b0wu.mjs} +2 -2
- package/dist/{runGemini-hXryGqFd.cjs → runGemini-FOBXtEU6.cjs} +2 -2
- package/dist/{types-CL_3YyS9.cjs → types-BYHCKlu_.cjs} +2 -2
- package/dist/{types-BQvaA3sv.mjs → types-C4QeUggl.mjs} +1 -1
- package/package.json +1 -1
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPCommandSchema.cpp +10 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPEditorCommands.cpp +178 -7
|
@@ -614,6 +614,14 @@ async function main() {
|
|
|
614
614
|
"Unreal Editor: Launch Project",
|
|
615
615
|
"Launch Unreal Editor for a given .uproject (no auto-restart). If the editor later crashes or becomes unreachable, Flockbay will abort the current agent run and report it in the chat."
|
|
616
616
|
);
|
|
617
|
+
forwardTool(
|
|
618
|
+
"unreal_editor_relaunch_last",
|
|
619
|
+
{
|
|
620
|
+
extraArgs: z.z.array(z.z.string()).optional().describe("Optional replacement UnrealEditor command-line args (advanced).")
|
|
621
|
+
},
|
|
622
|
+
"Unreal Editor: Relaunch Last Project",
|
|
623
|
+
"Relaunch the last Unreal project previously launched via unreal_editor_launch in this session (no auto-restart). Use this after a crash once you\u2019ve fixed files."
|
|
624
|
+
);
|
|
617
625
|
forwardTool(
|
|
618
626
|
"unreal_headless_screenshot",
|
|
619
627
|
{
|
|
@@ -612,6 +612,14 @@ async function main() {
|
|
|
612
612
|
"Unreal Editor: Launch Project",
|
|
613
613
|
"Launch Unreal Editor for a given .uproject (no auto-restart). If the editor later crashes or becomes unreachable, Flockbay will abort the current agent run and report it in the chat."
|
|
614
614
|
);
|
|
615
|
+
forwardTool(
|
|
616
|
+
"unreal_editor_relaunch_last",
|
|
617
|
+
{
|
|
618
|
+
extraArgs: z.array(z.string()).optional().describe("Optional replacement UnrealEditor command-line args (advanced).")
|
|
619
|
+
},
|
|
620
|
+
"Unreal Editor: Relaunch Last Project",
|
|
621
|
+
"Relaunch the last Unreal project previously launched via unreal_editor_launch in this session (no auto-restart). Use this after a crash once you\u2019ve fixed files."
|
|
622
|
+
);
|
|
615
623
|
forwardTool(
|
|
616
624
|
"unreal_headless_screenshot",
|
|
617
625
|
{
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import chalk from 'chalk';
|
|
2
2
|
import os, { homedir } from 'node:os';
|
|
3
3
|
import { randomUUID, createCipheriv, randomBytes } from 'node:crypto';
|
|
4
|
-
import { l as logger, p as projectPath, d as backoff, e as delay, R as RawJSONLinesSchema, c as configuration, f as readDaemonState, g as clearDaemonState, b as packageJson, r as readSettings, h as readCredentials, u as updateSettings, w as writeCredentials, i as unrealMcpPythonDir, j as acquireDaemonLock, k as writeDaemonState, m as ApiMachineClient, n as releaseDaemonLock, s as sendUnrealMcpTcpCommand, A as ApiClient, o as clearCredentials, q as clearMachineId, t as installUnrealMcpPluginToEngine, v as getLatestDaemonLog, x as normalizeServerUrlForNode } from './types-
|
|
4
|
+
import { l as logger, p as projectPath, d as backoff, e as delay, R as RawJSONLinesSchema, c as configuration, f as readDaemonState, g as clearDaemonState, b as packageJson, r as readSettings, h as readCredentials, u as updateSettings, w as writeCredentials, i as unrealMcpPythonDir, j as acquireDaemonLock, k as writeDaemonState, m as ApiMachineClient, n as releaseDaemonLock, s as sendUnrealMcpTcpCommand, A as ApiClient, o as clearCredentials, q as clearMachineId, t as installUnrealMcpPluginToEngine, v as getLatestDaemonLog, x as normalizeServerUrlForNode } from './types-C4QeUggl.mjs';
|
|
5
5
|
import { spawn, execFileSync, execSync } from 'node:child_process';
|
|
6
6
|
import path, { resolve, join, dirname } from 'node:path';
|
|
7
7
|
import { createInterface } from 'node:readline';
|
|
8
8
|
import * as fs from 'node:fs';
|
|
9
9
|
import fs__default, { existsSync, readFileSync, mkdirSync, readdirSync, accessSync, constants, statSync, createReadStream, writeFileSync, unlinkSync } from 'node:fs';
|
|
10
10
|
import process$1 from 'node:process';
|
|
11
|
-
import fs$1, { readFile, access as access$1, mkdir,
|
|
11
|
+
import fs$1, { readFile, access as access$1, mkdir, readdir, stat } from 'node:fs/promises';
|
|
12
12
|
import fs$2, { watch, access } from 'fs/promises';
|
|
13
13
|
import { useStdout, useInput, Box, Text, render } from 'ink';
|
|
14
14
|
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
|
@@ -7208,7 +7208,8 @@ async function startFlockbayServer(client, options) {
|
|
|
7208
7208
|
lastReachableAtMs: 0,
|
|
7209
7209
|
lastIssueAtMs: 0,
|
|
7210
7210
|
lastIssueKey: "",
|
|
7211
|
-
launched: null
|
|
7211
|
+
launched: null,
|
|
7212
|
+
lastLaunch: null
|
|
7212
7213
|
};
|
|
7213
7214
|
const emitIssue = (event) => {
|
|
7214
7215
|
const key = `${event.kind}:${event.severity}:${event.message}`;
|
|
@@ -7233,6 +7234,93 @@ async function startFlockbayServer(client, options) {
|
|
|
7233
7234
|
}
|
|
7234
7235
|
}
|
|
7235
7236
|
};
|
|
7237
|
+
const tailText = (text, maxChars) => {
|
|
7238
|
+
const t = String(text || "").trim();
|
|
7239
|
+
if (!t) return "";
|
|
7240
|
+
if (t.length <= maxChars) return t;
|
|
7241
|
+
return t.slice(t.length - maxChars);
|
|
7242
|
+
};
|
|
7243
|
+
const findLatestFile = async (dir, filter) => {
|
|
7244
|
+
try {
|
|
7245
|
+
const entries = await readdir(dir);
|
|
7246
|
+
let best = null;
|
|
7247
|
+
for (const name of entries) {
|
|
7248
|
+
if (!filter(name)) continue;
|
|
7249
|
+
const full = path.join(dir, name);
|
|
7250
|
+
let st;
|
|
7251
|
+
try {
|
|
7252
|
+
st = await stat(full);
|
|
7253
|
+
} catch {
|
|
7254
|
+
continue;
|
|
7255
|
+
}
|
|
7256
|
+
if (!st?.isFile?.()) continue;
|
|
7257
|
+
const mtimeMs = Number(st.mtimeMs || 0);
|
|
7258
|
+
if (!best || mtimeMs > best.mtimeMs) best = { path: full, mtimeMs };
|
|
7259
|
+
}
|
|
7260
|
+
return best?.path ?? null;
|
|
7261
|
+
} catch {
|
|
7262
|
+
return null;
|
|
7263
|
+
}
|
|
7264
|
+
};
|
|
7265
|
+
const findLatestCrashDir = async (projectRoot) => {
|
|
7266
|
+
const crashesDir = path.join(projectRoot, "Saved", "Crashes");
|
|
7267
|
+
try {
|
|
7268
|
+
const entries = await readdir(crashesDir);
|
|
7269
|
+
let best = null;
|
|
7270
|
+
for (const name of entries) {
|
|
7271
|
+
const full = path.join(crashesDir, name);
|
|
7272
|
+
let st;
|
|
7273
|
+
try {
|
|
7274
|
+
st = await stat(full);
|
|
7275
|
+
} catch {
|
|
7276
|
+
continue;
|
|
7277
|
+
}
|
|
7278
|
+
if (!st?.isDirectory?.()) continue;
|
|
7279
|
+
const mtimeMs = Number(st.mtimeMs || 0);
|
|
7280
|
+
if (!best || mtimeMs > best.mtimeMs) best = { path: full, mtimeMs };
|
|
7281
|
+
}
|
|
7282
|
+
return best?.path ?? null;
|
|
7283
|
+
} catch {
|
|
7284
|
+
return null;
|
|
7285
|
+
}
|
|
7286
|
+
};
|
|
7287
|
+
const readFileTail = async (filePath, maxBytes) => {
|
|
7288
|
+
try {
|
|
7289
|
+
const buf = await readFile(filePath);
|
|
7290
|
+
const slice = buf.length > maxBytes ? buf.subarray(buf.length - maxBytes) : buf;
|
|
7291
|
+
return slice.toString("utf8");
|
|
7292
|
+
} catch {
|
|
7293
|
+
return null;
|
|
7294
|
+
}
|
|
7295
|
+
};
|
|
7296
|
+
const gatherCrashDiagnosticsBestEffort = async (uprojectPath) => {
|
|
7297
|
+
const projectRoot = path.dirname(uprojectPath);
|
|
7298
|
+
const summary = [];
|
|
7299
|
+
const detail = { uprojectPath, projectRoot };
|
|
7300
|
+
const latestLog = await findLatestFile(path.join(projectRoot, "Saved", "Logs"), (n) => n.toLowerCase().endsWith(".log"));
|
|
7301
|
+
if (latestLog) {
|
|
7302
|
+
detail.projectLogPath = latestLog;
|
|
7303
|
+
const tail = await readFileTail(latestLog, 24e3);
|
|
7304
|
+
if (tail) {
|
|
7305
|
+
detail.projectLogTail = tailText(tail, 12e3);
|
|
7306
|
+
summary.push(`Latest log: ${latestLog}`);
|
|
7307
|
+
}
|
|
7308
|
+
}
|
|
7309
|
+
const latestCrashDir = await findLatestCrashDir(projectRoot);
|
|
7310
|
+
if (latestCrashDir) {
|
|
7311
|
+
detail.latestCrashDir = latestCrashDir;
|
|
7312
|
+
const crashContext = await findLatestFile(latestCrashDir, (n) => n.toLowerCase().includes("crashcontext") && n.toLowerCase().endsWith(".xml"));
|
|
7313
|
+
if (crashContext) {
|
|
7314
|
+
detail.crashContextPath = crashContext;
|
|
7315
|
+
const tail = await readFileTail(crashContext, 24e3);
|
|
7316
|
+
if (tail) {
|
|
7317
|
+
detail.crashContextTail = tailText(tail, 12e3);
|
|
7318
|
+
summary.push(`CrashContext: ${crashContext}`);
|
|
7319
|
+
}
|
|
7320
|
+
}
|
|
7321
|
+
}
|
|
7322
|
+
return { detail, summary };
|
|
7323
|
+
};
|
|
7236
7324
|
const getUnrealEditorExe = (engineRoot) => {
|
|
7237
7325
|
const root = engineRoot.trim().replace(/[\\/]+$/, "");
|
|
7238
7326
|
if (process.platform === "darwin") {
|
|
@@ -7288,7 +7376,7 @@ ${res.stderr}`;
|
|
|
7288
7376
|
kind: "unreachable",
|
|
7289
7377
|
severity: "warning",
|
|
7290
7378
|
detectedAtMs: now,
|
|
7291
|
-
message: "Unreal Editor is no longer reachable (it was reachable earlier). It may have crashed or been closed.",
|
|
7379
|
+
message: "Unreal Editor is no longer reachable (it was reachable earlier). It may have crashed or been closed.\nNext: fix the issue, then relaunch via unreal_editor_relaunch_last (if you launched from this session) or unreal_editor_launch.",
|
|
7292
7380
|
detail: {
|
|
7293
7381
|
lastReachableAtMs: state.lastReachableAtMs
|
|
7294
7382
|
}
|
|
@@ -7320,20 +7408,35 @@ ${res.stderr}`;
|
|
|
7320
7408
|
engineRoot,
|
|
7321
7409
|
startedAtMs: Date.now()
|
|
7322
7410
|
};
|
|
7411
|
+
state.lastLaunch = {
|
|
7412
|
+
uprojectPath,
|
|
7413
|
+
engineRoot,
|
|
7414
|
+
extraArgs,
|
|
7415
|
+
startedAtMs: Date.now()
|
|
7416
|
+
};
|
|
7323
7417
|
child.on("exit", (code, signal) => {
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7418
|
+
void (async () => {
|
|
7419
|
+
const now = Date.now();
|
|
7420
|
+
const exitCode = typeof code === "number" ? code : null;
|
|
7421
|
+
const sig = typeof signal === "string" ? signal : null;
|
|
7422
|
+
const isCrash = sig !== null || exitCode !== null && exitCode !== 0;
|
|
7423
|
+
state.launched = null;
|
|
7424
|
+
if (!isCrash) return;
|
|
7425
|
+
const diag = await gatherCrashDiagnosticsBestEffort(uprojectPath).catch(() => ({ detail: {}, summary: [] }));
|
|
7426
|
+
const msgParts = [
|
|
7427
|
+
`Unreal Editor process exited unexpectedly (code=${exitCode ?? "null"} signal=${sig ?? "null"}).`,
|
|
7428
|
+
`Project: ${uprojectPath}`,
|
|
7429
|
+
...diag.summary,
|
|
7430
|
+
`Next: fix the issue, then relaunch via unreal_editor_relaunch_last (or unreal_editor_launch).`
|
|
7431
|
+
];
|
|
7432
|
+
emitIssue({
|
|
7433
|
+
kind: "process_exit",
|
|
7434
|
+
severity: "crash",
|
|
7435
|
+
detectedAtMs: now,
|
|
7436
|
+
message: msgParts.filter(Boolean).join("\n"),
|
|
7437
|
+
detail: { exitCode, signal: sig, pid, uprojectPath, ...diag.detail }
|
|
7438
|
+
});
|
|
7439
|
+
})();
|
|
7337
7440
|
});
|
|
7338
7441
|
child.on("error", (err) => {
|
|
7339
7442
|
const now = Date.now();
|
|
@@ -7342,7 +7445,9 @@ ${res.stderr}`;
|
|
|
7342
7445
|
kind: "process_exit",
|
|
7343
7446
|
severity: "crash",
|
|
7344
7447
|
detectedAtMs: now,
|
|
7345
|
-
message: `Failed to launch Unreal Editor: ${err instanceof Error ? err.message : String(err)}
|
|
7448
|
+
message: `Failed to launch Unreal Editor: ${err instanceof Error ? err.message : String(err)}
|
|
7449
|
+
Project: ${uprojectPath}
|
|
7450
|
+
Next: fix the issue, then relaunch via unreal_editor_relaunch_last (or unreal_editor_launch).`,
|
|
7346
7451
|
detail: { pid, uprojectPath }
|
|
7347
7452
|
});
|
|
7348
7453
|
});
|
|
@@ -7352,6 +7457,12 @@ ${res.stderr}`;
|
|
|
7352
7457
|
noteUnrealActivity,
|
|
7353
7458
|
noteUnrealReachable,
|
|
7354
7459
|
launchEditor,
|
|
7460
|
+
relaunchLast: async (extraArgs) => {
|
|
7461
|
+
const last = state.lastLaunch;
|
|
7462
|
+
if (!last) throw new Error("No known prior Unreal launch in this session. Use unreal_editor_launch with an explicit uprojectPath.");
|
|
7463
|
+
const mergedArgs = Array.isArray(extraArgs) && extraArgs.length > 0 ? extraArgs.filter((a) => typeof a === "string" && a.trim()) : last.extraArgs;
|
|
7464
|
+
return launchEditor({ uprojectPath: last.uprojectPath, engineRoot: last.engineRoot, extraArgs: mergedArgs });
|
|
7465
|
+
},
|
|
7355
7466
|
stop: () => {
|
|
7356
7467
|
try {
|
|
7357
7468
|
interval.unref();
|
|
@@ -8858,6 +8969,32 @@ ${String(st.stdout || "").trim()}`
|
|
|
8858
8969
|
isError: false
|
|
8859
8970
|
};
|
|
8860
8971
|
}));
|
|
8972
|
+
mcp.registerTool("unreal_editor_relaunch_last", {
|
|
8973
|
+
title: "Unreal Editor: Relaunch Last Project",
|
|
8974
|
+
description: "Relaunch the last Unreal project previously launched via unreal_editor_launch in this session (no auto-restart). Use this after a crash once you\u2019ve fixed files. If it crashes again, Flockbay will abort and report again.",
|
|
8975
|
+
inputSchema: {
|
|
8976
|
+
extraArgs: z.array(z.string()).optional().describe("Optional replacement UnrealEditor command-line args (advanced).")
|
|
8977
|
+
}
|
|
8978
|
+
}, async (args) => runWithMcpToolCard("unreal_editor_relaunch_last", args, async () => {
|
|
8979
|
+
const extraArgs = Array.isArray(args?.extraArgs) ? args.extraArgs : void 0;
|
|
8980
|
+
unrealEditorSupervisor.noteUnrealActivity();
|
|
8981
|
+
try {
|
|
8982
|
+
const launched = await unrealEditorSupervisor.relaunchLast(extraArgs);
|
|
8983
|
+
return {
|
|
8984
|
+
content: [
|
|
8985
|
+
{ type: "text", text: `Relaunched Unreal Editor (last project).` },
|
|
8986
|
+
{ type: "text", text: JSON.stringify({ pid: launched.pid, exePath: launched.exePath }, null, 2) },
|
|
8987
|
+
{ type: "text", text: "Next: wait for the editor to finish loading, then re-run UnrealMCP tools." }
|
|
8988
|
+
],
|
|
8989
|
+
isError: false
|
|
8990
|
+
};
|
|
8991
|
+
} catch (err) {
|
|
8992
|
+
return {
|
|
8993
|
+
content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
|
|
8994
|
+
isError: true
|
|
8995
|
+
};
|
|
8996
|
+
}
|
|
8997
|
+
}));
|
|
8861
8998
|
mcp.registerTool("unreal_build_project", {
|
|
8862
8999
|
title: "Unreal Build Project (UBT)",
|
|
8863
9000
|
description: "Build the project via Unreal Build Tool (via Engine/Build/BatchFiles/Build.*). Returns structured errors (file/line) and a log path for deep debugging.",
|
|
@@ -10283,6 +10420,7 @@ Fix: ${res.hint}` : "";
|
|
|
10283
10420
|
"unreal_headless_screenshot",
|
|
10284
10421
|
"unreal_latest_screenshots",
|
|
10285
10422
|
"unreal_editor_launch",
|
|
10423
|
+
"unreal_editor_relaunch_last",
|
|
10286
10424
|
"unreal_build_project",
|
|
10287
10425
|
"unreal_mcp_command",
|
|
10288
10426
|
"unreal_mcp_list_capabilities",
|
|
@@ -12064,7 +12202,7 @@ ${engineRoot}`, {
|
|
|
12064
12202
|
} else if (subcommand === "codex") {
|
|
12065
12203
|
try {
|
|
12066
12204
|
await chdirToNearestUprojectRootIfPresent();
|
|
12067
|
-
const { runCodex } = await import('./runCodex-
|
|
12205
|
+
const { runCodex } = await import('./runCodex-Biis9GFw.mjs');
|
|
12068
12206
|
let startedBy = void 0;
|
|
12069
12207
|
let sessionId = void 0;
|
|
12070
12208
|
for (let i = 1; i < args.length; i++) {
|
|
@@ -12159,7 +12297,7 @@ ${engineRoot}`, {
|
|
|
12159
12297
|
}
|
|
12160
12298
|
try {
|
|
12161
12299
|
await chdirToNearestUprojectRootIfPresent();
|
|
12162
|
-
const { runGemini } = await import('./runGemini-
|
|
12300
|
+
const { runGemini } = await import('./runGemini-BSH4b0wu.mjs');
|
|
12163
12301
|
let startedBy = void 0;
|
|
12164
12302
|
let sessionId = void 0;
|
|
12165
12303
|
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-
|
|
6
|
+
var types = require('./types-BYHCKlu_.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');
|
|
@@ -1272,7 +1272,7 @@ function buildDaemonSafeEnv(baseEnv, binPath) {
|
|
|
1272
1272
|
env[pathKey] = [...prepend, ...existingParts].join(pathSep);
|
|
1273
1273
|
return env;
|
|
1274
1274
|
}
|
|
1275
|
-
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-
|
|
1275
|
+
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-D_mglYG0.cjs', document.baseURI).href)));
|
|
1276
1276
|
const __dirname$1 = path.join(__filename$1, "..");
|
|
1277
1277
|
function getGlobalClaudeVersion(claudeExecutable) {
|
|
1278
1278
|
try {
|
|
@@ -7230,7 +7230,8 @@ async function startFlockbayServer(client, options) {
|
|
|
7230
7230
|
lastReachableAtMs: 0,
|
|
7231
7231
|
lastIssueAtMs: 0,
|
|
7232
7232
|
lastIssueKey: "",
|
|
7233
|
-
launched: null
|
|
7233
|
+
launched: null,
|
|
7234
|
+
lastLaunch: null
|
|
7234
7235
|
};
|
|
7235
7236
|
const emitIssue = (event) => {
|
|
7236
7237
|
const key = `${event.kind}:${event.severity}:${event.message}`;
|
|
@@ -7255,6 +7256,93 @@ async function startFlockbayServer(client, options) {
|
|
|
7255
7256
|
}
|
|
7256
7257
|
}
|
|
7257
7258
|
};
|
|
7259
|
+
const tailText = (text, maxChars) => {
|
|
7260
|
+
const t = String(text || "").trim();
|
|
7261
|
+
if (!t) return "";
|
|
7262
|
+
if (t.length <= maxChars) return t;
|
|
7263
|
+
return t.slice(t.length - maxChars);
|
|
7264
|
+
};
|
|
7265
|
+
const findLatestFile = async (dir, filter) => {
|
|
7266
|
+
try {
|
|
7267
|
+
const entries = await fs$2.readdir(dir);
|
|
7268
|
+
let best = null;
|
|
7269
|
+
for (const name of entries) {
|
|
7270
|
+
if (!filter(name)) continue;
|
|
7271
|
+
const full = path.join(dir, name);
|
|
7272
|
+
let st;
|
|
7273
|
+
try {
|
|
7274
|
+
st = await fs$2.stat(full);
|
|
7275
|
+
} catch {
|
|
7276
|
+
continue;
|
|
7277
|
+
}
|
|
7278
|
+
if (!st?.isFile?.()) continue;
|
|
7279
|
+
const mtimeMs = Number(st.mtimeMs || 0);
|
|
7280
|
+
if (!best || mtimeMs > best.mtimeMs) best = { path: full, mtimeMs };
|
|
7281
|
+
}
|
|
7282
|
+
return best?.path ?? null;
|
|
7283
|
+
} catch {
|
|
7284
|
+
return null;
|
|
7285
|
+
}
|
|
7286
|
+
};
|
|
7287
|
+
const findLatestCrashDir = async (projectRoot) => {
|
|
7288
|
+
const crashesDir = path.join(projectRoot, "Saved", "Crashes");
|
|
7289
|
+
try {
|
|
7290
|
+
const entries = await fs$2.readdir(crashesDir);
|
|
7291
|
+
let best = null;
|
|
7292
|
+
for (const name of entries) {
|
|
7293
|
+
const full = path.join(crashesDir, name);
|
|
7294
|
+
let st;
|
|
7295
|
+
try {
|
|
7296
|
+
st = await fs$2.stat(full);
|
|
7297
|
+
} catch {
|
|
7298
|
+
continue;
|
|
7299
|
+
}
|
|
7300
|
+
if (!st?.isDirectory?.()) continue;
|
|
7301
|
+
const mtimeMs = Number(st.mtimeMs || 0);
|
|
7302
|
+
if (!best || mtimeMs > best.mtimeMs) best = { path: full, mtimeMs };
|
|
7303
|
+
}
|
|
7304
|
+
return best?.path ?? null;
|
|
7305
|
+
} catch {
|
|
7306
|
+
return null;
|
|
7307
|
+
}
|
|
7308
|
+
};
|
|
7309
|
+
const readFileTail = async (filePath, maxBytes) => {
|
|
7310
|
+
try {
|
|
7311
|
+
const buf = await fs$2.readFile(filePath);
|
|
7312
|
+
const slice = buf.length > maxBytes ? buf.subarray(buf.length - maxBytes) : buf;
|
|
7313
|
+
return slice.toString("utf8");
|
|
7314
|
+
} catch {
|
|
7315
|
+
return null;
|
|
7316
|
+
}
|
|
7317
|
+
};
|
|
7318
|
+
const gatherCrashDiagnosticsBestEffort = async (uprojectPath) => {
|
|
7319
|
+
const projectRoot = path.dirname(uprojectPath);
|
|
7320
|
+
const summary = [];
|
|
7321
|
+
const detail = { uprojectPath, projectRoot };
|
|
7322
|
+
const latestLog = await findLatestFile(path.join(projectRoot, "Saved", "Logs"), (n) => n.toLowerCase().endsWith(".log"));
|
|
7323
|
+
if (latestLog) {
|
|
7324
|
+
detail.projectLogPath = latestLog;
|
|
7325
|
+
const tail = await readFileTail(latestLog, 24e3);
|
|
7326
|
+
if (tail) {
|
|
7327
|
+
detail.projectLogTail = tailText(tail, 12e3);
|
|
7328
|
+
summary.push(`Latest log: ${latestLog}`);
|
|
7329
|
+
}
|
|
7330
|
+
}
|
|
7331
|
+
const latestCrashDir = await findLatestCrashDir(projectRoot);
|
|
7332
|
+
if (latestCrashDir) {
|
|
7333
|
+
detail.latestCrashDir = latestCrashDir;
|
|
7334
|
+
const crashContext = await findLatestFile(latestCrashDir, (n) => n.toLowerCase().includes("crashcontext") && n.toLowerCase().endsWith(".xml"));
|
|
7335
|
+
if (crashContext) {
|
|
7336
|
+
detail.crashContextPath = crashContext;
|
|
7337
|
+
const tail = await readFileTail(crashContext, 24e3);
|
|
7338
|
+
if (tail) {
|
|
7339
|
+
detail.crashContextTail = tailText(tail, 12e3);
|
|
7340
|
+
summary.push(`CrashContext: ${crashContext}`);
|
|
7341
|
+
}
|
|
7342
|
+
}
|
|
7343
|
+
}
|
|
7344
|
+
return { detail, summary };
|
|
7345
|
+
};
|
|
7258
7346
|
const getUnrealEditorExe = (engineRoot) => {
|
|
7259
7347
|
const root = engineRoot.trim().replace(/[\\/]+$/, "");
|
|
7260
7348
|
if (process.platform === "darwin") {
|
|
@@ -7310,7 +7398,7 @@ ${res.stderr}`;
|
|
|
7310
7398
|
kind: "unreachable",
|
|
7311
7399
|
severity: "warning",
|
|
7312
7400
|
detectedAtMs: now,
|
|
7313
|
-
message: "Unreal Editor is no longer reachable (it was reachable earlier). It may have crashed or been closed.",
|
|
7401
|
+
message: "Unreal Editor is no longer reachable (it was reachable earlier). It may have crashed or been closed.\nNext: fix the issue, then relaunch via unreal_editor_relaunch_last (if you launched from this session) or unreal_editor_launch.",
|
|
7314
7402
|
detail: {
|
|
7315
7403
|
lastReachableAtMs: state.lastReachableAtMs
|
|
7316
7404
|
}
|
|
@@ -7342,20 +7430,35 @@ ${res.stderr}`;
|
|
|
7342
7430
|
engineRoot,
|
|
7343
7431
|
startedAtMs: Date.now()
|
|
7344
7432
|
};
|
|
7433
|
+
state.lastLaunch = {
|
|
7434
|
+
uprojectPath,
|
|
7435
|
+
engineRoot,
|
|
7436
|
+
extraArgs,
|
|
7437
|
+
startedAtMs: Date.now()
|
|
7438
|
+
};
|
|
7345
7439
|
child.on("exit", (code, signal) => {
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7440
|
+
void (async () => {
|
|
7441
|
+
const now = Date.now();
|
|
7442
|
+
const exitCode = typeof code === "number" ? code : null;
|
|
7443
|
+
const sig = typeof signal === "string" ? signal : null;
|
|
7444
|
+
const isCrash = sig !== null || exitCode !== null && exitCode !== 0;
|
|
7445
|
+
state.launched = null;
|
|
7446
|
+
if (!isCrash) return;
|
|
7447
|
+
const diag = await gatherCrashDiagnosticsBestEffort(uprojectPath).catch(() => ({ detail: {}, summary: [] }));
|
|
7448
|
+
const msgParts = [
|
|
7449
|
+
`Unreal Editor process exited unexpectedly (code=${exitCode ?? "null"} signal=${sig ?? "null"}).`,
|
|
7450
|
+
`Project: ${uprojectPath}`,
|
|
7451
|
+
...diag.summary,
|
|
7452
|
+
`Next: fix the issue, then relaunch via unreal_editor_relaunch_last (or unreal_editor_launch).`
|
|
7453
|
+
];
|
|
7454
|
+
emitIssue({
|
|
7455
|
+
kind: "process_exit",
|
|
7456
|
+
severity: "crash",
|
|
7457
|
+
detectedAtMs: now,
|
|
7458
|
+
message: msgParts.filter(Boolean).join("\n"),
|
|
7459
|
+
detail: { exitCode, signal: sig, pid, uprojectPath, ...diag.detail }
|
|
7460
|
+
});
|
|
7461
|
+
})();
|
|
7359
7462
|
});
|
|
7360
7463
|
child.on("error", (err) => {
|
|
7361
7464
|
const now = Date.now();
|
|
@@ -7364,7 +7467,9 @@ ${res.stderr}`;
|
|
|
7364
7467
|
kind: "process_exit",
|
|
7365
7468
|
severity: "crash",
|
|
7366
7469
|
detectedAtMs: now,
|
|
7367
|
-
message: `Failed to launch Unreal Editor: ${err instanceof Error ? err.message : String(err)}
|
|
7470
|
+
message: `Failed to launch Unreal Editor: ${err instanceof Error ? err.message : String(err)}
|
|
7471
|
+
Project: ${uprojectPath}
|
|
7472
|
+
Next: fix the issue, then relaunch via unreal_editor_relaunch_last (or unreal_editor_launch).`,
|
|
7368
7473
|
detail: { pid, uprojectPath }
|
|
7369
7474
|
});
|
|
7370
7475
|
});
|
|
@@ -7374,6 +7479,12 @@ ${res.stderr}`;
|
|
|
7374
7479
|
noteUnrealActivity,
|
|
7375
7480
|
noteUnrealReachable,
|
|
7376
7481
|
launchEditor,
|
|
7482
|
+
relaunchLast: async (extraArgs) => {
|
|
7483
|
+
const last = state.lastLaunch;
|
|
7484
|
+
if (!last) throw new Error("No known prior Unreal launch in this session. Use unreal_editor_launch with an explicit uprojectPath.");
|
|
7485
|
+
const mergedArgs = Array.isArray(extraArgs) && extraArgs.length > 0 ? extraArgs.filter((a) => typeof a === "string" && a.trim()) : last.extraArgs;
|
|
7486
|
+
return launchEditor({ uprojectPath: last.uprojectPath, engineRoot: last.engineRoot, extraArgs: mergedArgs });
|
|
7487
|
+
},
|
|
7377
7488
|
stop: () => {
|
|
7378
7489
|
try {
|
|
7379
7490
|
interval.unref();
|
|
@@ -8880,6 +8991,32 @@ ${String(st.stdout || "").trim()}`
|
|
|
8880
8991
|
isError: false
|
|
8881
8992
|
};
|
|
8882
8993
|
}));
|
|
8994
|
+
mcp.registerTool("unreal_editor_relaunch_last", {
|
|
8995
|
+
title: "Unreal Editor: Relaunch Last Project",
|
|
8996
|
+
description: "Relaunch the last Unreal project previously launched via unreal_editor_launch in this session (no auto-restart). Use this after a crash once you\u2019ve fixed files. If it crashes again, Flockbay will abort and report again.",
|
|
8997
|
+
inputSchema: {
|
|
8998
|
+
extraArgs: z.z.array(z.z.string()).optional().describe("Optional replacement UnrealEditor command-line args (advanced).")
|
|
8999
|
+
}
|
|
9000
|
+
}, async (args) => runWithMcpToolCard("unreal_editor_relaunch_last", args, async () => {
|
|
9001
|
+
const extraArgs = Array.isArray(args?.extraArgs) ? args.extraArgs : void 0;
|
|
9002
|
+
unrealEditorSupervisor.noteUnrealActivity();
|
|
9003
|
+
try {
|
|
9004
|
+
const launched = await unrealEditorSupervisor.relaunchLast(extraArgs);
|
|
9005
|
+
return {
|
|
9006
|
+
content: [
|
|
9007
|
+
{ type: "text", text: `Relaunched Unreal Editor (last project).` },
|
|
9008
|
+
{ type: "text", text: JSON.stringify({ pid: launched.pid, exePath: launched.exePath }, null, 2) },
|
|
9009
|
+
{ type: "text", text: "Next: wait for the editor to finish loading, then re-run UnrealMCP tools." }
|
|
9010
|
+
],
|
|
9011
|
+
isError: false
|
|
9012
|
+
};
|
|
9013
|
+
} catch (err) {
|
|
9014
|
+
return {
|
|
9015
|
+
content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
|
|
9016
|
+
isError: true
|
|
9017
|
+
};
|
|
9018
|
+
}
|
|
9019
|
+
}));
|
|
8883
9020
|
mcp.registerTool("unreal_build_project", {
|
|
8884
9021
|
title: "Unreal Build Project (UBT)",
|
|
8885
9022
|
description: "Build the project via Unreal Build Tool (via Engine/Build/BatchFiles/Build.*). Returns structured errors (file/line) and a log path for deep debugging.",
|
|
@@ -10305,6 +10442,7 @@ Fix: ${res.hint}` : "";
|
|
|
10305
10442
|
"unreal_headless_screenshot",
|
|
10306
10443
|
"unreal_latest_screenshots",
|
|
10307
10444
|
"unreal_editor_launch",
|
|
10445
|
+
"unreal_editor_relaunch_last",
|
|
10308
10446
|
"unreal_build_project",
|
|
10309
10447
|
"unreal_mcp_command",
|
|
10310
10448
|
"unreal_mcp_list_capabilities",
|
|
@@ -12086,7 +12224,7 @@ ${engineRoot}`, {
|
|
|
12086
12224
|
} else if (subcommand === "codex") {
|
|
12087
12225
|
try {
|
|
12088
12226
|
await chdirToNearestUprojectRootIfPresent();
|
|
12089
|
-
const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-
|
|
12227
|
+
const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-CXJW0tzo.cjs'); });
|
|
12090
12228
|
let startedBy = void 0;
|
|
12091
12229
|
let sessionId = void 0;
|
|
12092
12230
|
for (let i = 1; i < args.length; i++) {
|
|
@@ -12181,7 +12319,7 @@ ${engineRoot}`, {
|
|
|
12181
12319
|
}
|
|
12182
12320
|
try {
|
|
12183
12321
|
await chdirToNearestUprojectRootIfPresent();
|
|
12184
|
-
const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-
|
|
12322
|
+
const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-FOBXtEU6.cjs'); });
|
|
12185
12323
|
let startedBy = void 0;
|
|
12186
12324
|
let sessionId = void 0;
|
|
12187
12325
|
for (let i = 1; i < args.length; i++) {
|
package/dist/index.cjs
CHANGED
package/dist/index.mjs
CHANGED
package/dist/lib.cjs
CHANGED
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-
|
|
1
|
+
export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-C4QeUggl.mjs';
|
|
2
2
|
import 'axios';
|
|
3
3
|
import 'node:fs';
|
|
4
4
|
import 'node:os';
|
|
@@ -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, r as readSettings, p as projectPath, c as configuration, b as packageJson } from './types-
|
|
3
|
+
import { l as logger, A as ApiClient, r as readSettings, p as projectPath, c as configuration, b as packageJson } from './types-C4QeUggl.mjs';
|
|
4
4
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
5
5
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
6
6
|
import { z } from 'zod';
|
|
@@ -10,7 +10,7 @@ import fs__default from 'node:fs';
|
|
|
10
10
|
import os from 'node:os';
|
|
11
11
|
import path, { resolve, join } from 'node:path';
|
|
12
12
|
import { spawnSync } from 'node:child_process';
|
|
13
|
-
import { s as shouldCountToolCall, c as consumeToolQuota, f as formatQuotaDeniedReason, h as hashObject, 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, e as detectUnrealProject, g as buildProjectCapsule, t as trimIdent, j as autoFinalizeCoordinationWorkItem, k as detectScreenshotsForGate, l as applyCoordinationSideEffectsFromMcpToolResult, m as stopCaffeinate } from './index-
|
|
13
|
+
import { s as shouldCountToolCall, c as consumeToolQuota, f as formatQuotaDeniedReason, h as hashObject, 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, e as detectUnrealProject, g as buildProjectCapsule, t as trimIdent, j as autoFinalizeCoordinationWorkItem, k as detectScreenshotsForGate, l as applyCoordinationSideEffectsFromMcpToolResult, m as stopCaffeinate } from './index-CX0Z8pmz.mjs';
|
|
14
14
|
import 'axios';
|
|
15
15
|
import 'node:events';
|
|
16
16
|
import 'socket.io-client';
|
|
@@ -2587,7 +2587,8 @@ async function runCodex(opts) {
|
|
|
2587
2587
|
const push = (role, text) => {
|
|
2588
2588
|
const t = String(text || "").trim();
|
|
2589
2589
|
if (!t) return;
|
|
2590
|
-
|
|
2590
|
+
const label = role === "user" ? "User" : role === "assistant" ? "Assistant" : "System";
|
|
2591
|
+
lines.push(`${label}: ${t}`);
|
|
2591
2592
|
};
|
|
2592
2593
|
for (const row of rows.slice(-80)) {
|
|
2593
2594
|
const content = row?.content;
|
|
@@ -2611,6 +2612,11 @@ async function runCodex(opts) {
|
|
|
2611
2612
|
if (txt) push("assistant", txt);
|
|
2612
2613
|
}
|
|
2613
2614
|
}
|
|
2615
|
+
} else if (payload.type === "event") {
|
|
2616
|
+
const data = payload.data;
|
|
2617
|
+
if (data && typeof data === "object" && data.type === "message" && typeof data.message === "string") {
|
|
2618
|
+
push("system", data.message);
|
|
2619
|
+
}
|
|
2614
2620
|
} else if (payload.type === "codex") {
|
|
2615
2621
|
const data = payload.data;
|
|
2616
2622
|
if (data && typeof data === "object" && data.type === "message" && typeof data.message === "string") {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var ink = require('ink');
|
|
4
4
|
var React = require('react');
|
|
5
|
-
var types = require('./types-
|
|
5
|
+
var types = require('./types-BYHCKlu_.cjs');
|
|
6
6
|
var index_js = require('@modelcontextprotocol/sdk/client/index.js');
|
|
7
7
|
var stdio_js = require('@modelcontextprotocol/sdk/client/stdio.js');
|
|
8
8
|
var z = require('zod');
|
|
@@ -12,7 +12,7 @@ var fs = require('node:fs');
|
|
|
12
12
|
var os = require('node:os');
|
|
13
13
|
var path = require('node:path');
|
|
14
14
|
var node_child_process = require('node:child_process');
|
|
15
|
-
var index = require('./index-
|
|
15
|
+
var index = require('./index-D_mglYG0.cjs');
|
|
16
16
|
require('axios');
|
|
17
17
|
require('node:events');
|
|
18
18
|
require('socket.io-client');
|
|
@@ -2589,7 +2589,8 @@ async function runCodex(opts) {
|
|
|
2589
2589
|
const push = (role, text) => {
|
|
2590
2590
|
const t = String(text || "").trim();
|
|
2591
2591
|
if (!t) return;
|
|
2592
|
-
|
|
2592
|
+
const label = role === "user" ? "User" : role === "assistant" ? "Assistant" : "System";
|
|
2593
|
+
lines.push(`${label}: ${t}`);
|
|
2593
2594
|
};
|
|
2594
2595
|
for (const row of rows.slice(-80)) {
|
|
2595
2596
|
const content = row?.content;
|
|
@@ -2613,6 +2614,11 @@ async function runCodex(opts) {
|
|
|
2613
2614
|
if (txt) push("assistant", txt);
|
|
2614
2615
|
}
|
|
2615
2616
|
}
|
|
2617
|
+
} else if (payload.type === "event") {
|
|
2618
|
+
const data = payload.data;
|
|
2619
|
+
if (data && typeof data === "object" && data.type === "message" && typeof data.message === "string") {
|
|
2620
|
+
push("system", data.message);
|
|
2621
|
+
}
|
|
2616
2622
|
} else if (payload.type === "codex") {
|
|
2617
2623
|
const data = payload.data;
|
|
2618
2624
|
if (data && typeof data === "object" && data.type === "message" && typeof data.message === "string") {
|
|
@@ -4,8 +4,8 @@ import { randomUUID, createHash } from 'node:crypto';
|
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path, { resolve, join as join$1, basename } from 'node:path';
|
|
6
6
|
import { mkdir, writeFile, readFile } from 'node:fs/promises';
|
|
7
|
-
import { l as logger, b as packageJson, A as ApiClient, r as readSettings, p as projectPath, c as configuration } from './types-
|
|
8
|
-
import { s as shouldCountToolCall, c as consumeToolQuota, f as formatQuotaDeniedReason, h as hashObject, i as initialMachineMetadata, n as notifyDaemonSessionStarted, M as MessageQueue2, g as buildProjectCapsule, a as setLatestUserImages, b as MessageBuffer, w as withUserImagesMarker, P as PLATFORM_SYSTEM_PROMPT, r as registerKillSessionHandler, d as startFlockbayServer, o as extractUserImagesMarker, p as getLatestUserImages, j as autoFinalizeCoordinationWorkItem, E as ElicitationHub, k as detectScreenshotsForGate, m as stopCaffeinate } from './index-
|
|
7
|
+
import { l as logger, b as packageJson, A as ApiClient, r as readSettings, p as projectPath, c as configuration } from './types-C4QeUggl.mjs';
|
|
8
|
+
import { s as shouldCountToolCall, c as consumeToolQuota, f as formatQuotaDeniedReason, h as hashObject, i as initialMachineMetadata, n as notifyDaemonSessionStarted, M as MessageQueue2, g as buildProjectCapsule, a as setLatestUserImages, b as MessageBuffer, w as withUserImagesMarker, P as PLATFORM_SYSTEM_PROMPT, r as registerKillSessionHandler, d as startFlockbayServer, o as extractUserImagesMarker, p as getLatestUserImages, j as autoFinalizeCoordinationWorkItem, E as ElicitationHub, k as detectScreenshotsForGate, m as stopCaffeinate } from './index-CX0Z8pmz.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';
|
|
@@ -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-
|
|
10
|
-
var index = require('./index-
|
|
9
|
+
var types = require('./types-BYHCKlu_.cjs');
|
|
10
|
+
var index = require('./index-D_mglYG0.cjs');
|
|
11
11
|
var node_child_process = require('node:child_process');
|
|
12
12
|
var sdk = require('@agentclientprotocol/sdk');
|
|
13
13
|
var fs = require('fs');
|
|
@@ -42,7 +42,7 @@ function _interopNamespaceDefault(e) {
|
|
|
42
42
|
var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
|
|
43
43
|
|
|
44
44
|
var name = "flockbay";
|
|
45
|
-
var version = "0.10.
|
|
45
|
+
var version = "0.10.20";
|
|
46
46
|
var description = "Flockbay CLI (local agent + daemon)";
|
|
47
47
|
var author = "Eduardo Orellana";
|
|
48
48
|
var license = "UNLICENSED";
|
|
@@ -770,7 +770,7 @@ class RpcHandlerManager {
|
|
|
770
770
|
}
|
|
771
771
|
}
|
|
772
772
|
|
|
773
|
-
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-
|
|
773
|
+
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-BYHCKlu_.cjs', document.baseURI).href))));
|
|
774
774
|
function projectPath() {
|
|
775
775
|
const path = path$1.resolve(__dirname$1, "..");
|
|
776
776
|
return path;
|
|
@@ -21,7 +21,7 @@ import net from 'node:net';
|
|
|
21
21
|
import { spawn as spawn$1 } from 'node:child_process';
|
|
22
22
|
|
|
23
23
|
var name = "flockbay";
|
|
24
|
-
var version = "0.10.
|
|
24
|
+
var version = "0.10.20";
|
|
25
25
|
var description = "Flockbay CLI (local agent + daemon)";
|
|
26
26
|
var author = "Eduardo Orellana";
|
|
27
27
|
var license = "UNLICENSED";
|
package/package.json
CHANGED
|
@@ -360,6 +360,16 @@ static TMap<FString, FUnrealMCPCommandSchema> BuildSchemas()
|
|
|
360
360
|
|
|
361
361
|
{
|
|
362
362
|
TArray<FUnrealMCPParamSchema> P;
|
|
363
|
+
AddEnumParam(
|
|
364
|
+
P,
|
|
365
|
+
TEXT("source"),
|
|
366
|
+
false,
|
|
367
|
+
TEXT("Capture source: auto picks PIE when playing, otherwise editor viewport."),
|
|
368
|
+
{TEXT("auto"), TEXT("pie"), TEXT("editor")},
|
|
369
|
+
{TEXT("mode")},
|
|
370
|
+
{{TEXT("play"), TEXT("pie")}, {TEXT("game"), TEXT("pie")}, {TEXT("viewport"), TEXT("editor")}});
|
|
371
|
+
AddNumberParam(P, TEXT("width"), false, TEXT("Optional output width (defaults to game viewport size or 1280)."), {TEXT("resX"), TEXT("w")});
|
|
372
|
+
AddNumberParam(P, TEXT("height"), false, TEXT("Optional output height (defaults to game viewport size or 720)."), {TEXT("resY"), TEXT("h")});
|
|
363
373
|
AddStringParam(P, TEXT("filepath"), false, TEXT("Output path. If omitted, defaults to <Project>/Saved/Screenshots/Flockbay/."), {TEXT("path"), TEXT("file_path")});
|
|
364
374
|
AddStringParam(P, TEXT("filename"), false, TEXT("Optional file name (auto-generated if omitted)."));
|
|
365
375
|
AddSimpleCommand(TEXT("take_screenshot"), TEXT("Take Screenshot"), TEXT("Capture a screenshot from the editor or PIE."), P, nullptr);
|
|
@@ -49,6 +49,10 @@
|
|
|
49
49
|
#include "Engine/EngineTypes.h"
|
|
50
50
|
#include "GameFramework/PlayerController.h"
|
|
51
51
|
#include "Camera/PlayerCameraManager.h"
|
|
52
|
+
#include "Engine/LocalPlayer.h"
|
|
53
|
+
#include "Engine/SceneCapture2D.h"
|
|
54
|
+
#include "Components/SceneCaptureComponent2D.h"
|
|
55
|
+
#include "Engine/TextureRenderTarget2D.h"
|
|
52
56
|
|
|
53
57
|
FUnrealMCPEditorCommands::FUnrealMCPEditorCommands()
|
|
54
58
|
{
|
|
@@ -2295,26 +2299,193 @@ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleTakeScreenshot(const TSh
|
|
|
2295
2299
|
FilePath += TEXT(".png");
|
|
2296
2300
|
}
|
|
2297
2301
|
|
|
2298
|
-
|
|
2302
|
+
FString Source = TEXT("auto");
|
|
2303
|
+
if (Params->TryGetStringField(TEXT("source"), Source))
|
|
2304
|
+
{
|
|
2305
|
+
Source = Source.TrimStartAndEnd().ToLower();
|
|
2306
|
+
if (Source.IsEmpty())
|
|
2307
|
+
{
|
|
2308
|
+
Source = TEXT("auto");
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
double WidthNum = 0.0;
|
|
2313
|
+
double HeightNum = 0.0;
|
|
2314
|
+
int32 RequestedWidth = 0;
|
|
2315
|
+
int32 RequestedHeight = 0;
|
|
2316
|
+
if (Params->TryGetNumberField(TEXT("width"), WidthNum))
|
|
2317
|
+
{
|
|
2318
|
+
RequestedWidth = static_cast<int32>(WidthNum);
|
|
2319
|
+
}
|
|
2320
|
+
else if (Params->TryGetNumberField(TEXT("resX"), WidthNum))
|
|
2321
|
+
{
|
|
2322
|
+
RequestedWidth = static_cast<int32>(WidthNum);
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
if (Params->TryGetNumberField(TEXT("height"), HeightNum))
|
|
2326
|
+
{
|
|
2327
|
+
RequestedHeight = static_cast<int32>(HeightNum);
|
|
2328
|
+
}
|
|
2329
|
+
else if (Params->TryGetNumberField(TEXT("resY"), HeightNum))
|
|
2330
|
+
{
|
|
2331
|
+
RequestedHeight = static_cast<int32>(HeightNum);
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
const bool bPieRunning = (GEditor && GEditor->PlayWorld);
|
|
2335
|
+
const bool bWantsPie = (Source == TEXT("pie"));
|
|
2336
|
+
const bool bWantsEditor = (Source == TEXT("editor"));
|
|
2337
|
+
const bool bAuto = (Source == TEXT("auto"));
|
|
2338
|
+
if (!bWantsPie && !bWantsEditor && !bAuto)
|
|
2339
|
+
{
|
|
2340
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Invalid screenshot source: %s (expected: auto|pie|editor)"), *Source));
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
const bool bUsePieCapture = bWantsPie || (bAuto && bPieRunning);
|
|
2344
|
+
if (bUsePieCapture)
|
|
2345
|
+
{
|
|
2346
|
+
if (!bPieRunning)
|
|
2347
|
+
{
|
|
2348
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("PIE is not running (PlayWorld is null)."));
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
UWorld* PlayWorld = GEditor->PlayWorld;
|
|
2352
|
+
APlayerController* PC = PlayWorld ? PlayWorld->GetFirstPlayerController() : nullptr;
|
|
2353
|
+
if (!PC)
|
|
2354
|
+
{
|
|
2355
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to find PlayerController in PlayWorld."));
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
APlayerCameraManager* PCM = PC->PlayerCameraManager;
|
|
2359
|
+
if (!PCM)
|
|
2360
|
+
{
|
|
2361
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to find PlayerCameraManager (PlayerController.PlayerCameraManager is null)."));
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
int32 Width = RequestedWidth;
|
|
2365
|
+
int32 Height = RequestedHeight;
|
|
2366
|
+
if (Width <= 0 || Height <= 0)
|
|
2367
|
+
{
|
|
2368
|
+
if (ULocalPlayer* LocalPlayer = PC->GetLocalPlayer())
|
|
2369
|
+
{
|
|
2370
|
+
if (LocalPlayer->ViewportClient && LocalPlayer->ViewportClient->Viewport)
|
|
2371
|
+
{
|
|
2372
|
+
const FIntPoint ViewSize = LocalPlayer->ViewportClient->Viewport->GetSizeXY();
|
|
2373
|
+
if (ViewSize.X > 0 && ViewSize.Y > 0)
|
|
2374
|
+
{
|
|
2375
|
+
if (Width <= 0)
|
|
2376
|
+
{
|
|
2377
|
+
Width = ViewSize.X;
|
|
2378
|
+
}
|
|
2379
|
+
if (Height <= 0)
|
|
2380
|
+
{
|
|
2381
|
+
Height = ViewSize.Y;
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
if (Width <= 0 || Height <= 0)
|
|
2389
|
+
{
|
|
2390
|
+
Width = 1280;
|
|
2391
|
+
Height = 720;
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
const FVector CamLocation = PCM->GetCameraLocation();
|
|
2395
|
+
const FRotator CamRotation = PCM->GetCameraRotation();
|
|
2396
|
+
|
|
2397
|
+
FActorSpawnParameters SpawnParams;
|
|
2398
|
+
SpawnParams.ObjectFlags |= RF_Transient;
|
|
2399
|
+
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
2400
|
+
|
|
2401
|
+
ASceneCapture2D* CaptureActor = PlayWorld->SpawnActor<ASceneCapture2D>(ASceneCapture2D::StaticClass(), CamLocation, CamRotation, SpawnParams);
|
|
2402
|
+
if (!CaptureActor)
|
|
2403
|
+
{
|
|
2404
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to spawn ASceneCapture2D for PIE screenshot capture."));
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
USceneCaptureComponent2D* CaptureComp = CaptureActor->GetCaptureComponent2D();
|
|
2408
|
+
if (!CaptureComp)
|
|
2409
|
+
{
|
|
2410
|
+
CaptureActor->Destroy();
|
|
2411
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get SceneCaptureComponent2D from ASceneCapture2D."));
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
UTextureRenderTarget2D* RenderTarget = NewObject<UTextureRenderTarget2D>(CaptureComp);
|
|
2415
|
+
if (!RenderTarget)
|
|
2416
|
+
{
|
|
2417
|
+
CaptureActor->Destroy();
|
|
2418
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create UTextureRenderTarget2D."));
|
|
2419
|
+
}
|
|
2420
|
+
RenderTarget->InitCustomFormat(Width, Height, PF_B8G8R8A8, /*bForceLinearGamma*/ false);
|
|
2421
|
+
RenderTarget->ClearColor = FLinearColor::Black;
|
|
2422
|
+
RenderTarget->UpdateResourceImmediate(true);
|
|
2423
|
+
|
|
2424
|
+
CaptureComp->TextureTarget = RenderTarget;
|
|
2425
|
+
CaptureComp->bCaptureEveryFrame = false;
|
|
2426
|
+
CaptureComp->bCaptureOnMovement = false;
|
|
2427
|
+
CaptureComp->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR;
|
|
2428
|
+
CaptureComp->FOVAngle = PCM->GetFOVAngle();
|
|
2429
|
+
|
|
2430
|
+
CaptureComp->CaptureScene();
|
|
2431
|
+
|
|
2432
|
+
FTextureRenderTargetResource* RenderResource = RenderTarget->GameThread_GetRenderTargetResource();
|
|
2433
|
+
if (!RenderResource)
|
|
2434
|
+
{
|
|
2435
|
+
CaptureActor->Destroy();
|
|
2436
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get render target resource for PIE screenshot capture."));
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
TArray<FColor> Bitmap;
|
|
2440
|
+
if (!RenderResource->ReadPixels(Bitmap))
|
|
2441
|
+
{
|
|
2442
|
+
CaptureActor->Destroy();
|
|
2443
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to read pixels from PIE render target."));
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
CaptureActor->Destroy();
|
|
2447
|
+
|
|
2448
|
+
TArray<uint8> CompressedBitmap;
|
|
2449
|
+
FImageUtils::CompressImageArray(Width, Height, Bitmap, CompressedBitmap);
|
|
2450
|
+
if (!FFileHelper::SaveArrayToFile(CompressedBitmap, *FilePath))
|
|
2451
|
+
{
|
|
2452
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to save screenshot PNG to disk."));
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
|
2456
|
+
ResultObj->SetStringField(TEXT("filepath"), FilePath);
|
|
2457
|
+
ResultObj->SetStringField(TEXT("source"), TEXT("pie"));
|
|
2458
|
+
ResultObj->SetNumberField(TEXT("width"), Width);
|
|
2459
|
+
ResultObj->SetNumberField(TEXT("height"), Height);
|
|
2460
|
+
return ResultObj;
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
// Editor viewport screenshot (can be stale if the OS window is not focused; prefer PIE capture when playing).
|
|
2299
2464
|
if (GEditor && GEditor->GetActiveViewport())
|
|
2300
2465
|
{
|
|
2466
|
+
GEditor->RedrawAllViewports(/*bInvalidateHitProxies*/ true);
|
|
2467
|
+
|
|
2301
2468
|
FViewport* Viewport = GEditor->GetActiveViewport();
|
|
2469
|
+
const FIntPoint ViewSize = Viewport->GetSizeXY();
|
|
2302
2470
|
TArray<FColor> Bitmap;
|
|
2303
|
-
FIntRect ViewportRect(0, 0,
|
|
2304
|
-
|
|
2471
|
+
FIntRect ViewportRect(0, 0, ViewSize.X, ViewSize.Y);
|
|
2472
|
+
|
|
2305
2473
|
if (Viewport->ReadPixels(Bitmap, FReadSurfaceDataFlags(), ViewportRect))
|
|
2306
2474
|
{
|
|
2307
2475
|
TArray<uint8> CompressedBitmap;
|
|
2308
|
-
FImageUtils::CompressImageArray(
|
|
2309
|
-
|
|
2476
|
+
FImageUtils::CompressImageArray(ViewSize.X, ViewSize.Y, Bitmap, CompressedBitmap);
|
|
2477
|
+
|
|
2310
2478
|
if (FFileHelper::SaveArrayToFile(CompressedBitmap, *FilePath))
|
|
2311
2479
|
{
|
|
2312
2480
|
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
|
|
2313
2481
|
ResultObj->SetStringField(TEXT("filepath"), FilePath);
|
|
2482
|
+
ResultObj->SetStringField(TEXT("source"), TEXT("editor"));
|
|
2483
|
+
ResultObj->SetNumberField(TEXT("width"), ViewSize.X);
|
|
2484
|
+
ResultObj->SetNumberField(TEXT("height"), ViewSize.Y);
|
|
2314
2485
|
return ResultObj;
|
|
2315
2486
|
}
|
|
2316
2487
|
}
|
|
2317
2488
|
}
|
|
2318
|
-
|
|
2319
|
-
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to take screenshot"));
|
|
2489
|
+
|
|
2490
|
+
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to take screenshot."));
|
|
2320
2491
|
}
|