flockbay 0.10.27 → 0.10.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{index-D7iB7SzL.mjs → index-DGp07Mik.mjs} +1020 -450
- package/dist/{index-YM6v-xgK.cjs → index-b9kiPsW3.cjs} +1020 -450
- package/dist/index.cjs +3 -2
- package/dist/index.mjs +3 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +8 -0
- package/dist/lib.d.mts +8 -0
- package/dist/lib.mjs +1 -1
- package/dist/{runCodex-Ujz1z7Tp.mjs → runCodex-B0JRo8YP.mjs} +186 -61
- package/dist/{runCodex-DqzFigke.cjs → runCodex-Bq_-aYHm.cjs} +186 -60
- package/dist/{runGemini-BbdMLMnn.mjs → runGemini-DO9xzjyY.mjs} +3 -2
- package/dist/{runGemini-_3wOrKzX.cjs → runGemini-pQgdPy5x.cjs} +3 -2
- package/dist/{types-BVvMLtsa.cjs → types-DPBm2ycs.cjs} +59 -5
- package/dist/{types-DYcgk0yQ.mjs → types-DdJKBH6T.mjs} +58 -4
- package/package.json +4 -2
- package/scripts/run-with-root-env.mjs +48 -0
- package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPEditorCommands.cpp +7 -1
|
@@ -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 buildAndInstallUnrealMcpPlugin, x as getLatestDaemonLog, y 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 buildAndInstallUnrealMcpPlugin, x as getLatestDaemonLog, y as normalizeServerUrlForNode } from './types-DdJKBH6T.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, readdir, stat, rename, open as open$1 } from 'node:fs/promises';
|
|
11
|
+
import fs$1, { readFile, access as access$1, mkdir, readdir, stat, writeFile, rename, open as open$1 } 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';
|
|
@@ -35,6 +35,7 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/
|
|
|
35
35
|
import 'tweetnacl';
|
|
36
36
|
import { createServer as createServer$1 } from 'http';
|
|
37
37
|
import { promisify } from 'util';
|
|
38
|
+
import { deflateSync } from 'node:zlib';
|
|
38
39
|
|
|
39
40
|
class Session {
|
|
40
41
|
path;
|
|
@@ -4651,6 +4652,10 @@ async function stopDaemonSession(sessionId) {
|
|
|
4651
4652
|
const result = await daemonPost("/stop-session", { sessionId });
|
|
4652
4653
|
return result.success || false;
|
|
4653
4654
|
}
|
|
4655
|
+
async function spawnDaemonSession(directory, sessionId, agent) {
|
|
4656
|
+
const result = await daemonPost("/spawn-session", { directory, sessionId, agent });
|
|
4657
|
+
return result;
|
|
4658
|
+
}
|
|
4654
4659
|
async function stopDaemonHttp() {
|
|
4655
4660
|
await daemonPost("/stop");
|
|
4656
4661
|
}
|
|
@@ -5331,7 +5336,8 @@ function startDaemonControlServer({
|
|
|
5331
5336
|
schema: {
|
|
5332
5337
|
body: z.object({
|
|
5333
5338
|
directory: z.string(),
|
|
5334
|
-
sessionId: z.string().optional()
|
|
5339
|
+
sessionId: z.string().optional(),
|
|
5340
|
+
agent: z.enum(["codex", "claude", "gemini"]).optional()
|
|
5335
5341
|
}),
|
|
5336
5342
|
response: {
|
|
5337
5343
|
200: z.object({
|
|
@@ -5352,9 +5358,9 @@ function startDaemonControlServer({
|
|
|
5352
5358
|
}
|
|
5353
5359
|
}
|
|
5354
5360
|
}, async (request, reply) => {
|
|
5355
|
-
const { directory, sessionId } = request.body;
|
|
5356
|
-
logger.debug(`[CONTROL SERVER] Spawn session request: dir=${directory}, sessionId=${sessionId || "new"}`);
|
|
5357
|
-
const result = await spawnSession({ directory, sessionId });
|
|
5361
|
+
const { directory, sessionId, agent } = request.body;
|
|
5362
|
+
logger.debug(`[CONTROL SERVER] Spawn session request: dir=${directory}, sessionId=${sessionId || "new"}, agent=${agent || "default"}`);
|
|
5363
|
+
const result = await spawnSession({ directory, sessionId, agent });
|
|
5358
5364
|
switch (result.type) {
|
|
5359
5365
|
case "success":
|
|
5360
5366
|
if (!result.sessionId) {
|
|
@@ -6307,14 +6313,42 @@ Log: ${logPath || `not found (check ${configuration.logsDir})`}` + formatLogExce
|
|
|
6307
6313
|
httpPort: controlPort,
|
|
6308
6314
|
startedAt: Date.now()
|
|
6309
6315
|
};
|
|
6316
|
+
let hydratedMetadata = { ...initialMachineMetadata };
|
|
6317
|
+
let hydratedSeq = 0;
|
|
6318
|
+
try {
|
|
6319
|
+
const endpoint = configuration.serverUrl.replace(/\/+$/, "");
|
|
6320
|
+
const res = await fetch(`${endpoint}/v1/machines/${encodeURIComponent(machineId)}`, {
|
|
6321
|
+
method: "GET",
|
|
6322
|
+
headers: {
|
|
6323
|
+
Authorization: `Machine ${credentials.machineToken}`
|
|
6324
|
+
},
|
|
6325
|
+
signal: AbortSignal.timeout(1e4)
|
|
6326
|
+
});
|
|
6327
|
+
if (res.ok) {
|
|
6328
|
+
const data = await res.json().catch(() => null);
|
|
6329
|
+
const existingMachine = data?.machine && typeof data.machine === "object" ? data.machine : null;
|
|
6330
|
+
const existingMetadata = existingMachine?.metadata && typeof existingMachine.metadata === "object" ? existingMachine.metadata : null;
|
|
6331
|
+
if (existingMetadata) {
|
|
6332
|
+
hydratedMetadata = { ...existingMetadata, ...initialMachineMetadata };
|
|
6333
|
+
hydratedSeq = Number(existingMachine?.seq || 0);
|
|
6334
|
+
}
|
|
6335
|
+
} else if (res.status !== 404) {
|
|
6336
|
+
const detail = await res.text().catch(() => "");
|
|
6337
|
+
logger.debug(
|
|
6338
|
+
`[DAEMON RUN] Failed to hydrate machine metadata (status=${res.status}): ${detail.slice(0, 800)}`
|
|
6339
|
+
);
|
|
6340
|
+
}
|
|
6341
|
+
} catch (err) {
|
|
6342
|
+
logger.debug("[DAEMON RUN] Failed to hydrate machine metadata (best-effort):", err);
|
|
6343
|
+
}
|
|
6310
6344
|
const machine = {
|
|
6311
6345
|
id: machineId,
|
|
6312
|
-
seq:
|
|
6346
|
+
seq: hydratedSeq,
|
|
6313
6347
|
active: false,
|
|
6314
6348
|
activeAt: null,
|
|
6315
6349
|
createdAt: null,
|
|
6316
6350
|
updatedAt: null,
|
|
6317
|
-
metadata:
|
|
6351
|
+
metadata: hydratedMetadata,
|
|
6318
6352
|
daemonState: initialDaemonState
|
|
6319
6353
|
};
|
|
6320
6354
|
machineRef = machine;
|
|
@@ -7185,10 +7219,37 @@ async function runCmdAndCapture(args) {
|
|
|
7185
7219
|
function serverBaseUrl() {
|
|
7186
7220
|
return (configuration.serverUrl || "").replace(/\/+$/, "");
|
|
7187
7221
|
}
|
|
7222
|
+
function readHttpTimeoutMs(envKey, fallbackMs) {
|
|
7223
|
+
const raw = String(process.env[envKey] ?? "").trim();
|
|
7224
|
+
const parsed = Number(raw);
|
|
7225
|
+
if (Number.isFinite(parsed) && parsed > 0) return Math.max(250, parsed);
|
|
7226
|
+
return fallbackMs;
|
|
7227
|
+
}
|
|
7228
|
+
function isAbortError(err) {
|
|
7229
|
+
if (!err) return false;
|
|
7230
|
+
if (typeof err === "object" && err.name === "AbortError") return true;
|
|
7231
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7232
|
+
return /aborted|aborterror/i.test(msg);
|
|
7233
|
+
}
|
|
7234
|
+
async function fetchWithTimeout(url, init, timeoutMs, label) {
|
|
7235
|
+
const controller = new AbortController();
|
|
7236
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
7237
|
+
try {
|
|
7238
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
7239
|
+
} catch (err) {
|
|
7240
|
+
if (isAbortError(err)) {
|
|
7241
|
+
throw new Error(`Request timed out after ${timeoutMs}ms: ${label}`);
|
|
7242
|
+
}
|
|
7243
|
+
throw err;
|
|
7244
|
+
} finally {
|
|
7245
|
+
clearTimeout(timeout);
|
|
7246
|
+
}
|
|
7247
|
+
}
|
|
7188
7248
|
async function uploadScreenshotViewsForSession(args) {
|
|
7189
7249
|
const baseUrl = serverBaseUrl();
|
|
7190
7250
|
if (!baseUrl) throw new Error("Missing configuration.serverUrl");
|
|
7191
7251
|
const endpoint = `${baseUrl}/v1/sessions/${encodeURIComponent(args.sessionId)}/screenshots`;
|
|
7252
|
+
const timeoutMs = readHttpTimeoutMs("FLOCKBAY_MCP_SCREENSHOT_UPLOAD_TIMEOUT_MS", 18e4);
|
|
7192
7253
|
const form = new FormData();
|
|
7193
7254
|
for (const v of args.views) {
|
|
7194
7255
|
const buf = await readFile(v.path);
|
|
@@ -7201,15 +7262,20 @@ async function uploadScreenshotViewsForSession(args) {
|
|
|
7201
7262
|
const blob = new Blob([buf], { type: contentType });
|
|
7202
7263
|
form.append(`file:${v.id}`, blob, filename);
|
|
7203
7264
|
}
|
|
7204
|
-
const res = await
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7265
|
+
const res = await fetchWithTimeout(
|
|
7266
|
+
endpoint,
|
|
7267
|
+
{
|
|
7268
|
+
method: "POST",
|
|
7269
|
+
headers: {
|
|
7270
|
+
// This tool runs inside the CLI/daemon context, so we authenticate as the machine.
|
|
7271
|
+
// The backend accepts `Machine <token>` for machine-scoped auth.
|
|
7272
|
+
Authorization: `Machine ${args.token}`
|
|
7273
|
+
},
|
|
7274
|
+
body: form
|
|
7210
7275
|
},
|
|
7211
|
-
|
|
7212
|
-
|
|
7276
|
+
timeoutMs,
|
|
7277
|
+
`POST ${endpoint}`
|
|
7278
|
+
);
|
|
7213
7279
|
if (!res.ok) {
|
|
7214
7280
|
const text = await res.text().catch(() => "");
|
|
7215
7281
|
throw new Error(`Screenshot upload failed (${res.status}): ${text || res.statusText}`);
|
|
@@ -7333,14 +7399,21 @@ async function startFlockbayServer(client, options) {
|
|
|
7333
7399
|
logger.debug("[flockbayMCP] Failed to register elicitation RPC handler", err);
|
|
7334
7400
|
}
|
|
7335
7401
|
const postJson = async (pathname, body) => {
|
|
7336
|
-
const
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7402
|
+
const timeoutMs = readHttpTimeoutMs("FLOCKBAY_MCP_HTTP_TIMEOUT_MS", 6e4);
|
|
7403
|
+
const url = `${configuration.serverUrl.replace(/\/+$/, "")}${pathname}`;
|
|
7404
|
+
const res = await fetchWithTimeout(
|
|
7405
|
+
url,
|
|
7406
|
+
{
|
|
7407
|
+
method: "POST",
|
|
7408
|
+
headers: {
|
|
7409
|
+
Authorization: `Machine ${client.getAuthToken()}`,
|
|
7410
|
+
"Content-Type": "application/json"
|
|
7411
|
+
},
|
|
7412
|
+
body: JSON.stringify(body ?? {})
|
|
7341
7413
|
},
|
|
7342
|
-
|
|
7343
|
-
|
|
7414
|
+
timeoutMs,
|
|
7415
|
+
`POST ${url}`
|
|
7416
|
+
);
|
|
7344
7417
|
const data = await res.json().catch(() => null);
|
|
7345
7418
|
if (!res.ok) {
|
|
7346
7419
|
const msg = typeof data?.error === "string" ? data.error : `Request failed (${res.status})`;
|
|
@@ -7774,20 +7847,200 @@ ${String(st.stdout || "").trim()}`
|
|
|
7774
7847
|
isError
|
|
7775
7848
|
};
|
|
7776
7849
|
};
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7850
|
+
let mcpToolRunChain = Promise.resolve();
|
|
7851
|
+
const runMcpToolSerialized = async (fn) => {
|
|
7852
|
+
const res = mcpToolRunChain.then(fn, fn);
|
|
7853
|
+
mcpToolRunChain = res.then(() => void 0, () => void 0);
|
|
7854
|
+
return await res;
|
|
7855
|
+
};
|
|
7856
|
+
const toolResultsArtifactsRoot = () => {
|
|
7857
|
+
const sessionDir = readSessionWorkingDirectory();
|
|
7858
|
+
return path.join(sessionDir, ".flockbay", "artifacts", "tool-results");
|
|
7859
|
+
};
|
|
7860
|
+
const safePathSegment = (raw) => String(raw || "").trim().replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^_+/, "").replace(/_+$/, "") || "unknown";
|
|
7861
|
+
const coerceTextBlocks = (content) => {
|
|
7862
|
+
if (!Array.isArray(content)) return [];
|
|
7863
|
+
const out = [];
|
|
7864
|
+
for (const block of content) {
|
|
7865
|
+
if (!block || typeof block !== "object") continue;
|
|
7866
|
+
if (block.type !== "text") continue;
|
|
7867
|
+
const t = block.text;
|
|
7868
|
+
if (typeof t === "string" && t.trim()) out.push(t.trim());
|
|
7869
|
+
}
|
|
7870
|
+
return out;
|
|
7871
|
+
};
|
|
7872
|
+
const writeToolResultArtifact = async (args) => {
|
|
7873
|
+
const root = toolResultsArtifactsRoot();
|
|
7874
|
+
const toolDir = path.join(root, safePathSegment(args.toolName));
|
|
7875
|
+
await mkdir(toolDir, { recursive: true });
|
|
7876
|
+
const filePath = path.join(toolDir, `${safePathSegment(args.toolResultId)}.json`);
|
|
7877
|
+
const payload = {
|
|
7878
|
+
kind: "mcp_tool_result",
|
|
7879
|
+
toolName: args.toolName,
|
|
7880
|
+
toolResultId: args.toolResultId,
|
|
7881
|
+
isError: args.isError,
|
|
7882
|
+
createdAtMs: Date.now(),
|
|
7883
|
+
input: args.input ?? null,
|
|
7884
|
+
output: args.output ?? null
|
|
7885
|
+
};
|
|
7886
|
+
await writeFile(filePath, JSON.stringify(payload, null, 2), "utf8");
|
|
7887
|
+
return { id: args.toolResultId, path: filePath };
|
|
7888
|
+
};
|
|
7889
|
+
const buildModelMinimalToolResult = (args) => {
|
|
7890
|
+
const headLines = [];
|
|
7891
|
+
if (args.isError) headLines.push(`Tool failed: ${args.toolName}`);
|
|
7892
|
+
else headLines.push(`Tool completed: ${args.toolName}`);
|
|
7893
|
+
const texts = coerceTextBlocks(args.fullResult?.content);
|
|
7894
|
+
const first = texts[0];
|
|
7895
|
+
if (first && !/^\s*[{[]/.test(first)) headLines.push(first);
|
|
7896
|
+
headLines.push(`Evidence: toolResultId=${args.artifact.id}`);
|
|
7897
|
+
headLines.push(`Tip: use tool_result_query/tool_result_read for details (avoid re-reading full tool JSON unless necessary).`);
|
|
7898
|
+
const modelContent = [{ type: "text", text: headLines.join("\n") }];
|
|
7899
|
+
if (args.passthroughContent && Array.isArray(args.fullResult?.content)) {
|
|
7900
|
+
for (const block of args.fullResult.content) {
|
|
7901
|
+
if (!block || typeof block !== "object") continue;
|
|
7902
|
+
if (block.type === "image") modelContent.push(block);
|
|
7903
|
+
}
|
|
7904
|
+
}
|
|
7905
|
+
const views = Array.isArray(args.fullResult?.views) ? args.fullResult.views : null;
|
|
7906
|
+
const safeViews = views && views.map((v) => {
|
|
7907
|
+
if (!v || typeof v !== "object") return v;
|
|
7908
|
+
const { base64: _base64, data: _data, ...rest } = v;
|
|
7909
|
+
return rest;
|
|
7910
|
+
});
|
|
7911
|
+
return {
|
|
7912
|
+
content: modelContent,
|
|
7913
|
+
...safeViews ? { views: safeViews } : {},
|
|
7914
|
+
isError: args.isError,
|
|
7915
|
+
toolResultId: args.artifact.id
|
|
7916
|
+
};
|
|
7917
|
+
};
|
|
7918
|
+
const resolveToolResultArtifactPath = async (args) => {
|
|
7919
|
+
const root = toolResultsArtifactsRoot();
|
|
7920
|
+
const resolvedRoot = path.resolve(root);
|
|
7921
|
+
const toolResultId = String(args.toolResultId || "").trim();
|
|
7922
|
+
if (!toolResultId) return { ok: false, error: "Missing toolResultId." };
|
|
7923
|
+
const rawPath = String(args.artifactPath || "").trim();
|
|
7924
|
+
const toolName = String(args.toolName || "").trim();
|
|
7925
|
+
const safeId = safePathSegment(toolResultId);
|
|
7926
|
+
let filePath = "";
|
|
7927
|
+
if (rawPath) {
|
|
7928
|
+
filePath = rawPath;
|
|
7929
|
+
} else if (toolName) {
|
|
7930
|
+
filePath = path.join(root, safePathSegment(toolName), `${safeId}.json`);
|
|
7931
|
+
} else {
|
|
7932
|
+
if (!existsSync(root)) return { ok: false, error: `Tool results root not found: ${root}` };
|
|
7933
|
+
const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
|
|
7934
|
+
const matches = [];
|
|
7935
|
+
for (const entry of entries) {
|
|
7936
|
+
if (!entry.isDirectory()) continue;
|
|
7937
|
+
const candidate = path.join(root, entry.name, `${safeId}.json`);
|
|
7938
|
+
if (existsSync(candidate)) matches.push(candidate);
|
|
7939
|
+
}
|
|
7940
|
+
if (matches.length === 0) return { ok: false, error: `tool result not found: ${toolResultId}` };
|
|
7941
|
+
if (matches.length > 1) {
|
|
7942
|
+
return {
|
|
7943
|
+
ok: false,
|
|
7944
|
+
error: `tool result id is ambiguous (found in multiple tool folders). Provide toolName to disambiguate. toolResultId=${toolResultId}`
|
|
7945
|
+
};
|
|
7946
|
+
}
|
|
7947
|
+
filePath = matches[0];
|
|
7948
|
+
}
|
|
7949
|
+
const resolvedFile = path.resolve(filePath);
|
|
7950
|
+
if (!resolvedFile.startsWith(resolvedRoot + path.sep) && resolvedFile !== resolvedRoot) {
|
|
7951
|
+
return { ok: false, error: `Refusing to read outside tool-results root.` };
|
|
7952
|
+
}
|
|
7953
|
+
if (!existsSync(resolvedFile)) return { ok: false, error: `tool result artifact not found` };
|
|
7954
|
+
return { ok: true, path: resolvedFile };
|
|
7955
|
+
};
|
|
7956
|
+
const normalizeQueryPath = (raw) => {
|
|
7957
|
+
const s = String(raw || "").trim();
|
|
7958
|
+
if (!s) return "";
|
|
7959
|
+
if (s.startsWith("/")) return s;
|
|
7960
|
+
const parts = s.split(".").map((p) => p.trim()).filter(Boolean).map((p) => p.replace(/~/g, "~0").replace(/\//g, "~1"));
|
|
7961
|
+
return `/${parts.join("/")}`;
|
|
7962
|
+
};
|
|
7963
|
+
const getValueAtJsonPointer = (root, pointerRaw) => {
|
|
7964
|
+
const pointer = normalizeQueryPath(pointerRaw);
|
|
7965
|
+
if (!pointer) return { ok: false, error: "Empty path." };
|
|
7966
|
+
if (pointer === "/" || pointer === "") return { ok: true, value: root };
|
|
7967
|
+
if (!pointer.startsWith("/")) return { ok: false, error: 'Invalid JSON pointer (must start with "/").' };
|
|
7968
|
+
const decode = (seg) => seg.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
7969
|
+
const segments = pointer.split("/").slice(1).map((seg) => decode(seg));
|
|
7970
|
+
let cur = root;
|
|
7971
|
+
for (const seg of segments) {
|
|
7972
|
+
if (cur == null) return { ok: false, error: `Missing path segment: ${seg}` };
|
|
7973
|
+
if (Array.isArray(cur)) {
|
|
7974
|
+
if (!/^\d+$/.test(seg)) return { ok: false, error: `Expected array index but got "${seg}"` };
|
|
7975
|
+
const idx = Number(seg);
|
|
7976
|
+
if (!Number.isFinite(idx) || idx < 0 || idx >= cur.length) return { ok: false, error: `Array index out of range: ${seg}` };
|
|
7977
|
+
cur = cur[idx];
|
|
7978
|
+
continue;
|
|
7979
|
+
}
|
|
7980
|
+
if (typeof cur !== "object") return { ok: false, error: `Cannot descend into non-object at "${seg}"` };
|
|
7981
|
+
if (!(seg in cur)) return { ok: false, error: `Missing key: ${seg}` };
|
|
7982
|
+
cur = cur[seg];
|
|
7790
7983
|
}
|
|
7984
|
+
return { ok: true, value: cur };
|
|
7985
|
+
};
|
|
7986
|
+
const runWithMcpToolCard = async (toolName, input, fn, options2) => {
|
|
7987
|
+
return await runMcpToolSerialized(async () => {
|
|
7988
|
+
const uiCallId = emitFlockbayMcpToolCall(toolName, input);
|
|
7989
|
+
const toolResultId = uiCallId || randomUUID();
|
|
7990
|
+
try {
|
|
7991
|
+
const fullResult = await fn();
|
|
7992
|
+
const isError = Boolean(fullResult?.isError);
|
|
7993
|
+
const artifact = await writeToolResultArtifact({
|
|
7994
|
+
toolName,
|
|
7995
|
+
toolResultId,
|
|
7996
|
+
input,
|
|
7997
|
+
output: fullResult,
|
|
7998
|
+
isError
|
|
7999
|
+
});
|
|
8000
|
+
const uiPayload = fullResult && typeof fullResult === "object" && !Array.isArray(fullResult) ? { ...fullResult, toolResultId: artifact.id, artifactPath: artifact.path } : fullResult;
|
|
8001
|
+
emitFlockbayMcpToolResult(uiCallId, uiPayload, isError);
|
|
8002
|
+
if (options2?.modelReturn === "full") {
|
|
8003
|
+
if (fullResult && typeof fullResult === "object" && !Array.isArray(fullResult)) {
|
|
8004
|
+
return { ...fullResult, toolResultId: artifact.id, isError };
|
|
8005
|
+
}
|
|
8006
|
+
return fullResult;
|
|
8007
|
+
}
|
|
8008
|
+
return buildModelMinimalToolResult({
|
|
8009
|
+
toolName,
|
|
8010
|
+
artifact,
|
|
8011
|
+
fullResult,
|
|
8012
|
+
isError,
|
|
8013
|
+
passthroughContent: Boolean(options2?.passthroughImagesToModel)
|
|
8014
|
+
});
|
|
8015
|
+
} catch (err) {
|
|
8016
|
+
const fullResult = textToolResult(
|
|
8017
|
+
`Tool "${toolName}" failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
8018
|
+
true
|
|
8019
|
+
);
|
|
8020
|
+
const artifact = await writeToolResultArtifact({
|
|
8021
|
+
toolName,
|
|
8022
|
+
toolResultId,
|
|
8023
|
+
input,
|
|
8024
|
+
output: fullResult,
|
|
8025
|
+
isError: true
|
|
8026
|
+
});
|
|
8027
|
+
const uiPayload = fullResult && typeof fullResult === "object" && !Array.isArray(fullResult) ? { ...fullResult, toolResultId: artifact.id, artifactPath: artifact.path } : fullResult;
|
|
8028
|
+
emitFlockbayMcpToolResult(uiCallId, uiPayload, true);
|
|
8029
|
+
if (options2?.modelReturn === "full") {
|
|
8030
|
+
if (fullResult && typeof fullResult === "object" && !Array.isArray(fullResult)) {
|
|
8031
|
+
return { ...fullResult, toolResultId: artifact.id, isError: true };
|
|
8032
|
+
}
|
|
8033
|
+
return fullResult;
|
|
8034
|
+
}
|
|
8035
|
+
return buildModelMinimalToolResult({
|
|
8036
|
+
toolName,
|
|
8037
|
+
artifact,
|
|
8038
|
+
fullResult,
|
|
8039
|
+
isError: true,
|
|
8040
|
+
passthroughContent: Boolean(options2?.passthroughImagesToModel)
|
|
8041
|
+
});
|
|
8042
|
+
}
|
|
8043
|
+
});
|
|
7791
8044
|
};
|
|
7792
8045
|
mcp.registerTool(
|
|
7793
8046
|
"ask_user_question",
|
|
@@ -7811,27 +8064,15 @@ ${String(st.stdout || "").trim()}`
|
|
|
7811
8064
|
)
|
|
7812
8065
|
}
|
|
7813
8066
|
},
|
|
7814
|
-
async (args) => {
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
"Stop here and wait for the user to send a new message with the answers when ready."
|
|
7824
|
-
].join("\n"),
|
|
7825
|
-
false
|
|
7826
|
-
);
|
|
7827
|
-
emitFlockbayMcpToolResult(callId, result, false);
|
|
7828
|
-
return result;
|
|
7829
|
-
} catch (err) {
|
|
7830
|
-
const result = textToolResult(`AskUserQuestion failed: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
7831
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
7832
|
-
return result;
|
|
7833
|
-
}
|
|
7834
|
-
}
|
|
8067
|
+
async (args) => runWithMcpToolCard("ask_user_question", args, async () => {
|
|
8068
|
+
return textToolResult(
|
|
8069
|
+
[
|
|
8070
|
+
"Questions were shown to the user.",
|
|
8071
|
+
"Stop here and wait for the user to send a new message with the answers when ready."
|
|
8072
|
+
].join("\n"),
|
|
8073
|
+
false
|
|
8074
|
+
);
|
|
8075
|
+
})
|
|
7835
8076
|
);
|
|
7836
8077
|
mcp.registerTool("change_title", {
|
|
7837
8078
|
description: "Change the title of the current chat session",
|
|
@@ -7870,28 +8111,19 @@ ${String(st.stdout || "").trim()}`
|
|
|
7870
8111
|
description: "Fetch the current coordination ledger snapshot (work items + planned/active/done intent) for this project.",
|
|
7871
8112
|
inputSchema: {}
|
|
7872
8113
|
},
|
|
7873
|
-
async () => {
|
|
7874
|
-
const
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
console.error("[flockbayMCP] markCoordinationLedgerRead failed:", error);
|
|
7883
|
-
logger.debug("[flockbayMCP] markCoordinationLedgerRead failed:", error);
|
|
7884
|
-
}
|
|
8114
|
+
async () => runWithMcpToolCard("coordination_ledger_snapshot", {}, async () => {
|
|
8115
|
+
const { projectId } = readCoordinationIds();
|
|
8116
|
+
const data = await postJson("/v1/coordination/work-items/list", { projectId });
|
|
8117
|
+
if (typeof client?.markCoordinationLedgerRead === "function") {
|
|
8118
|
+
try {
|
|
8119
|
+
client.markCoordinationLedgerRead();
|
|
8120
|
+
} catch (error) {
|
|
8121
|
+
console.error("[flockbayMCP] markCoordinationLedgerRead failed:", error);
|
|
8122
|
+
logger.debug("[flockbayMCP] markCoordinationLedgerRead failed:", error);
|
|
7885
8123
|
}
|
|
7886
|
-
const result = textToolResult(JSON.stringify({ projectId, workItems: data?.workItems ?? [] }, null, 2), false);
|
|
7887
|
-
emitFlockbayMcpToolResult(callId, result, false);
|
|
7888
|
-
return result;
|
|
7889
|
-
} catch (err) {
|
|
7890
|
-
const result = textToolResult(`Failed to fetch ledger snapshot: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
7891
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
7892
|
-
return result;
|
|
7893
8124
|
}
|
|
7894
|
-
|
|
8125
|
+
return textToolResult(JSON.stringify({ projectId, workItems: data?.workItems ?? [] }, null, 2), false);
|
|
8126
|
+
})
|
|
7895
8127
|
);
|
|
7896
8128
|
mcp.registerTool(
|
|
7897
8129
|
"ledger_read",
|
|
@@ -7900,28 +8132,19 @@ ${String(st.stdout || "").trim()}`
|
|
|
7900
8132
|
description: "Alias for coordination_ledger_snapshot: fetch the current shared coordination ledger snapshot for this project.",
|
|
7901
8133
|
inputSchema: {}
|
|
7902
8134
|
},
|
|
7903
|
-
async () => {
|
|
7904
|
-
const
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
console.error("[flockbayMCP] markCoordinationLedgerRead failed:", error);
|
|
7913
|
-
logger.debug("[flockbayMCP] markCoordinationLedgerRead failed:", error);
|
|
7914
|
-
}
|
|
8135
|
+
async () => runWithMcpToolCard("ledger_read", {}, async () => {
|
|
8136
|
+
const { projectId } = readCoordinationIds();
|
|
8137
|
+
const data = await postJson("/v1/coordination/work-items/list", { projectId });
|
|
8138
|
+
if (typeof client?.markCoordinationLedgerRead === "function") {
|
|
8139
|
+
try {
|
|
8140
|
+
client.markCoordinationLedgerRead();
|
|
8141
|
+
} catch (error) {
|
|
8142
|
+
console.error("[flockbayMCP] markCoordinationLedgerRead failed:", error);
|
|
8143
|
+
logger.debug("[flockbayMCP] markCoordinationLedgerRead failed:", error);
|
|
7915
8144
|
}
|
|
7916
|
-
const result = textToolResult(JSON.stringify({ projectId, workItems: data?.workItems ?? [] }, null, 2), false);
|
|
7917
|
-
emitFlockbayMcpToolResult(callId, result, false);
|
|
7918
|
-
return result;
|
|
7919
|
-
} catch (err) {
|
|
7920
|
-
const result = textToolResult(`Failed to read ledger: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
7921
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
7922
|
-
return result;
|
|
7923
8145
|
}
|
|
7924
|
-
|
|
8146
|
+
return textToolResult(JSON.stringify({ projectId, workItems: data?.workItems ?? [] }, null, 2), false);
|
|
8147
|
+
})
|
|
7925
8148
|
);
|
|
7926
8149
|
mcp.registerTool(
|
|
7927
8150
|
"docs_index_read",
|
|
@@ -7930,58 +8153,49 @@ ${String(st.stdout || "").trim()}`
|
|
|
7930
8153
|
description: "Read the required game Documentation index (index.md) and a compact tree summary. Marks this session as having read the docs index.",
|
|
7931
8154
|
inputSchema: {}
|
|
7932
8155
|
},
|
|
7933
|
-
async () => {
|
|
7934
|
-
const
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
);
|
|
7977
|
-
emitFlockbayMcpToolResult(callId, result, false);
|
|
7978
|
-
return result;
|
|
7979
|
-
} catch (err) {
|
|
7980
|
-
const result = textToolResult(`Failed to read docs index: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
7981
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
7982
|
-
return result;
|
|
7983
|
-
}
|
|
7984
|
-
}
|
|
8156
|
+
async () => runWithMcpToolCard("docs_index_read", {}, async () => {
|
|
8157
|
+
const { projectId } = readCoordinationIds();
|
|
8158
|
+
const sessionId = String(client?.sessionId || "").trim();
|
|
8159
|
+
const meta = client?.metadata;
|
|
8160
|
+
const machineId = String(meta?.machineId || "").trim() || null;
|
|
8161
|
+
const index = await postJson("/v1/workspace/projects/docs/index", {
|
|
8162
|
+
workspaceProjectId: projectId
|
|
8163
|
+
});
|
|
8164
|
+
const tree = await postJson("/v1/workspace/projects/docs/tree", {
|
|
8165
|
+
workspaceProjectId: projectId
|
|
8166
|
+
});
|
|
8167
|
+
if (typeof client?.markDocsIndexRead === "function") {
|
|
8168
|
+
try {
|
|
8169
|
+
client.markDocsIndexRead();
|
|
8170
|
+
} catch (error) {
|
|
8171
|
+
console.error("[flockbayMCP] markDocsIndexRead failed:", error);
|
|
8172
|
+
logger.debug("[flockbayMCP] markDocsIndexRead failed:", error);
|
|
8173
|
+
}
|
|
8174
|
+
}
|
|
8175
|
+
const nodes = Array.isArray(tree?.nodes) ? tree.nodes : [];
|
|
8176
|
+
const compact = nodes.filter((n) => n && (n.kind === "folder" || n.kind === "document")).map((n) => ({
|
|
8177
|
+
id: n.id,
|
|
8178
|
+
kind: n.kind,
|
|
8179
|
+
parentId: n.parentId ?? null,
|
|
8180
|
+
name: n.name,
|
|
8181
|
+
title: n.metadata && n.metadata.title || null,
|
|
8182
|
+
isIndex: Boolean(n.isIndex)
|
|
8183
|
+
}));
|
|
8184
|
+
return textToolResult(
|
|
8185
|
+
JSON.stringify(
|
|
8186
|
+
{
|
|
8187
|
+
workspaceProjectId: projectId,
|
|
8188
|
+
sessionId,
|
|
8189
|
+
machineId,
|
|
8190
|
+
index: index?.doc ?? null,
|
|
8191
|
+
tree: compact
|
|
8192
|
+
},
|
|
8193
|
+
null,
|
|
8194
|
+
2
|
|
8195
|
+
),
|
|
8196
|
+
false
|
|
8197
|
+
);
|
|
8198
|
+
})
|
|
7985
8199
|
);
|
|
7986
8200
|
mcp.registerTool(
|
|
7987
8201
|
"docs_tree",
|
|
@@ -7990,22 +8204,16 @@ ${String(st.stdout || "").trim()}`
|
|
|
7990
8204
|
description: "Fetch the Documentation Library tree (folders + docs metadata; no bodies).",
|
|
7991
8205
|
inputSchema: {}
|
|
7992
8206
|
},
|
|
7993
|
-
async () => {
|
|
7994
|
-
const
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
})
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
} catch (err) {
|
|
8004
|
-
const result = textToolResult(`Failed to fetch docs tree: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
8005
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
8006
|
-
return result;
|
|
8007
|
-
}
|
|
8008
|
-
}
|
|
8207
|
+
async () => runWithMcpToolCard("docs_tree", {}, async () => {
|
|
8208
|
+
const { projectId } = readCoordinationIds();
|
|
8209
|
+
const tree = await postJson("/v1/workspace/projects/docs/tree", {
|
|
8210
|
+
workspaceProjectId: projectId
|
|
8211
|
+
});
|
|
8212
|
+
return textToolResult(
|
|
8213
|
+
JSON.stringify({ workspaceProjectId: projectId, library: tree?.library ?? null, nodes: tree?.nodes ?? [] }, null, 2),
|
|
8214
|
+
false
|
|
8215
|
+
);
|
|
8216
|
+
})
|
|
8009
8217
|
);
|
|
8010
8218
|
mcp.registerTool(
|
|
8011
8219
|
"docs_get",
|
|
@@ -8016,22 +8224,13 @@ ${String(st.stdout || "").trim()}`
|
|
|
8016
8224
|
nodeId: z.string().describe("Document node id.")
|
|
8017
8225
|
}
|
|
8018
8226
|
},
|
|
8019
|
-
async (args) => {
|
|
8020
|
-
const
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
const result = textToolResult(JSON.stringify({ workspaceProjectId: projectId, node: node?.node ?? null }, null, 2), false);
|
|
8027
|
-
emitFlockbayMcpToolResult(callId, result, false);
|
|
8028
|
-
return result;
|
|
8029
|
-
} catch (err) {
|
|
8030
|
-
const result = textToolResult(`Failed to fetch doc: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
8031
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
8032
|
-
return result;
|
|
8033
|
-
}
|
|
8034
|
-
}
|
|
8227
|
+
async (args) => runWithMcpToolCard("docs_get", args, async () => {
|
|
8228
|
+
const { projectId } = readCoordinationIds();
|
|
8229
|
+
const nodeId = String(args?.nodeId || "").trim();
|
|
8230
|
+
if (!nodeId) return textToolResult("Missing nodeId", true);
|
|
8231
|
+
const node = await postJson("/v1/workspace/projects/docs/get", { workspaceProjectId: projectId, nodeId });
|
|
8232
|
+
return textToolResult(JSON.stringify({ workspaceProjectId: projectId, node: node?.node ?? null }, null, 2), false);
|
|
8233
|
+
})
|
|
8035
8234
|
);
|
|
8036
8235
|
mcp.registerTool(
|
|
8037
8236
|
"docs_search",
|
|
@@ -8043,26 +8242,17 @@ ${String(st.stdout || "").trim()}`
|
|
|
8043
8242
|
limit: z.number().int().positive().optional().describe("Max results (default 50).")
|
|
8044
8243
|
}
|
|
8045
8244
|
},
|
|
8046
|
-
async (args) => {
|
|
8047
|
-
const
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
const result = textToolResult(JSON.stringify({ workspaceProjectId: projectId, results: res?.results ?? [] }, null, 2), false);
|
|
8058
|
-
emitFlockbayMcpToolResult(callId, result, false);
|
|
8059
|
-
return result;
|
|
8060
|
-
} catch (err) {
|
|
8061
|
-
const result = textToolResult(`Failed to search docs: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
8062
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
8063
|
-
return result;
|
|
8064
|
-
}
|
|
8065
|
-
}
|
|
8245
|
+
async (args) => runWithMcpToolCard("docs_search", args, async () => {
|
|
8246
|
+
const { projectId } = readCoordinationIds();
|
|
8247
|
+
const q = String(args?.q || "").trim();
|
|
8248
|
+
const limit = args?.limit;
|
|
8249
|
+
const res = await postJson("/v1/workspace/projects/docs/search", {
|
|
8250
|
+
workspaceProjectId: projectId,
|
|
8251
|
+
q,
|
|
8252
|
+
limit
|
|
8253
|
+
});
|
|
8254
|
+
return textToolResult(JSON.stringify({ workspaceProjectId: projectId, results: res?.results ?? [] }, null, 2), false);
|
|
8255
|
+
})
|
|
8066
8256
|
);
|
|
8067
8257
|
mcp.registerTool(
|
|
8068
8258
|
"docs_create_folder",
|
|
@@ -8074,26 +8264,17 @@ ${String(st.stdout || "").trim()}`
|
|
|
8074
8264
|
parentId: z.string().optional().describe("Parent folder node id (defaults to root).")
|
|
8075
8265
|
}
|
|
8076
8266
|
},
|
|
8077
|
-
async (args) => {
|
|
8078
|
-
const
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
const result = textToolResult(JSON.stringify({ workspaceProjectId: projectId, node: res?.node ?? null }, null, 2), false);
|
|
8089
|
-
emitFlockbayMcpToolResult(callId, result, false);
|
|
8090
|
-
return result;
|
|
8091
|
-
} catch (err) {
|
|
8092
|
-
const result = textToolResult(`Failed to create folder: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
8093
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
8094
|
-
return result;
|
|
8095
|
-
}
|
|
8096
|
-
}
|
|
8267
|
+
async (args) => runWithMcpToolCard("docs_create_folder", args, async () => {
|
|
8268
|
+
const { projectId } = readCoordinationIds();
|
|
8269
|
+
const name = String(args?.name || "").trim();
|
|
8270
|
+
const parentId = args?.parentId ? String(args.parentId).trim() : void 0;
|
|
8271
|
+
const res = await postJson("/v1/workspace/projects/docs/create-folder", {
|
|
8272
|
+
workspaceProjectId: projectId,
|
|
8273
|
+
name,
|
|
8274
|
+
parentId
|
|
8275
|
+
});
|
|
8276
|
+
return textToolResult(JSON.stringify({ workspaceProjectId: projectId, node: res?.node ?? null }, null, 2), false);
|
|
8277
|
+
})
|
|
8097
8278
|
);
|
|
8098
8279
|
mcp.registerTool(
|
|
8099
8280
|
"docs_create_doc",
|
|
@@ -8107,30 +8288,21 @@ ${String(st.stdout || "").trim()}`
|
|
|
8107
8288
|
metadata: z.record(z.any()).optional().describe("Metadata object (JSON).")
|
|
8108
8289
|
}
|
|
8109
8290
|
},
|
|
8110
|
-
async (args) => {
|
|
8111
|
-
const
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
const result = textToolResult(JSON.stringify({ workspaceProjectId: projectId, node: res?.node ?? null }, null, 2), false);
|
|
8126
|
-
emitFlockbayMcpToolResult(callId, result, false);
|
|
8127
|
-
return result;
|
|
8128
|
-
} catch (err) {
|
|
8129
|
-
const result = textToolResult(`Failed to create doc: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
8130
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
8131
|
-
return result;
|
|
8132
|
-
}
|
|
8133
|
-
}
|
|
8291
|
+
async (args) => runWithMcpToolCard("docs_create_doc", args, async () => {
|
|
8292
|
+
const { projectId } = readCoordinationIds();
|
|
8293
|
+
const name = String(args?.name || "").trim();
|
|
8294
|
+
const parentId = args?.parentId ? String(args.parentId).trim() : void 0;
|
|
8295
|
+
const bodyMarkdown = args?.bodyMarkdown ?? "";
|
|
8296
|
+
const metadata = args?.metadata ?? {};
|
|
8297
|
+
const res = await postJson("/v1/workspace/projects/docs/create-doc", {
|
|
8298
|
+
workspaceProjectId: projectId,
|
|
8299
|
+
name,
|
|
8300
|
+
parentId,
|
|
8301
|
+
bodyMarkdown,
|
|
8302
|
+
metadata
|
|
8303
|
+
});
|
|
8304
|
+
return textToolResult(JSON.stringify({ workspaceProjectId: projectId, node: res?.node ?? null }, null, 2), false);
|
|
8305
|
+
})
|
|
8134
8306
|
);
|
|
8135
8307
|
mcp.registerTool(
|
|
8136
8308
|
"docs_claim",
|
|
@@ -8142,32 +8314,23 @@ ${String(st.stdout || "").trim()}`
|
|
|
8142
8314
|
leaseMs: z.number().int().positive().optional().describe("Lease TTL (ms).")
|
|
8143
8315
|
}
|
|
8144
8316
|
},
|
|
8145
|
-
async (args) => {
|
|
8146
|
-
const
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
const result = textToolResult(JSON.stringify({ workspaceProjectId: projectId, ...res }, null, 2), false);
|
|
8163
|
-
emitFlockbayMcpToolResult(callId, result, false);
|
|
8164
|
-
return result;
|
|
8165
|
-
} catch (err) {
|
|
8166
|
-
const result = textToolResult(`Failed to claim doc: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
8167
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
8168
|
-
return result;
|
|
8169
|
-
}
|
|
8170
|
-
}
|
|
8317
|
+
async (args) => runWithMcpToolCard("docs_claim", args, async () => {
|
|
8318
|
+
const { projectId, workItemId } = readCoordinationIds();
|
|
8319
|
+
const sessionId = String(client?.sessionId || "").trim();
|
|
8320
|
+
const meta = client?.metadata;
|
|
8321
|
+
const machineId = String(meta?.machineId || "").trim() || null;
|
|
8322
|
+
const nodeId = String(args?.nodeId || "").trim();
|
|
8323
|
+
const leaseMs = args?.leaseMs;
|
|
8324
|
+
const res = await postJson("/v1/workspace/projects/docs/leases/claim", {
|
|
8325
|
+
workspaceProjectId: projectId,
|
|
8326
|
+
nodeId,
|
|
8327
|
+
workItemId,
|
|
8328
|
+
sessionId,
|
|
8329
|
+
machineId,
|
|
8330
|
+
leaseMs
|
|
8331
|
+
});
|
|
8332
|
+
return textToolResult(JSON.stringify({ workspaceProjectId: projectId, ...res }, null, 2), false);
|
|
8333
|
+
})
|
|
8171
8334
|
);
|
|
8172
8335
|
mcp.registerTool(
|
|
8173
8336
|
"docs_release",
|
|
@@ -8178,27 +8341,18 @@ ${String(st.stdout || "").trim()}`
|
|
|
8178
8341
|
nodeId: z.string().describe("Document node id.")
|
|
8179
8342
|
}
|
|
8180
8343
|
},
|
|
8181
|
-
async (args) => {
|
|
8182
|
-
const
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
const result = textToolResult(JSON.stringify({ workspaceProjectId: projectId, ...res }, null, 2), false);
|
|
8194
|
-
emitFlockbayMcpToolResult(callId, result, false);
|
|
8195
|
-
return result;
|
|
8196
|
-
} catch (err) {
|
|
8197
|
-
const result = textToolResult(`Failed to release doc: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
8198
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
8199
|
-
return result;
|
|
8200
|
-
}
|
|
8201
|
-
}
|
|
8344
|
+
async (args) => runWithMcpToolCard("docs_release", args, async () => {
|
|
8345
|
+
const { projectId, workItemId } = readCoordinationIds();
|
|
8346
|
+
const sessionId = String(client?.sessionId || "").trim();
|
|
8347
|
+
const nodeId = String(args?.nodeId || "").trim();
|
|
8348
|
+
const res = await postJson("/v1/workspace/projects/docs/leases/release", {
|
|
8349
|
+
workspaceProjectId: projectId,
|
|
8350
|
+
nodeId,
|
|
8351
|
+
workItemId,
|
|
8352
|
+
sessionId
|
|
8353
|
+
});
|
|
8354
|
+
return textToolResult(JSON.stringify({ workspaceProjectId: projectId, ...res }, null, 2), false);
|
|
8355
|
+
})
|
|
8202
8356
|
);
|
|
8203
8357
|
mcp.registerTool(
|
|
8204
8358
|
"docs_update",
|
|
@@ -8213,56 +8367,47 @@ ${String(st.stdout || "").trim()}`
|
|
|
8213
8367
|
expectedBodyVersion: z.number().int().nonnegative().optional().describe("Optional optimistic concurrency check.")
|
|
8214
8368
|
}
|
|
8215
8369
|
},
|
|
8216
|
-
async (args) => {
|
|
8217
|
-
const
|
|
8370
|
+
async (args) => runWithMcpToolCard("docs_update", args, async () => {
|
|
8371
|
+
const { projectId, workItemId } = readCoordinationIds();
|
|
8372
|
+
const sessionId = String(client?.sessionId || "").trim();
|
|
8373
|
+
const nodeId = String(args?.nodeId || "").trim();
|
|
8374
|
+
const name = args?.name;
|
|
8375
|
+
const bodyMarkdown = args?.bodyMarkdown;
|
|
8376
|
+
const metadata = args?.metadata;
|
|
8377
|
+
const expectedBodyVersion = args?.expectedBodyVersion;
|
|
8378
|
+
const attemptUpdate = async () => {
|
|
8379
|
+
return await postJson("/v1/workspace/projects/docs/update-doc", {
|
|
8380
|
+
workspaceProjectId: projectId,
|
|
8381
|
+
nodeId,
|
|
8382
|
+
workItemId,
|
|
8383
|
+
sessionId,
|
|
8384
|
+
name,
|
|
8385
|
+
bodyMarkdown,
|
|
8386
|
+
metadata,
|
|
8387
|
+
expectedBodyVersion
|
|
8388
|
+
});
|
|
8389
|
+
};
|
|
8390
|
+
let res;
|
|
8218
8391
|
try {
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
const
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
const metadata = args?.metadata;
|
|
8225
|
-
const expectedBodyVersion = args?.expectedBodyVersion;
|
|
8226
|
-
const attemptUpdate = async () => {
|
|
8227
|
-
return await postJson("/v1/workspace/projects/docs/update-doc", {
|
|
8392
|
+
res = await attemptUpdate();
|
|
8393
|
+
} catch (e) {
|
|
8394
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
8395
|
+
if (msg === "lease_required") {
|
|
8396
|
+
await postJson("/v1/workspace/projects/docs/leases/claim", {
|
|
8228
8397
|
workspaceProjectId: projectId,
|
|
8229
8398
|
nodeId,
|
|
8230
8399
|
workItemId,
|
|
8231
8400
|
sessionId,
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
metadata,
|
|
8235
|
-
expectedBodyVersion
|
|
8401
|
+
machineId: String(client?.metadata?.machineId || "").trim() || null,
|
|
8402
|
+
leaseMs: 2 * 6e4
|
|
8236
8403
|
});
|
|
8237
|
-
};
|
|
8238
|
-
let res;
|
|
8239
|
-
try {
|
|
8240
8404
|
res = await attemptUpdate();
|
|
8241
|
-
}
|
|
8242
|
-
|
|
8243
|
-
if (msg === "lease_required") {
|
|
8244
|
-
await postJson("/v1/workspace/projects/docs/leases/claim", {
|
|
8245
|
-
workspaceProjectId: projectId,
|
|
8246
|
-
nodeId,
|
|
8247
|
-
workItemId,
|
|
8248
|
-
sessionId,
|
|
8249
|
-
machineId: String(client?.metadata?.machineId || "").trim() || null,
|
|
8250
|
-
leaseMs: 2 * 6e4
|
|
8251
|
-
});
|
|
8252
|
-
res = await attemptUpdate();
|
|
8253
|
-
} else {
|
|
8254
|
-
throw e;
|
|
8255
|
-
}
|
|
8405
|
+
} else {
|
|
8406
|
+
throw e;
|
|
8256
8407
|
}
|
|
8257
|
-
const result = textToolResult(JSON.stringify({ workspaceProjectId: projectId, ...res }, null, 2), false);
|
|
8258
|
-
emitFlockbayMcpToolResult(callId, result, false);
|
|
8259
|
-
return result;
|
|
8260
|
-
} catch (err) {
|
|
8261
|
-
const result = textToolResult(`Failed to update doc: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
8262
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
8263
|
-
return result;
|
|
8264
8408
|
}
|
|
8265
|
-
|
|
8409
|
+
return textToolResult(JSON.stringify({ workspaceProjectId: projectId, ...res }, null, 2), false);
|
|
8410
|
+
})
|
|
8266
8411
|
);
|
|
8267
8412
|
mcp.registerTool(
|
|
8268
8413
|
"docs_delete",
|
|
@@ -8273,27 +8418,18 @@ ${String(st.stdout || "").trim()}`
|
|
|
8273
8418
|
nodeId: z.string().describe("Node id (folder or document).")
|
|
8274
8419
|
}
|
|
8275
8420
|
},
|
|
8276
|
-
async (args) => {
|
|
8277
|
-
const
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
const result = textToolResult(JSON.stringify({ workspaceProjectId: projectId, ...res }, null, 2), false);
|
|
8289
|
-
emitFlockbayMcpToolResult(callId, result, false);
|
|
8290
|
-
return result;
|
|
8291
|
-
} catch (err) {
|
|
8292
|
-
const result = textToolResult(`Failed to delete node: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
8293
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
8294
|
-
return result;
|
|
8295
|
-
}
|
|
8296
|
-
}
|
|
8421
|
+
async (args) => runWithMcpToolCard("docs_delete", args, async () => {
|
|
8422
|
+
const { projectId, workItemId } = readCoordinationIds();
|
|
8423
|
+
const sessionId = String(client?.sessionId || "").trim();
|
|
8424
|
+
const nodeId = String(args?.nodeId || "").trim();
|
|
8425
|
+
const res = await postJson("/v1/workspace/projects/docs/delete-node", {
|
|
8426
|
+
workspaceProjectId: projectId,
|
|
8427
|
+
nodeId,
|
|
8428
|
+
workItemId,
|
|
8429
|
+
sessionId
|
|
8430
|
+
});
|
|
8431
|
+
return textToolResult(JSON.stringify({ workspaceProjectId: projectId, ...res }, null, 2), false);
|
|
8432
|
+
})
|
|
8297
8433
|
);
|
|
8298
8434
|
mcp.registerTool(
|
|
8299
8435
|
"docs_import",
|
|
@@ -8309,21 +8445,12 @@ ${String(st.stdout || "").trim()}`
|
|
|
8309
8445
|
)
|
|
8310
8446
|
}
|
|
8311
8447
|
},
|
|
8312
|
-
async (args) => {
|
|
8313
|
-
const
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
const result = textToolResult(JSON.stringify({ workspaceProjectId: projectId, ...res }, null, 2), false);
|
|
8319
|
-
emitFlockbayMcpToolResult(callId, result, false);
|
|
8320
|
-
return result;
|
|
8321
|
-
} catch (err) {
|
|
8322
|
-
const result = textToolResult(`Failed to import docs: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
8323
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
8324
|
-
return result;
|
|
8325
|
-
}
|
|
8326
|
-
}
|
|
8448
|
+
async (args) => runWithMcpToolCard("docs_import", args, async () => {
|
|
8449
|
+
const { projectId } = readCoordinationIds();
|
|
8450
|
+
const entries = Array.isArray(args?.entries) ? args.entries : [];
|
|
8451
|
+
const res = await postJson("/v1/workspace/projects/docs/import", { workspaceProjectId: projectId, entries });
|
|
8452
|
+
return textToolResult(JSON.stringify({ workspaceProjectId: projectId, ...res }, null, 2), false);
|
|
8453
|
+
})
|
|
8327
8454
|
);
|
|
8328
8455
|
mcp.registerTool(
|
|
8329
8456
|
"evidence_list",
|
|
@@ -8337,10 +8464,11 @@ ${String(st.stdout || "").trim()}`
|
|
|
8337
8464
|
async (args) => runWithMcpToolCard("evidence_list", args, async () => {
|
|
8338
8465
|
const limit = Math.max(1, Math.min(200, Number(args?.limit || 50) || 50));
|
|
8339
8466
|
const url = `${configuration.serverUrl.replace(/\/+$/, "")}/v1/sessions/${encodeURIComponent(client.sessionId)}/evidence-artifacts?limit=${limit}`;
|
|
8340
|
-
const
|
|
8467
|
+
const timeoutMs = readHttpTimeoutMs("FLOCKBAY_MCP_HTTP_TIMEOUT_MS", 6e4);
|
|
8468
|
+
const res = await fetchWithTimeout(url, {
|
|
8341
8469
|
method: "GET",
|
|
8342
8470
|
headers: { Authorization: `Machine ${client.getAuthToken()}`, accept: "application/json" }
|
|
8343
|
-
});
|
|
8471
|
+
}, timeoutMs, `GET ${url}`);
|
|
8344
8472
|
const data = await res.json().catch(() => null);
|
|
8345
8473
|
if (!res.ok) {
|
|
8346
8474
|
const msg = typeof data?.error === "string" ? data.error : `Request failed (${res.status})`;
|
|
@@ -8362,10 +8490,11 @@ ${String(st.stdout || "").trim()}`
|
|
|
8362
8490
|
const id = String(args?.evidenceArtifactId || "").trim();
|
|
8363
8491
|
if (!id) return textToolResult("missing-evidenceArtifactId", true);
|
|
8364
8492
|
const url = `${configuration.serverUrl.replace(/\/+$/, "")}/v1/sessions/${encodeURIComponent(client.sessionId)}/evidence-artifacts/${encodeURIComponent(id)}`;
|
|
8365
|
-
const
|
|
8493
|
+
const timeoutMs = readHttpTimeoutMs("FLOCKBAY_MCP_HTTP_TIMEOUT_MS", 6e4);
|
|
8494
|
+
const res = await fetchWithTimeout(url, {
|
|
8366
8495
|
method: "GET",
|
|
8367
8496
|
headers: { Authorization: `Machine ${client.getAuthToken()}`, accept: "application/json" }
|
|
8368
|
-
});
|
|
8497
|
+
}, timeoutMs, `GET ${url}`);
|
|
8369
8498
|
const data = await res.json().catch(() => null);
|
|
8370
8499
|
if (!res.ok) {
|
|
8371
8500
|
const msg = typeof data?.error === "string" ? data.error : `Request failed (${res.status})`;
|
|
@@ -8374,6 +8503,72 @@ ${String(st.stdout || "").trim()}`
|
|
|
8374
8503
|
return textToolResult(JSON.stringify(data, null, 2), false);
|
|
8375
8504
|
})
|
|
8376
8505
|
);
|
|
8506
|
+
mcp.registerTool(
|
|
8507
|
+
"tool_result_read",
|
|
8508
|
+
{
|
|
8509
|
+
title: "Tool Result Read",
|
|
8510
|
+
description: "Read a previously-stored MCP tool result artifact produced by Flockbay MCP (full tool input/output JSON). Use this when you explicitly need details beyond the default minimal tool observation.",
|
|
8511
|
+
inputSchema: {
|
|
8512
|
+
toolName: z.string().optional().describe("Tool name (folder under .flockbay/artifacts/tool-results)."),
|
|
8513
|
+
toolResultId: z.string().describe("toolResultId from a prior tool observation."),
|
|
8514
|
+
artifactPath: z.string().optional().describe("Optional absolute artifact path (advanced).")
|
|
8515
|
+
}
|
|
8516
|
+
},
|
|
8517
|
+
async (args) => runWithMcpToolCard("tool_result_read", args, async () => {
|
|
8518
|
+
const toolName = typeof args?.toolName === "string" ? String(args.toolName).trim() : null;
|
|
8519
|
+
const toolResultId = typeof args?.toolResultId === "string" ? String(args.toolResultId).trim() : "";
|
|
8520
|
+
const artifactPath = typeof args?.artifactPath === "string" ? String(args.artifactPath).trim() : null;
|
|
8521
|
+
const resolved = await resolveToolResultArtifactPath({ toolName, toolResultId, artifactPath });
|
|
8522
|
+
if (!resolved.ok) return textToolResult(resolved.error, true);
|
|
8523
|
+
const raw = await readFile(resolved.path, "utf8");
|
|
8524
|
+
return {
|
|
8525
|
+
content: [
|
|
8526
|
+
{ type: "text", text: raw }
|
|
8527
|
+
],
|
|
8528
|
+
isError: false
|
|
8529
|
+
};
|
|
8530
|
+
}, { modelReturn: "full" })
|
|
8531
|
+
);
|
|
8532
|
+
mcp.registerTool(
|
|
8533
|
+
"tool_result_query",
|
|
8534
|
+
{
|
|
8535
|
+
title: "Tool Result Query",
|
|
8536
|
+
description: "Query one or more JSON paths from a stored tool result artifact (small slices). Prefer this over reading the full artifact to avoid re-injecting huge payloads into the model context.",
|
|
8537
|
+
inputSchema: {
|
|
8538
|
+
toolName: z.string().optional().describe("Tool name (folder under .flockbay/artifacts/tool-results)."),
|
|
8539
|
+
toolResultId: z.string().describe("toolResultId from a prior tool observation."),
|
|
8540
|
+
artifactPath: z.string().optional().describe("Optional absolute artifact path (advanced)."),
|
|
8541
|
+
paths: z.array(z.string()).min(1).describe('JSON pointer paths ("/output/result/location") or dot-paths ("output.result.location").')
|
|
8542
|
+
}
|
|
8543
|
+
},
|
|
8544
|
+
async (args) => runWithMcpToolCard("tool_result_query", args, async () => {
|
|
8545
|
+
const toolName = typeof args?.toolName === "string" ? String(args.toolName).trim() : null;
|
|
8546
|
+
const toolResultId = typeof args?.toolResultId === "string" ? String(args.toolResultId).trim() : "";
|
|
8547
|
+
const artifactPath = typeof args?.artifactPath === "string" ? String(args.artifactPath).trim() : null;
|
|
8548
|
+
const paths = Array.isArray(args?.paths) ? args.paths.map((p) => String(p || "").trim()).filter(Boolean) : [];
|
|
8549
|
+
if (!toolResultId) return textToolResult("Missing toolResultId.", true);
|
|
8550
|
+
if (paths.length === 0) return textToolResult("Missing paths[] (provide one or more JSON paths).", true);
|
|
8551
|
+
const resolved = await resolveToolResultArtifactPath({ toolName, toolResultId, artifactPath });
|
|
8552
|
+
if (!resolved.ok) return textToolResult(resolved.error, true);
|
|
8553
|
+
let json;
|
|
8554
|
+
try {
|
|
8555
|
+
json = JSON.parse(await readFile(resolved.path, "utf8"));
|
|
8556
|
+
} catch (err) {
|
|
8557
|
+
return textToolResult(`Failed to parse tool result artifact JSON: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
8558
|
+
}
|
|
8559
|
+
const out = {};
|
|
8560
|
+
for (const p of paths) {
|
|
8561
|
+
const res = getValueAtJsonPointer(json, p);
|
|
8562
|
+
if (!res.ok) return textToolResult(`Invalid path "${p}": ${res.error}`, true);
|
|
8563
|
+
out[p] = res.value;
|
|
8564
|
+
}
|
|
8565
|
+
return {
|
|
8566
|
+
content: [{ type: "text", text: JSON.stringify({ toolResultId, values: out }, null, 2) }],
|
|
8567
|
+
values: out,
|
|
8568
|
+
isError: false
|
|
8569
|
+
};
|
|
8570
|
+
}, { modelReturn: "full" })
|
|
8571
|
+
);
|
|
8377
8572
|
mcp.registerTool(
|
|
8378
8573
|
"coordination_update_intent",
|
|
8379
8574
|
{
|
|
@@ -8389,31 +8584,22 @@ ${String(st.stdout || "").trim()}`
|
|
|
8389
8584
|
leaseMs: z.number().int().positive().optional().describe("Soft lease TTL for this ledger entry (ms). UI-only staleness hint; default ~2h.")
|
|
8390
8585
|
}
|
|
8391
8586
|
},
|
|
8392
|
-
async (args) => {
|
|
8393
|
-
const
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
|
|
8401
|
-
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
const result = textToolResult(JSON.stringify({ success: true, workItem: data?.workItem ?? null }, null, 2), false);
|
|
8409
|
-
emitFlockbayMcpToolResult(callId, result, false);
|
|
8410
|
-
return result;
|
|
8411
|
-
} catch (err) {
|
|
8412
|
-
const result = textToolResult(`Failed to update intent: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
8413
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
8414
|
-
return result;
|
|
8415
|
-
}
|
|
8416
|
-
}
|
|
8587
|
+
async (args) => runWithMcpToolCard("coordination_update_intent", args, async () => {
|
|
8588
|
+
const { projectId, workItemId } = readCoordinationIds();
|
|
8589
|
+
const body = {
|
|
8590
|
+
projectId,
|
|
8591
|
+
workItemId,
|
|
8592
|
+
summary: args?.summary ?? void 0,
|
|
8593
|
+
status: args?.status ?? void 0,
|
|
8594
|
+
plannedFiles: args?.plannedFiles ?? void 0,
|
|
8595
|
+
activeFiles: args?.activeFiles ?? void 0,
|
|
8596
|
+
doneFiles: args?.doneFiles ?? void 0,
|
|
8597
|
+
waitingOnFiles: args?.waitingOnFiles ?? void 0,
|
|
8598
|
+
leaseMs: args?.leaseMs ?? void 0
|
|
8599
|
+
};
|
|
8600
|
+
const data = await postJson("/v1/coordination/work-items/update", body);
|
|
8601
|
+
return textToolResult(JSON.stringify({ success: true, workItem: data?.workItem ?? null }, null, 2), false);
|
|
8602
|
+
})
|
|
8417
8603
|
);
|
|
8418
8604
|
mcp.registerTool(
|
|
8419
8605
|
"ledger_claim",
|
|
@@ -8430,8 +8616,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
8430
8616
|
}
|
|
8431
8617
|
},
|
|
8432
8618
|
async (args) => {
|
|
8433
|
-
|
|
8434
|
-
try {
|
|
8619
|
+
return await runWithMcpToolCard("ledger_claim", args, async () => {
|
|
8435
8620
|
const { projectId, workItemId } = readCoordinationIds();
|
|
8436
8621
|
if (args?.summary !== void 0 || args?.status !== void 0 || args?.plannedFiles !== void 0) {
|
|
8437
8622
|
await postJson("/v1/coordination/work-items/update", {
|
|
@@ -8467,9 +8652,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
8467
8652
|
const data = await postJson("/v1/coordination/work-items/claim-files", { projectId, workItemId, ...payloadArgs });
|
|
8468
8653
|
const ok = Boolean(data?.success);
|
|
8469
8654
|
if (!ok) {
|
|
8470
|
-
|
|
8471
|
-
emitFlockbayMcpToolResult(callId, result2, true);
|
|
8472
|
-
return result2;
|
|
8655
|
+
return textToolResult(JSON.stringify(data ?? { success: false, error: "claim_failed" }, null, 2), true);
|
|
8473
8656
|
}
|
|
8474
8657
|
const claimedFiles = Array.isArray(data?.claimedFiles) ? data.claimedFiles : files;
|
|
8475
8658
|
const claimedSet = new Set(claimedFiles.map((v) => String(v || "").trim()).filter(Boolean));
|
|
@@ -8501,7 +8684,6 @@ ${String(st.stdout || "").trim()}`
|
|
|
8501
8684
|
),
|
|
8502
8685
|
true
|
|
8503
8686
|
);
|
|
8504
|
-
emitFlockbayMcpToolResult(callId, result2, true);
|
|
8505
8687
|
return result2;
|
|
8506
8688
|
}
|
|
8507
8689
|
try {
|
|
@@ -8525,13 +8707,8 @@ ${String(st.stdout || "").trim()}`
|
|
|
8525
8707
|
),
|
|
8526
8708
|
false
|
|
8527
8709
|
);
|
|
8528
|
-
emitFlockbayMcpToolResult(callId, result, false);
|
|
8529
8710
|
return result;
|
|
8530
|
-
}
|
|
8531
|
-
const result = textToolResult(`Failed to claim via ledger: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
8532
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
8533
|
-
return result;
|
|
8534
|
-
}
|
|
8711
|
+
});
|
|
8535
8712
|
}
|
|
8536
8713
|
);
|
|
8537
8714
|
mcp.registerTool(
|
|
@@ -8548,8 +8725,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
8548
8725
|
}
|
|
8549
8726
|
},
|
|
8550
8727
|
async (args) => {
|
|
8551
|
-
|
|
8552
|
-
try {
|
|
8728
|
+
return await runWithMcpToolCard("ledger_release", args, async () => {
|
|
8553
8729
|
const { projectId, workItemId } = readCoordinationIds();
|
|
8554
8730
|
const data = await postJson("/v1/coordination/work-items/list", { projectId });
|
|
8555
8731
|
const items = Array.isArray(data?.workItems) ? data.workItems : [];
|
|
@@ -8598,14 +8774,8 @@ ${String(st.stdout || "").trim()}`
|
|
|
8598
8774
|
} catch (err) {
|
|
8599
8775
|
logger.debug("[flockbayMCP] Failed to revoke local write tokens:", err);
|
|
8600
8776
|
}
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
return result;
|
|
8604
|
-
} catch (err) {
|
|
8605
|
-
const result = textToolResult(`Failed to release ledger entry: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
8606
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
8607
|
-
return result;
|
|
8608
|
-
}
|
|
8777
|
+
return textToolResult(JSON.stringify({ success: true, workItem: updated?.workItem ?? null }, null, 2), false);
|
|
8778
|
+
});
|
|
8609
8779
|
}
|
|
8610
8780
|
);
|
|
8611
8781
|
mcp.registerTool(
|
|
@@ -8619,8 +8789,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
8619
8789
|
}
|
|
8620
8790
|
},
|
|
8621
8791
|
async (args) => {
|
|
8622
|
-
|
|
8623
|
-
try {
|
|
8792
|
+
return await runWithMcpToolCard("coordination_check_files", args, async () => {
|
|
8624
8793
|
const { projectId, workItemId } = readCoordinationIds();
|
|
8625
8794
|
const includeSelf = args?.includeSelf !== false;
|
|
8626
8795
|
const rawFiles = Array.isArray(args?.files) ? args.files : [];
|
|
@@ -8675,14 +8844,8 @@ ${String(st.stdout || "").trim()}`
|
|
|
8675
8844
|
const otherOwners = owners.filter((o) => !o.isSelf);
|
|
8676
8845
|
return { filePath, available: otherOwners.length === 0, owners };
|
|
8677
8846
|
});
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
return result;
|
|
8681
|
-
} catch (err) {
|
|
8682
|
-
const result = textToolResult(`Failed to check files: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
8683
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
8684
|
-
return result;
|
|
8685
|
-
}
|
|
8847
|
+
return textToolResult(JSON.stringify({ projectId, files: out }, null, 2), false);
|
|
8848
|
+
});
|
|
8686
8849
|
}
|
|
8687
8850
|
);
|
|
8688
8851
|
mcp.registerTool(
|
|
@@ -8697,8 +8860,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
8697
8860
|
}
|
|
8698
8861
|
},
|
|
8699
8862
|
async (args) => {
|
|
8700
|
-
|
|
8701
|
-
try {
|
|
8863
|
+
return await runWithMcpToolCard("coordination_claim_files", args, async () => {
|
|
8702
8864
|
const { projectId, workItemId } = readCoordinationIds();
|
|
8703
8865
|
const rawFiles = Array.isArray(args?.files) ? args.files : [];
|
|
8704
8866
|
const files = rawFiles.map((v) => String(v || "").trim().replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/^\/+/, "")).filter((v) => Boolean(v));
|
|
@@ -8726,9 +8888,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
8726
8888
|
};
|
|
8727
8889
|
const data = await postJson("/v1/coordination/work-items/claim-files", body);
|
|
8728
8890
|
if (!data?.success) {
|
|
8729
|
-
|
|
8730
|
-
emitFlockbayMcpToolResult(callId, result2, true);
|
|
8731
|
-
return result2;
|
|
8891
|
+
return textToolResult(JSON.stringify(data ?? { success: false, error: "claim_failed" }, null, 2), true);
|
|
8732
8892
|
}
|
|
8733
8893
|
const claimedFiles = Array.isArray(data?.claimedFiles) ? data.claimedFiles : files;
|
|
8734
8894
|
const claimedSet = new Set(claimedFiles.map((v) => String(v || "").trim()).filter(Boolean));
|
|
@@ -8760,7 +8920,6 @@ ${String(st.stdout || "").trim()}`
|
|
|
8760
8920
|
),
|
|
8761
8921
|
true
|
|
8762
8922
|
);
|
|
8763
|
-
emitFlockbayMcpToolResult(callId, result2, true);
|
|
8764
8923
|
return result2;
|
|
8765
8924
|
}
|
|
8766
8925
|
try {
|
|
@@ -8784,13 +8943,8 @@ ${String(st.stdout || "").trim()}`
|
|
|
8784
8943
|
),
|
|
8785
8944
|
false
|
|
8786
8945
|
);
|
|
8787
|
-
emitFlockbayMcpToolResult(callId, result, false);
|
|
8788
8946
|
return result;
|
|
8789
|
-
}
|
|
8790
|
-
const result = textToolResult(`Failed to claim files: ${err instanceof Error ? err.message : String(err)}`, true);
|
|
8791
|
-
emitFlockbayMcpToolResult(callId, result, true);
|
|
8792
|
-
return result;
|
|
8793
|
-
}
|
|
8947
|
+
});
|
|
8794
8948
|
}
|
|
8795
8949
|
);
|
|
8796
8950
|
mcp.registerTool(
|
|
@@ -8819,7 +8973,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
8819
8973
|
content.push({ type: "image", data: img.base64, mimeType: img.mimeType });
|
|
8820
8974
|
});
|
|
8821
8975
|
return { content, isError: false };
|
|
8822
|
-
})
|
|
8976
|
+
}, { passthroughImagesToModel: true })
|
|
8823
8977
|
);
|
|
8824
8978
|
mcp.registerTool(
|
|
8825
8979
|
"read_images",
|
|
@@ -8938,7 +9092,7 @@ ${String(st.stdout || "").trim()}`
|
|
|
8938
9092
|
isError: true
|
|
8939
9093
|
};
|
|
8940
9094
|
}
|
|
8941
|
-
})
|
|
9095
|
+
}, { passthroughImagesToModel: true })
|
|
8942
9096
|
);
|
|
8943
9097
|
mcp.registerTool("unreal_latest_screenshots", {
|
|
8944
9098
|
title: "Latest Unreal Screenshots (Validation)",
|
|
@@ -9780,10 +9934,10 @@ ${String(st.stdout || "").trim()}`
|
|
|
9780
9934
|
paths: z.array(z.string()).optional().describe('Root content paths to search (default ["/Game"]).'),
|
|
9781
9935
|
includeWarnings: z.boolean().optional().describe("Include warnings in the per-blueprint messages list (default true)."),
|
|
9782
9936
|
limit: z.number().int().positive().optional().describe("Max number of blueprints to compile (default 500, max 10000)."),
|
|
9783
|
-
timeoutMs: z.number().int().positive().optional().describe("Socket timeout in ms (default
|
|
9937
|
+
timeoutMs: z.number().int().positive().optional().describe("Socket timeout in ms (default 30000, max 30000).")
|
|
9784
9938
|
}
|
|
9785
9939
|
}, async (args) => runWithMcpToolCard("unreal_mcp_compile_blueprints_all", args, async () => {
|
|
9786
|
-
const timeoutMs = args.timeoutMs ??
|
|
9940
|
+
const timeoutMs = Math.min(3e4, Math.max(250, Number(args.timeoutMs ?? 3e4) || 3e4));
|
|
9787
9941
|
unrealEditorSupervisor.noteUnrealActivity();
|
|
9788
9942
|
try {
|
|
9789
9943
|
const params = {};
|
|
@@ -9814,10 +9968,10 @@ ${String(st.stdout || "").trim()}`
|
|
|
9814
9968
|
title: "Unreal Save All (UnrealMCP)",
|
|
9815
9969
|
description: "Save all dirty packages (maps + content) in the running Unreal Editor without prompting. Intended to prevent leaving editor changes unsaved. Fails if PIE is running.",
|
|
9816
9970
|
inputSchema: {
|
|
9817
|
-
timeoutMs: z.number().int().positive().optional().describe("Socket timeout in ms (default
|
|
9971
|
+
timeoutMs: z.number().int().positive().optional().describe("Socket timeout in ms (default 30000, max 30000).")
|
|
9818
9972
|
}
|
|
9819
9973
|
}, async (args) => runWithMcpToolCard("unreal_mcp_save_all", args, async () => {
|
|
9820
|
-
const timeoutMs = args?.timeoutMs ??
|
|
9974
|
+
const timeoutMs = Math.min(3e4, Math.max(250, Number(args?.timeoutMs ?? 3e4) || 3e4));
|
|
9821
9975
|
unrealEditorSupervisor.noteUnrealActivity();
|
|
9822
9976
|
try {
|
|
9823
9977
|
const response = await sendUnrealMcpTcpCommand({ type: "save_all", params: {}, timeoutMs });
|
|
@@ -10592,6 +10746,8 @@ Fix: ${res.hint}` : "";
|
|
|
10592
10746
|
"docs_import",
|
|
10593
10747
|
"evidence_list",
|
|
10594
10748
|
"evidence_get",
|
|
10749
|
+
"tool_result_read",
|
|
10750
|
+
"tool_result_query",
|
|
10595
10751
|
"coordination_update_intent",
|
|
10596
10752
|
"coordination_check_files",
|
|
10597
10753
|
"coordination_claim_files",
|
|
@@ -11913,6 +12069,410 @@ async function handleConnectVendor(vendor, displayName, flags) {
|
|
|
11913
12069
|
}
|
|
11914
12070
|
}
|
|
11915
12071
|
|
|
12072
|
+
function readFlag(args, flag) {
|
|
12073
|
+
return args.includes(flag);
|
|
12074
|
+
}
|
|
12075
|
+
function readArgValue$2(args, flag) {
|
|
12076
|
+
const idx = args.indexOf(flag);
|
|
12077
|
+
if (idx < 0) return null;
|
|
12078
|
+
const v = args[idx + 1];
|
|
12079
|
+
return v ? String(v) : null;
|
|
12080
|
+
}
|
|
12081
|
+
function parseAgentList(args) {
|
|
12082
|
+
const rawAgent = readArgValue$2(args, "--agent") || readArgValue$2(args, "-a");
|
|
12083
|
+
const rawAgents = readArgValue$2(args, "--agents");
|
|
12084
|
+
const wantsAll = readFlag(args, "--all");
|
|
12085
|
+
const split = (raw) => raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
12086
|
+
const desired = wantsAll ? ["codex", "claude", "gemini"] : rawAgents ? split(rawAgents) : rawAgent ? [rawAgent.trim().toLowerCase()] : ["codex", "claude", "gemini"];
|
|
12087
|
+
const allow = /* @__PURE__ */ new Set(["codex", "claude", "gemini"]);
|
|
12088
|
+
const agents = desired.filter((a) => allow.has(a));
|
|
12089
|
+
return agents.length > 0 ? agents : ["codex", "claude", "gemini"];
|
|
12090
|
+
}
|
|
12091
|
+
function permissionModeForAgent(agent) {
|
|
12092
|
+
if (agent === "claude") return "bypassPermissions";
|
|
12093
|
+
return "yolo";
|
|
12094
|
+
}
|
|
12095
|
+
function nowIsoCompact() {
|
|
12096
|
+
const d = /* @__PURE__ */ new Date();
|
|
12097
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
12098
|
+
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
12099
|
+
}
|
|
12100
|
+
async function ensureDaemonRunning({ skipUnreal }) {
|
|
12101
|
+
const ok = await checkIfDaemonRunningAndCleanupStaleState();
|
|
12102
|
+
if (ok) {
|
|
12103
|
+
const same = await isDaemonRunningCurrentCliVersion();
|
|
12104
|
+
if (same) return;
|
|
12105
|
+
logger.debug("[smoke-test] daemon running old CLI version; restarting");
|
|
12106
|
+
await stopDaemon();
|
|
12107
|
+
}
|
|
12108
|
+
logger.debug("[smoke-test] daemon not running; starting via `flockbay start`");
|
|
12109
|
+
const args = ["start"];
|
|
12110
|
+
if (skipUnreal) args.push("--skip-unreal");
|
|
12111
|
+
const child = spawnFlockbayCLI(args, {
|
|
12112
|
+
stdio: "ignore",
|
|
12113
|
+
env: {
|
|
12114
|
+
...process.env,
|
|
12115
|
+
// Do not pop a browser while smoke-testing.
|
|
12116
|
+
FLOCKBAY_NO_OPEN: "1"
|
|
12117
|
+
},
|
|
12118
|
+
windowsHide: true
|
|
12119
|
+
});
|
|
12120
|
+
child.unref();
|
|
12121
|
+
const deadline = Date.now() + 2e4;
|
|
12122
|
+
while (Date.now() < deadline) {
|
|
12123
|
+
const running = await checkIfDaemonRunningAndCleanupStaleState();
|
|
12124
|
+
if (running) return;
|
|
12125
|
+
await delay(250);
|
|
12126
|
+
}
|
|
12127
|
+
throw new Error("daemon_start_timeout");
|
|
12128
|
+
}
|
|
12129
|
+
async function createSmokeWorkspaceDir(baseDir) {
|
|
12130
|
+
const root = baseDir?.trim() ? path.resolve(baseDir) : path.join(os.tmpdir(), "flockbay-smoke", nowIsoCompact() + "_" + randomUUID().slice(0, 8));
|
|
12131
|
+
await fs$1.mkdir(root, { recursive: true });
|
|
12132
|
+
const secret = `SMOKE_SECRET_${randomUUID().slice(0, 8)}`;
|
|
12133
|
+
await fs$1.writeFile(path.join(root, "smoke.txt"), `flockbay smoke test
|
|
12134
|
+
SMOKE_SECRET=${secret}
|
|
12135
|
+
`, "utf8");
|
|
12136
|
+
return { dir: root, secret };
|
|
12137
|
+
}
|
|
12138
|
+
function isAgentRecord(record) {
|
|
12139
|
+
return record && typeof record === "object" && record.role === "agent";
|
|
12140
|
+
}
|
|
12141
|
+
function extractTextFromRecord(record) {
|
|
12142
|
+
try {
|
|
12143
|
+
if (!record || typeof record !== "object") return "";
|
|
12144
|
+
const content = record.content;
|
|
12145
|
+
if (!content || typeof content !== "object") return "";
|
|
12146
|
+
if (content.type === "text" && typeof content.text === "string") return content.text;
|
|
12147
|
+
if (content.type === "tool" && Array.isArray(content.tools)) return JSON.stringify(content.tools);
|
|
12148
|
+
if (content.type === "output") {
|
|
12149
|
+
const msg = content?.data?.message;
|
|
12150
|
+
const items = Array.isArray(msg?.content) ? msg.content : [];
|
|
12151
|
+
const texts = [];
|
|
12152
|
+
for (const item of items) {
|
|
12153
|
+
if (item?.type === "text" && typeof item.text === "string") texts.push(item.text);
|
|
12154
|
+
}
|
|
12155
|
+
return texts.join("\n");
|
|
12156
|
+
}
|
|
12157
|
+
return "";
|
|
12158
|
+
} catch {
|
|
12159
|
+
return "";
|
|
12160
|
+
}
|
|
12161
|
+
}
|
|
12162
|
+
function recordIncludesNeedle(record, needle) {
|
|
12163
|
+
const n = String(needle || "").trim();
|
|
12164
|
+
if (!n) return true;
|
|
12165
|
+
const text = extractTextFromRecord(record);
|
|
12166
|
+
if (text && text.includes(n)) return true;
|
|
12167
|
+
try {
|
|
12168
|
+
return JSON.stringify(record).includes(n);
|
|
12169
|
+
} catch {
|
|
12170
|
+
return false;
|
|
12171
|
+
}
|
|
12172
|
+
}
|
|
12173
|
+
async function waitForMessage(opts) {
|
|
12174
|
+
const { sessionClient, predicate, timeoutMs } = opts;
|
|
12175
|
+
const deadline = Date.now() + timeoutMs;
|
|
12176
|
+
const existing = await sessionClient.listMessages().catch(() => []);
|
|
12177
|
+
for (const msg of existing) {
|
|
12178
|
+
const record = msg?.content;
|
|
12179
|
+
if (predicate(record)) return msg;
|
|
12180
|
+
}
|
|
12181
|
+
return await new Promise((resolve, reject) => {
|
|
12182
|
+
const onUpdate = (u) => {
|
|
12183
|
+
if (u?.body?.t !== "new-message") return;
|
|
12184
|
+
const record = u?.body?.message?.content;
|
|
12185
|
+
if (!record) return;
|
|
12186
|
+
if (!predicate(record)) return;
|
|
12187
|
+
cleanup();
|
|
12188
|
+
resolve(u?.body?.message);
|
|
12189
|
+
};
|
|
12190
|
+
const tick = () => {
|
|
12191
|
+
if (Date.now() < deadline) return;
|
|
12192
|
+
cleanup();
|
|
12193
|
+
reject(new Error("timeout_waiting_for_message"));
|
|
12194
|
+
};
|
|
12195
|
+
const interval = setInterval(tick, 200);
|
|
12196
|
+
const cleanup = () => {
|
|
12197
|
+
clearInterval(interval);
|
|
12198
|
+
sessionClient.off("update", onUpdate);
|
|
12199
|
+
};
|
|
12200
|
+
sessionClient.on("update", onUpdate);
|
|
12201
|
+
});
|
|
12202
|
+
}
|
|
12203
|
+
async function runScenario(name, fn) {
|
|
12204
|
+
const startedAtMs = Date.now();
|
|
12205
|
+
try {
|
|
12206
|
+
await fn();
|
|
12207
|
+
return { name, ok: true, startedAtMs, finishedAtMs: Date.now() };
|
|
12208
|
+
} catch (err) {
|
|
12209
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12210
|
+
return { name, ok: false, error: msg, startedAtMs, finishedAtMs: Date.now() };
|
|
12211
|
+
}
|
|
12212
|
+
}
|
|
12213
|
+
function tinyPngBase64() {
|
|
12214
|
+
const width = 64;
|
|
12215
|
+
const height = 64;
|
|
12216
|
+
const pngSignature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
12217
|
+
const crcTable = (() => {
|
|
12218
|
+
const table = new Uint32Array(256);
|
|
12219
|
+
for (let i = 0; i < 256; i += 1) {
|
|
12220
|
+
let c = i;
|
|
12221
|
+
for (let k = 0; k < 8; k += 1) {
|
|
12222
|
+
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
12223
|
+
}
|
|
12224
|
+
table[i] = c >>> 0;
|
|
12225
|
+
}
|
|
12226
|
+
return table;
|
|
12227
|
+
})();
|
|
12228
|
+
const crc32 = (buf) => {
|
|
12229
|
+
let c = 4294967295;
|
|
12230
|
+
for (let i = 0; i < buf.length; i += 1) {
|
|
12231
|
+
c = crcTable[(c ^ buf[i]) & 255] ^ c >>> 8;
|
|
12232
|
+
}
|
|
12233
|
+
return (c ^ 4294967295) >>> 0;
|
|
12234
|
+
};
|
|
12235
|
+
const chunk = (type, data) => {
|
|
12236
|
+
const typeBuf = Buffer.from(type, "ascii");
|
|
12237
|
+
const lenBuf = Buffer.alloc(4);
|
|
12238
|
+
lenBuf.writeUInt32BE(data.length, 0);
|
|
12239
|
+
const crcBuf = Buffer.alloc(4);
|
|
12240
|
+
const crc = crc32(Buffer.concat([typeBuf, data]));
|
|
12241
|
+
crcBuf.writeUInt32BE(crc, 0);
|
|
12242
|
+
return Buffer.concat([lenBuf, typeBuf, data, crcBuf]);
|
|
12243
|
+
};
|
|
12244
|
+
const ihdr = Buffer.alloc(13);
|
|
12245
|
+
ihdr.writeUInt32BE(width, 0);
|
|
12246
|
+
ihdr.writeUInt32BE(height, 4);
|
|
12247
|
+
ihdr[8] = 8;
|
|
12248
|
+
ihdr[9] = 6;
|
|
12249
|
+
ihdr[10] = 0;
|
|
12250
|
+
ihdr[11] = 0;
|
|
12251
|
+
ihdr[12] = 0;
|
|
12252
|
+
const bytesPerPixel = 4;
|
|
12253
|
+
const rowBytes = 1 + width * bytesPerPixel;
|
|
12254
|
+
const raw = Buffer.alloc(rowBytes * height);
|
|
12255
|
+
for (let y = 0; y < height; y += 1) {
|
|
12256
|
+
const rowStart = y * rowBytes;
|
|
12257
|
+
raw[rowStart] = 0;
|
|
12258
|
+
for (let x = 0; x < width; x += 1) {
|
|
12259
|
+
const p = rowStart + 1 + x * bytesPerPixel;
|
|
12260
|
+
raw[p + 0] = 45;
|
|
12261
|
+
raw[p + 1] = 125;
|
|
12262
|
+
raw[p + 2] = 255;
|
|
12263
|
+
raw[p + 3] = 255;
|
|
12264
|
+
}
|
|
12265
|
+
}
|
|
12266
|
+
const compressed = deflateSync(raw, { level: 6 });
|
|
12267
|
+
const png = Buffer.concat([
|
|
12268
|
+
pngSignature,
|
|
12269
|
+
chunk("IHDR", ihdr),
|
|
12270
|
+
chunk("IDAT", compressed),
|
|
12271
|
+
chunk("IEND", Buffer.alloc(0))
|
|
12272
|
+
]);
|
|
12273
|
+
return png.toString("base64");
|
|
12274
|
+
}
|
|
12275
|
+
async function safeDeleteDir(dir) {
|
|
12276
|
+
try {
|
|
12277
|
+
await fs$1.rm(dir, { recursive: true, force: true });
|
|
12278
|
+
} catch {
|
|
12279
|
+
}
|
|
12280
|
+
}
|
|
12281
|
+
async function deleteSession(api, sessionId) {
|
|
12282
|
+
try {
|
|
12283
|
+
await api.deleteSessionById(sessionId);
|
|
12284
|
+
} catch {
|
|
12285
|
+
}
|
|
12286
|
+
}
|
|
12287
|
+
async function ensureSessionActive(api, sessionId, timeoutMs) {
|
|
12288
|
+
const deadline = Date.now() + timeoutMs;
|
|
12289
|
+
while (Date.now() < deadline) {
|
|
12290
|
+
const s = await api.getSessionById(sessionId).catch(() => null);
|
|
12291
|
+
if (s?.active) return;
|
|
12292
|
+
await delay(350);
|
|
12293
|
+
}
|
|
12294
|
+
throw new Error("session_not_active");
|
|
12295
|
+
}
|
|
12296
|
+
async function runSmokeForAgent(agent, args) {
|
|
12297
|
+
const startedAtMs = Date.now();
|
|
12298
|
+
const scenarios = [];
|
|
12299
|
+
const keep = readFlag(args, "--keep");
|
|
12300
|
+
const skipUnreal = !readFlag(args, "--with-unreal");
|
|
12301
|
+
const baseDir = readArgValue$2(args, "--directory") || readArgValue$2(args, "--dir");
|
|
12302
|
+
const timeoutMsRaw = readArgValue$2(args, "--timeout-ms");
|
|
12303
|
+
const timeoutMs = timeoutMsRaw && Number.isFinite(Number(timeoutMsRaw)) ? Number(timeoutMsRaw) : 9e4;
|
|
12304
|
+
let sessionId;
|
|
12305
|
+
let directory;
|
|
12306
|
+
let sessionClient = null;
|
|
12307
|
+
let api = null;
|
|
12308
|
+
try {
|
|
12309
|
+
await ensureDaemonRunning({ skipUnreal });
|
|
12310
|
+
const { auth, machineId } = await ensureMachineAuthOrLogin();
|
|
12311
|
+
api = await ApiClient.create(auth);
|
|
12312
|
+
const { dir, secret } = await createSmokeWorkspaceDir(baseDir);
|
|
12313
|
+
directory = dir;
|
|
12314
|
+
const sessionMeta = {
|
|
12315
|
+
path: dir,
|
|
12316
|
+
machineId,
|
|
12317
|
+
flavor: agent,
|
|
12318
|
+
createdBy: "smoke-test",
|
|
12319
|
+
tag: `smoke_${agent}_${nowIsoCompact()}`
|
|
12320
|
+
};
|
|
12321
|
+
const session = await api.createSession({ metadata: sessionMeta, state: null });
|
|
12322
|
+
sessionId = session.id;
|
|
12323
|
+
if (!sessionId) throw new Error("create_session_failed");
|
|
12324
|
+
const spawnResult = await spawnDaemonSession(dir, sessionId, agent);
|
|
12325
|
+
if (!spawnResult?.success) {
|
|
12326
|
+
const msg = String(spawnResult?.error || "spawn_failed");
|
|
12327
|
+
throw new Error(msg);
|
|
12328
|
+
}
|
|
12329
|
+
sessionClient = api.sessionSyncClient(session);
|
|
12330
|
+
await sessionClient.connectAndWait(15e3);
|
|
12331
|
+
await ensureSessionActive(api, sessionId, 25e3);
|
|
12332
|
+
const permissionMode = permissionModeForAgent(agent);
|
|
12333
|
+
scenarios.push(await runScenario("basic-message", async () => {
|
|
12334
|
+
const token = `SMOKE_OK_${agent}_${randomUUID().slice(0, 6)}`;
|
|
12335
|
+
sessionClient.sendUserText(`Reply with exactly: ${token}`, { permissionMode });
|
|
12336
|
+
await waitForMessage({
|
|
12337
|
+
sessionClient,
|
|
12338
|
+
predicate: (r) => isAgentRecord(r) && recordIncludesNeedle(r, token),
|
|
12339
|
+
timeoutMs
|
|
12340
|
+
});
|
|
12341
|
+
}));
|
|
12342
|
+
scenarios.push(await runScenario("tool-search", async () => {
|
|
12343
|
+
const token = `TOOL_SEARCH_OK_${randomUUID().slice(0, 6)}`;
|
|
12344
|
+
sessionClient.sendUserText(
|
|
12345
|
+
[
|
|
12346
|
+
"Read the file `smoke.txt` in the project directory using MCP tools (e.g. `read_file_chunk` or `search`).",
|
|
12347
|
+
"Find the value after `SMOKE_SECRET=`.",
|
|
12348
|
+
"",
|
|
12349
|
+
`Reply with two lines exactly:`,
|
|
12350
|
+
`1) SMOKE_SECRET=<value>`,
|
|
12351
|
+
`2) ${token}`
|
|
12352
|
+
].join("\n"),
|
|
12353
|
+
{ permissionMode }
|
|
12354
|
+
);
|
|
12355
|
+
await waitForMessage({
|
|
12356
|
+
sessionClient,
|
|
12357
|
+
predicate: (r) => isAgentRecord(r) && recordIncludesNeedle(r, `SMOKE_SECRET=${secret}`) && recordIncludesNeedle(r, token),
|
|
12358
|
+
timeoutMs
|
|
12359
|
+
});
|
|
12360
|
+
}));
|
|
12361
|
+
scenarios.push(await runScenario("image-attachment", async () => {
|
|
12362
|
+
const token = `IMAGE_OK_${randomUUID().slice(0, 6)}`;
|
|
12363
|
+
const base64 = tinyPngBase64();
|
|
12364
|
+
sessionClient.sendUserText(
|
|
12365
|
+
`Describe the attached image in one short sentence, then reply with exactly: ${token}`,
|
|
12366
|
+
{
|
|
12367
|
+
permissionMode,
|
|
12368
|
+
attachments: {
|
|
12369
|
+
images: [{ mimeType: "image/png", base64, name: "smoke.png" }]
|
|
12370
|
+
}
|
|
12371
|
+
}
|
|
12372
|
+
);
|
|
12373
|
+
const msg = await waitForMessage({
|
|
12374
|
+
sessionClient,
|
|
12375
|
+
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")),
|
|
12376
|
+
timeoutMs
|
|
12377
|
+
});
|
|
12378
|
+
const record = msg?.content;
|
|
12379
|
+
if (agent === "codex" && recordIncludesNeedle(record, "image-attachment-missing")) {
|
|
12380
|
+
throw new Error("image-attachment-missing (Codex did not receive images; check latest_user_images tool + vision support)");
|
|
12381
|
+
}
|
|
12382
|
+
if (recordIncludesNeedle(record, "Could not process image") || recordIncludesNeedle(record, "invalid_request_error")) {
|
|
12383
|
+
throw new Error("provider_image_invalid (image rejected by provider; check base64/mimeType pipeline)");
|
|
12384
|
+
}
|
|
12385
|
+
}));
|
|
12386
|
+
const ok = scenarios.every((s) => s.ok);
|
|
12387
|
+
return {
|
|
12388
|
+
agent,
|
|
12389
|
+
ok,
|
|
12390
|
+
sessionId,
|
|
12391
|
+
directory,
|
|
12392
|
+
scenarios,
|
|
12393
|
+
startedAtMs,
|
|
12394
|
+
finishedAtMs: Date.now()
|
|
12395
|
+
};
|
|
12396
|
+
} finally {
|
|
12397
|
+
if (sessionClient) {
|
|
12398
|
+
try {
|
|
12399
|
+
sessionClient.removeAllListeners();
|
|
12400
|
+
} catch {
|
|
12401
|
+
}
|
|
12402
|
+
try {
|
|
12403
|
+
sessionClient.close();
|
|
12404
|
+
} catch {
|
|
12405
|
+
}
|
|
12406
|
+
}
|
|
12407
|
+
if (sessionId) {
|
|
12408
|
+
if (!keep) {
|
|
12409
|
+
try {
|
|
12410
|
+
await stopDaemonSession(sessionId);
|
|
12411
|
+
} catch {
|
|
12412
|
+
}
|
|
12413
|
+
}
|
|
12414
|
+
}
|
|
12415
|
+
if (directory && !keep) {
|
|
12416
|
+
await safeDeleteDir(directory);
|
|
12417
|
+
}
|
|
12418
|
+
if (sessionId && api && !keep) {
|
|
12419
|
+
await deleteSession(api, sessionId);
|
|
12420
|
+
}
|
|
12421
|
+
}
|
|
12422
|
+
}
|
|
12423
|
+
function showHelp() {
|
|
12424
|
+
console.log(`
|
|
12425
|
+
${chalk.bold("flockbay smoke-test")} - end-to-end smoke tests for daemon + session runtime + MCP tools
|
|
12426
|
+
|
|
12427
|
+
Usage:
|
|
12428
|
+
flockbay smoke-test [--all] [--agent codex|claude|gemini] [--agents codex,claude] [--directory <path>]
|
|
12429
|
+
|
|
12430
|
+
Options:
|
|
12431
|
+
--all Run all agents (default)
|
|
12432
|
+
--agent, -a Run a single agent
|
|
12433
|
+
--agents Comma-separated list of agents
|
|
12434
|
+
--directory, --dir Directory to run the session in (defaults to a temp folder)
|
|
12435
|
+
--timeout-ms Per-scenario timeout (default 90000)
|
|
12436
|
+
--keep Do not stop session or delete temp dir
|
|
12437
|
+
--json Write results JSON to a file
|
|
12438
|
+
--with-unreal Allow Unreal bridge prompts (off by default)
|
|
12439
|
+
`);
|
|
12440
|
+
}
|
|
12441
|
+
async function handleSmokeTestCommand(args) {
|
|
12442
|
+
if (args.includes("--help") || args.includes("-h") || args.includes("help")) {
|
|
12443
|
+
showHelp();
|
|
12444
|
+
return;
|
|
12445
|
+
}
|
|
12446
|
+
const agents = parseAgentList(args);
|
|
12447
|
+
const outPath = readArgValue$2(args, "--json");
|
|
12448
|
+
console.log(chalk.bold("\nFlockbay smoke test\n"));
|
|
12449
|
+
console.log(chalk.gray(`Server: ${configuration.serverUrl}`));
|
|
12450
|
+
console.log(chalk.gray(`Profile: ${configuration.profile}`));
|
|
12451
|
+
console.log(chalk.gray(`Agents: ${agents.join(", ")}`));
|
|
12452
|
+
console.log("");
|
|
12453
|
+
const results = [];
|
|
12454
|
+
for (const agent of agents) {
|
|
12455
|
+
console.log(chalk.bold(`== ${agent} ==`));
|
|
12456
|
+
const r = await runSmokeForAgent(agent, args);
|
|
12457
|
+
results.push(r);
|
|
12458
|
+
for (const s of r.scenarios) {
|
|
12459
|
+
if (s.ok) console.log(chalk.green(` \u2713 ${s.name}`));
|
|
12460
|
+
else console.log(chalk.red(` \u2717 ${s.name}: ${s.error || "failed"}`));
|
|
12461
|
+
}
|
|
12462
|
+
console.log(r.ok ? chalk.green(" PASS\n") : chalk.red(" FAIL\n"));
|
|
12463
|
+
}
|
|
12464
|
+
const allOk = results.every((r) => r.ok);
|
|
12465
|
+
if (outPath) {
|
|
12466
|
+
const abs = path.resolve(outPath);
|
|
12467
|
+
await fs$1.mkdir(path.dirname(abs), { recursive: true });
|
|
12468
|
+
await fs$1.writeFile(abs, JSON.stringify({ ok: allOk, results }, null, 2) + "\n", "utf8");
|
|
12469
|
+
console.log(chalk.gray(`Wrote: ${abs}`));
|
|
12470
|
+
}
|
|
12471
|
+
if (!allOk) {
|
|
12472
|
+
process.exitCode = 1;
|
|
12473
|
+
}
|
|
12474
|
+
}
|
|
12475
|
+
|
|
11916
12476
|
function readArgValue$1(args, key) {
|
|
11917
12477
|
const idx = args.indexOf(key);
|
|
11918
12478
|
if (idx === -1) return null;
|
|
@@ -12636,6 +13196,16 @@ async function authAndSetupMachineIfNeeded() {
|
|
|
12636
13196
|
process.exit(1);
|
|
12637
13197
|
}
|
|
12638
13198
|
return;
|
|
13199
|
+
} else if (subcommand === "smoke-test") {
|
|
13200
|
+
try {
|
|
13201
|
+
await ensureProdServerWhenLocalDevUnreachable();
|
|
13202
|
+
await handleSmokeTestCommand(args.slice(1));
|
|
13203
|
+
} catch (error) {
|
|
13204
|
+
console.error(chalk.red("Smoke test failed:"), error instanceof Error ? error.message : "Unknown error");
|
|
13205
|
+
if (process.env.DEBUG) console.error(error);
|
|
13206
|
+
process.exit(1);
|
|
13207
|
+
}
|
|
13208
|
+
return;
|
|
12639
13209
|
} else if (subcommand === "start") {
|
|
12640
13210
|
const startArgs = args.slice(1);
|
|
12641
13211
|
if (startArgs.includes("--help") || startArgs.includes("-h") || startArgs.includes("help")) {
|
|
@@ -12678,7 +13248,7 @@ ${engineRoot}`, {
|
|
|
12678
13248
|
} else if (subcommand === "codex") {
|
|
12679
13249
|
try {
|
|
12680
13250
|
await chdirToNearestUprojectRootIfPresent();
|
|
12681
|
-
const { runCodex } = await import('./runCodex-
|
|
13251
|
+
const { runCodex } = await import('./runCodex-B0JRo8YP.mjs');
|
|
12682
13252
|
let startedBy = void 0;
|
|
12683
13253
|
let sessionId = void 0;
|
|
12684
13254
|
for (let i = 1; i < args.length; i++) {
|
|
@@ -12780,7 +13350,7 @@ ${engineRoot}`, {
|
|
|
12780
13350
|
}
|
|
12781
13351
|
try {
|
|
12782
13352
|
await chdirToNearestUprojectRootIfPresent();
|
|
12783
|
-
const { runGemini } = await import('./runGemini-
|
|
13353
|
+
const { runGemini } = await import('./runGemini-DO9xzjyY.mjs');
|
|
12784
13354
|
let startedBy = void 0;
|
|
12785
13355
|
let sessionId = void 0;
|
|
12786
13356
|
for (let i = 1; i < args.length; i++) {
|