gdharness 0.5.7 → 0.5.9

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/build/index.js CHANGED
@@ -10126,9 +10126,9 @@ function defectReport(where, error, godotVersion) {
10126
10126
 
10127
10127
  // src/server.ts
10128
10128
  import { execFile as execFile3, spawn } from "node:child_process";
10129
- import { existsSync as existsSync6, mkdtempSync as mkdtempSync2, readdirSync as readdirSync5, readFileSync as readFileSync7, realpathSync as realpathSync2, rmSync as rmSync2 } from "node:fs";
10129
+ import { existsSync as existsSync6, mkdtempSync as mkdtempSync2, readdirSync as readdirSync5, readFileSync as readFileSync7, realpathSync as realpathSync2, rmSync as rmSync3 } from "node:fs";
10130
10130
  import { tmpdir as tmpdir4 } from "node:os";
10131
- import { basename, dirname as dirname2, join as join7, normalize as normalize2 } from "node:path";
10131
+ import { basename, dirname as dirname3, join as join8, normalize as normalize2 } from "node:path";
10132
10132
  import { setTimeout as delay2 } from "node:timers/promises";
10133
10133
  import { fileURLToPath as fileURLToPath2 } from "node:url";
10134
10134
  import { promisify as promisify3 } from "node:util";
@@ -24696,9 +24696,47 @@ class StdioServerTransport {
24696
24696
  }
24697
24697
  }
24698
24698
 
24699
+ // src/bridge-announce.ts
24700
+ import { mkdirSync as mkdirSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
24701
+ import { dirname, join as join2 } from "node:path";
24702
+ function announcementPath(projectPath) {
24703
+ return join2(projectPath, ".godot", "gdharness-bridge.json");
24704
+ }
24705
+ var BRIDGE_ANNOUNCE_PROTOCOL = 1;
24706
+ function announceBridge(projectPath, bridge) {
24707
+ const path = announcementPath(projectPath);
24708
+ try {
24709
+ mkdirSync2(dirname(path), { recursive: true });
24710
+ const announcement = {
24711
+ protocol: BRIDGE_ANNOUNCE_PROTOCOL,
24712
+ host: bridge.host,
24713
+ port: bridge.port,
24714
+ pid: process.pid,
24715
+ version: bridge.version,
24716
+ startedAt: new Date().toISOString()
24717
+ };
24718
+ writeFileSync2(path, `${JSON.stringify(announcement, null, 2)}
24719
+ `, "utf8");
24720
+ return path;
24721
+ } catch (error) {
24722
+ console.error(`[SERVER] Could not announce the editor bridge at ${path}: ${errorMessage(error)}`);
24723
+ return null;
24724
+ }
24725
+ }
24726
+ function withdrawBridge(path) {
24727
+ if (path === null) {
24728
+ return;
24729
+ }
24730
+ try {
24731
+ rmSync(path, { force: true });
24732
+ } catch (error) {
24733
+ console.error(`[SERVER] Could not withdraw the editor bridge announcement: ${errorMessage(error)}`);
24734
+ }
24735
+ }
24736
+
24699
24737
  // src/class-cache.ts
24700
24738
  import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3 } from "node:fs";
24701
- import { join as join2 } from "node:path";
24739
+ import { join as join3 } from "node:path";
24702
24740
  function declaredClasses(projectPath) {
24703
24741
  const declared = new Map;
24704
24742
  const visit = (directory, prefix) => {
@@ -24706,7 +24744,7 @@ function declaredClasses(projectPath) {
24706
24744
  if (entry.name.startsWith(".")) {
24707
24745
  continue;
24708
24746
  }
24709
- const path = join2(directory, entry.name);
24747
+ const path = join3(directory, entry.name);
24710
24748
  if (entry.isDirectory()) {
24711
24749
  visit(path, `${prefix}${entry.name}/`);
24712
24750
  } else if (entry.isFile() && entry.name.endsWith(".gd")) {
@@ -24721,7 +24759,7 @@ function declaredClasses(projectPath) {
24721
24759
  return declared;
24722
24760
  }
24723
24761
  function cachedClasses(projectPath) {
24724
- const cache = join2(projectPath, ".godot", "global_script_class_cache.cfg");
24762
+ const cache = join3(projectPath, ".godot", "global_script_class_cache.cfg");
24725
24763
  if (!existsSync2(cache)) {
24726
24764
  return null;
24727
24765
  }
@@ -25426,19 +25464,37 @@ class GodotBridge extends EventEmitter {
25426
25464
  connectionInfo = null;
25427
25465
  pendingRequests = new Map;
25428
25466
  resourceQueues = new Map;
25429
- port;
25467
+ wantedPort;
25468
+ boundPort = null;
25430
25469
  host;
25431
25470
  timeoutMs;
25432
- constructor(port = DEFAULT_PORT, host = DEFAULT_HOST, timeoutMs = DEFAULT_TIMEOUT_MS) {
25471
+ mayMove;
25472
+ constructor(port = DEFAULT_PORT, host = DEFAULT_HOST, timeoutMs = DEFAULT_TIMEOUT_MS, mayMove = false) {
25433
25473
  super();
25434
- this.port = port;
25474
+ this.wantedPort = port;
25435
25475
  this.host = host;
25436
25476
  this.timeoutMs = timeoutMs;
25477
+ this.mayMove = mayMove;
25437
25478
  }
25438
- start() {
25479
+ get port() {
25480
+ return this.boundPort ?? this.wantedPort;
25481
+ }
25482
+ async start() {
25439
25483
  if (this.httpServer) {
25440
- return Promise.resolve();
25484
+ return;
25485
+ }
25486
+ try {
25487
+ await this.listenOn(this.wantedPort);
25488
+ } catch (error) {
25489
+ const held = error instanceof Error && "code" in error && error.code === "EADDRINUSE";
25490
+ if (!held || !this.mayMove) {
25491
+ throw error;
25492
+ }
25493
+ this.log("warn", `Editor bridge port ${this.wantedPort} is held; taking another one.`);
25494
+ await this.listenOn(0);
25441
25495
  }
25496
+ }
25497
+ listenOn(port) {
25442
25498
  return new Promise((resolve, reject) => {
25443
25499
  const server = http.createServer((_req, res) => {
25444
25500
  res.writeHead(404);
@@ -25462,6 +25518,8 @@ class GodotBridge extends EventEmitter {
25462
25518
  settled = true;
25463
25519
  this.httpServer = server;
25464
25520
  this.godotWss = godotWss;
25521
+ const bound = server.address();
25522
+ this.boundPort = typeof bound === "object" && bound !== null ? bound.port : port;
25465
25523
  this.log("info", `Editor bridge listening on ${this.host}:${this.port}`);
25466
25524
  resolve();
25467
25525
  });
@@ -25478,7 +25536,7 @@ class GodotBridge extends EventEmitter {
25478
25536
  godotWss.on("error", (error) => {
25479
25537
  this.log("error", `Godot WebSocket server error: ${error.message}`);
25480
25538
  });
25481
- server.listen(this.port, this.host);
25539
+ server.listen(port, this.host);
25482
25540
  });
25483
25541
  }
25484
25542
  async stop() {
@@ -25512,6 +25570,7 @@ class GodotBridge extends EventEmitter {
25512
25570
  this.httpServer = null;
25513
25571
  }
25514
25572
  await Promise.all(closeTasks);
25573
+ this.boundPort = null;
25515
25574
  this.connectionInfo = null;
25516
25575
  this.log("info", "WebSocket bridge stopped");
25517
25576
  }
@@ -25813,8 +25872,8 @@ class GodotBridge extends EventEmitter {
25813
25872
  }
25814
25873
  }
25815
25874
  var defaultBridge = null;
25816
- function getDefaultBridge() {
25817
- defaultBridge ??= new GodotBridge(resolveDefaultBridgePort(), resolveDefaultBridgeHost());
25875
+ function getDefaultBridge(mayMove = false) {
25876
+ defaultBridge ??= new GodotBridge(resolveDefaultBridgePort(), resolveDefaultBridgeHost(), DEFAULT_TIMEOUT_MS, mayMove);
25818
25877
  return defaultBridge;
25819
25878
  }
25820
25879
 
@@ -25827,7 +25886,7 @@ import { promisify } from "node:util";
25827
25886
  // src/detection.ts
25828
25887
  import { existsSync as existsSync3, readdirSync as readdirSync2, statSync } from "node:fs";
25829
25888
  import { homedir as homedir2 } from "node:os";
25830
- import { join as join3 } from "node:path";
25889
+ import { join as join4 } from "node:path";
25831
25890
  function resolveHomeDirectory() {
25832
25891
  try {
25833
25892
  return homedir2();
@@ -25851,7 +25910,7 @@ function scanDirectoryForGodotBinaries(directory, platform) {
25851
25910
  if (!pattern.test(name)) {
25852
25911
  continue;
25853
25912
  }
25854
- const fullPath = join3(directory, name);
25913
+ const fullPath = join4(directory, name);
25855
25914
  try {
25856
25915
  const stat = statSync(fullPath);
25857
25916
  if (stat.isFile()) {
@@ -25860,7 +25919,7 @@ function scanDirectoryForGodotBinaries(directory, platform) {
25860
25919
  } catch {}
25861
25920
  }
25862
25921
  matches.sort((a, b) => b.mtime - a.mtime);
25863
- return matches.map((m) => join3(directory, m.name));
25922
+ return matches.map((m) => join4(directory, m.name));
25864
25923
  }
25865
25924
  function conventionalPaths(platform, home) {
25866
25925
  const paths = ["godot"];
@@ -26003,9 +26062,9 @@ class GodotLocator {
26003
26062
 
26004
26063
  // src/headless.ts
26005
26064
  import { execFile as execFile2 } from "node:child_process";
26006
- import { mkdtempSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
26065
+ import { mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
26007
26066
  import { tmpdir as tmpdir2 } from "node:os";
26008
- import { join as join4 } from "node:path";
26067
+ import { join as join5 } from "node:path";
26009
26068
  import { promisify as promisify2 } from "node:util";
26010
26069
  var run2 = promisify2(execFile2);
26011
26070
  function snakeCased(params) {
@@ -26046,9 +26105,9 @@ function reason(stdout, stderr) {
26046
26105
  return stdout.trim().split(/\r?\n/).at(-1) ?? "no output at all";
26047
26106
  }
26048
26107
  async function runOperation(engine, operation, params, projectPath) {
26049
- const paramsDir = mkdtempSync(join4(tmpdir2(), "gdharness-params-"));
26050
- const paramsFile = join4(paramsDir, `${operation}.json`);
26051
- writeFileSync2(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
26108
+ const paramsDir = mkdtempSync(join5(tmpdir2(), "gdharness-params-"));
26109
+ const paramsFile = join5(paramsDir, `${operation}.json`);
26110
+ writeFileSync3(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
26052
26111
  const args = [
26053
26112
  "--headless",
26054
26113
  "--path",
@@ -26078,7 +26137,7 @@ async function runOperation(engine, operation, params, projectPath) {
26078
26137
  messages: []
26079
26138
  };
26080
26139
  } finally {
26081
- rmSync(paramsDir, { recursive: true, force: true });
26140
+ rmSync2(paramsDir, { recursive: true, force: true });
26082
26141
  }
26083
26142
  const payload = lastJsonObject(stdout);
26084
26143
  if (payload === null) {
@@ -26373,7 +26432,7 @@ function parseJUnit(xml) {
26373
26432
  import { realpathSync } from "node:fs";
26374
26433
  import { readFile, realpath } from "node:fs/promises";
26375
26434
  import { createConnection as createConnection2 } from "node:net";
26376
- import { dirname, resolve as resolve2 } from "node:path";
26435
+ import { dirname as dirname2, resolve as resolve2 } from "node:path";
26377
26436
  import { fileURLToPath, pathToFileURL } from "node:url";
26378
26437
 
26379
26438
  // src/paths.ts
@@ -26551,7 +26610,7 @@ class GodotLSPClient {
26551
26610
  if (this.initialized) {
26552
26611
  return;
26553
26612
  }
26554
- const rootPath = this.rootPath ?? dirname(resolve2(filePath));
26613
+ const rootPath = this.rootPath ?? dirname2(resolve2(filePath));
26555
26614
  await this.initialize(rootPath);
26556
26615
  }
26557
26616
  async sendRequest(method, params) {
@@ -26919,7 +26978,7 @@ async function handleLSPTool(client, toolName, args) {
26919
26978
 
26920
26979
  // src/project-scan.ts
26921
26980
  import { readdirSync as readdirSync3, readFileSync as readFileSync4 } from "node:fs";
26922
- import { join as join5 } from "node:path";
26981
+ import { join as join6 } from "node:path";
26923
26982
  var SKIPPED = new Set([".git", ".godot", ".import", "node_modules"]);
26924
26983
  var ASSET_EXTENSIONS = new Set(["png", "jpg", "jpeg", "webp", "svg", "ttf", "otf", "wav", "mp3", "ogg"]);
26925
26984
  function projectStructure(projectPath) {
@@ -26930,7 +26989,7 @@ function projectStructure(projectPath) {
26930
26989
  continue;
26931
26990
  }
26932
26991
  if (entry.isDirectory()) {
26933
- visit(join5(directory, entry.name));
26992
+ visit(join6(directory, entry.name));
26934
26993
  } else if (entry.isFile()) {
26935
26994
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
26936
26995
  if (extension === "tscn") {
@@ -26972,7 +27031,7 @@ function searchProject(projectPath, options) {
26972
27031
  if (SKIPPED.has(entry.name)) {
26973
27032
  continue;
26974
27033
  }
26975
- const entryPath = join5(directory, entry.name);
27034
+ const entryPath = join6(directory, entry.name);
26976
27035
  if (entry.isDirectory()) {
26977
27036
  visit(entryPath);
26978
27037
  continue;
@@ -27226,7 +27285,7 @@ function setupResourceHandlers(mcp, getProjectPath) {
27226
27285
  import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync6, unlinkSync } from "node:fs";
27227
27286
  import { createConnection as createConnection3 } from "node:net";
27228
27287
  import { tmpdir as tmpdir3 } from "node:os";
27229
- import { join as join6, resolve as resolve4 } from "node:path";
27288
+ import { join as join7, resolve as resolve4 } from "node:path";
27230
27289
 
27231
27290
  // src/tool-args.ts
27232
27291
  function asParams(value) {
@@ -27288,13 +27347,13 @@ function runtimeDirectory(variables = process.env) {
27288
27347
  return explicit;
27289
27348
  }
27290
27349
  const perUser = envValue("XDG_RUNTIME_DIR", variables);
27291
- return join6(perUser ?? tmpdir3(), "gdharness");
27350
+ return join7(perUser ?? tmpdir3(), "gdharness");
27292
27351
  }
27293
27352
  function runtimeDirectories(variables = process.env) {
27294
27353
  const candidates = [runtimeDirectory(variables)];
27295
27354
  const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
27296
27355
  for (const base of fallbacks) {
27297
- candidates.push(join6(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
27356
+ candidates.push(join7(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
27298
27357
  }
27299
27358
  return [...new Set(candidates.map((path) => resolve4(path)))];
27300
27359
  }
@@ -27357,7 +27416,7 @@ function announcedIn(directory) {
27357
27416
  if (!match) {
27358
27417
  continue;
27359
27418
  }
27360
- const file = join6(directory, entry);
27419
+ const file = join7(directory, entry);
27361
27420
  const pid = Number.parseInt(match[1] ?? "", 10);
27362
27421
  const announced = processAlive(pid) ? parseAnnouncement(file, pid) : { kind: "rubbish" };
27363
27422
  if (announced.kind === "rubbish") {
@@ -28251,7 +28310,7 @@ var TOOL_SPECS = [
28251
28310
  },
28252
28311
  {
28253
28312
  name: "runtime_input",
28254
- description: "Input to the running game: a whole click on a Control named by path, or a raw action, key, mouse button or mouse motion. All of it works headless, where the window is 64 by 64 and the GUI only takes what is inside it.",
28313
+ description: "Input to the running game: a whole click on a Control named by path, typing into whatever has the focus, or a raw action, key, mouse button or mouse motion. All of it works headless, where the window is 64 by 64 and the GUI only takes what is inside it.",
28255
28314
  parameters: {
28256
28315
  projectPath: RUNNING_PROJECT_PATH,
28257
28316
  nodePath: { type: "string", description: "click: the Control to click, at its centre." },
@@ -28259,6 +28318,10 @@ var TOOL_SPECS = [
28259
28318
  pressed: { type: "boolean", description: "Press or release. Default true." },
28260
28319
  strength: { type: "number", description: "action: 0 to 1. Default 1." },
28261
28320
  keycode: { type: "string", description: 'key: the key name, such as "Space" or "A".' },
28321
+ text: {
28322
+ type: "string",
28323
+ description: "text: what to type. A newline is Enter and a tab is Tab."
28324
+ },
28262
28325
  shift: { type: "boolean" },
28263
28326
  ctrl: { type: "boolean" },
28264
28327
  alt: { type: "boolean" },
@@ -28281,6 +28344,10 @@ var TOOL_SPECS = [
28281
28344
  },
28282
28345
  action: { summary: "press or release an action", requires: ["action"] },
28283
28346
  key: { summary: "press or release a key", requires: ["keycode"] },
28347
+ text: {
28348
+ summary: "type a string wherever the focus is, a character at a time",
28349
+ requires: ["text"]
28350
+ },
28284
28351
  mouse_click: { summary: "one mouse button event at a position", requires: ["x", "y"] },
28285
28352
  mouse_motion: { summary: "move the mouse to a position", requires: ["x", "y"] }
28286
28353
  }
@@ -28405,7 +28472,7 @@ function buildToolDefinitions() {
28405
28472
  var UPDATE_NOTICE_EVERY = 500;
28406
28473
  var FEEDBACK_NOTICE_EVERY = 250;
28407
28474
  var run3 = promisify3(execFile3);
28408
- var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
28475
+ var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
28409
28476
  var EDITOR_RESTART_TIMEOUT_MS = 90000;
28410
28477
  var BRIDGE_RETRY_MS = 2000;
28411
28478
  var PATH_SOLUTIONS = [
@@ -28503,7 +28570,7 @@ function camelCased(params) {
28503
28570
  class GodotServer {
28504
28571
  mcp;
28505
28572
  locator = new GodotLocator;
28506
- operationsScript = join7(__dirname2, "godot", "operations", "godot_operations.gd");
28573
+ operationsScript = join8(__dirname2, "godot", "operations", "godot_operations.gd");
28507
28574
  godotBridge;
28508
28575
  tools = buildToolDefinitions();
28509
28576
  activeProcess = null;
@@ -28517,8 +28584,11 @@ class GodotServer {
28517
28584
  bridgeRetry = null;
28518
28585
  lastProjectPath = null;
28519
28586
  shutdownInitiated = false;
28587
+ ownProject;
28588
+ announcedAt = null;
28520
28589
  constructor() {
28521
- this.godotBridge = getDefaultBridge();
28590
+ this.ownProject = envValue("GDHARNESS_PROJECT") ?? null;
28591
+ this.godotBridge = getDefaultBridge(this.ownProject !== null);
28522
28592
  this.mcp = new McpServer({ name: "gdharness", version: SERVER_VERSION }, { capabilities: { tools: {}, resources: {} } });
28523
28593
  this.setupToolHandlers();
28524
28594
  setupResourceHandlers(this.mcp, () => this.lastProjectPath);
@@ -28566,6 +28636,7 @@ class GodotServer {
28566
28636
  this.bridgeStartupError = null;
28567
28637
  const bridgeStatus = this.godotBridge.getStatus();
28568
28638
  console.error(`[SERVER] Godot Editor Bridge started on ${bridgeStatus.host}:${bridgeStatus.port}`);
28639
+ this.announceTheBridge();
28569
28640
  } catch (bridgeError) {
28570
28641
  const code = bridgeError instanceof Error && "code" in bridgeError && typeof bridgeError.code === "string" ? bridgeError.code : null;
28571
28642
  const reason = errorMessage(bridgeError);
@@ -28598,6 +28669,18 @@ class GodotServer {
28598
28669
  this.stopTryingTheBridge();
28599
28670
  const bridgeStatus = this.godotBridge.getStatus();
28600
28671
  console.error(`[SERVER] Godot Editor Bridge came up on ${bridgeStatus.host}:${bridgeStatus.port}; editor tools are live.`);
28672
+ this.announceTheBridge();
28673
+ }
28674
+ announceTheBridge() {
28675
+ if (this.ownProject === null) {
28676
+ return;
28677
+ }
28678
+ const status = this.godotBridge.getStatus();
28679
+ this.announcedAt = announceBridge(this.ownProject, {
28680
+ host: status.host,
28681
+ port: status.port,
28682
+ version: SERVER_VERSION
28683
+ });
28601
28684
  }
28602
28685
  stopTryingTheBridge() {
28603
28686
  if (this.bridgeRetry !== null) {
@@ -28608,6 +28691,8 @@ class GodotServer {
28608
28691
  async cleanup() {
28609
28692
  this.logDebug("Cleaning up resources");
28610
28693
  this.stopTryingTheBridge();
28694
+ withdrawBridge(this.announcedAt);
28695
+ this.announcedAt = null;
28611
28696
  if (this.activeProcess) {
28612
28697
  this.activeProcess.process?.kill();
28613
28698
  this.activeProcess = null;
@@ -28990,7 +29075,7 @@ class GodotServer {
28990
29075
  if (path === undefined) {
28991
29076
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
28992
29077
  }
28993
- const file = join7(path, "project.godot");
29078
+ const file = join8(path, "project.godot");
28994
29079
  if (!existsSync6(file)) {
28995
29080
  return {
28996
29081
  ok: false,
@@ -29255,7 +29340,7 @@ class GodotServer {
29255
29340
  return contained.response;
29256
29341
  }
29257
29342
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
29258
- if (!existsSync6(join7(project.value.path, runner))) {
29343
+ if (!existsSync6(join8(project.value.path, runner))) {
29259
29344
  return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
29260
29345
  "Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
29261
29346
  ]);
@@ -29288,7 +29373,7 @@ class GodotServer {
29288
29373
  ];
29289
29374
  const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
29290
29375
  this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
29291
- const userData = mkdtempSync2(join7(tmpdir4(), "gdharness-tests-"));
29376
+ const userData = mkdtempSync2(join8(tmpdir4(), "gdharness-tests-"));
29292
29377
  const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
29293
29378
  const hung = await new Promise((resolve) => {
29294
29379
  const timer = setTimeout(() => {
@@ -29304,20 +29389,20 @@ class GodotServer {
29304
29389
  resolve(false);
29305
29390
  });
29306
29391
  });
29307
- const reportsDir = join7(project.value.path, ".godot", "gdharness-reports");
29392
+ const reportsDir = join8(project.value.path, ".godot", "gdharness-reports");
29308
29393
  let report = null;
29309
29394
  let reportProblem = null;
29310
29395
  try {
29311
29396
  const written = existsSync6(reportsDir) ? readdirSync5(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
29312
29397
  const newest = written.at(-1);
29313
29398
  if (newest !== undefined) {
29314
- report = parseJUnit(readFileSync7(join7(reportsDir, newest, "results.xml"), "utf8"));
29399
+ report = parseJUnit(readFileSync7(join8(reportsDir, newest, "results.xml"), "utf8"));
29315
29400
  }
29316
29401
  } catch (error) {
29317
29402
  reportProblem = errorMessage(error);
29318
29403
  } finally {
29319
- rmSync2(reportsDir, { recursive: true, force: true });
29320
- rmSync2(userData, { recursive: true, force: true });
29404
+ rmSync3(reportsDir, { recursive: true, force: true });
29405
+ rmSync3(userData, { recursive: true, force: true });
29321
29406
  }
29322
29407
  const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries;
29323
29408
  const verdicts = {
@@ -29430,6 +29515,7 @@ class GodotServer {
29430
29515
  startupError: this.bridgeStartupError,
29431
29516
  staleNote: stale ? addonMismatch(status.addonVersion, SERVER_VERSION) : undefined,
29432
29517
  retryingBridge: this.bridgeRetry === null ? undefined : true,
29518
+ announcedAt: this.announcedAt ?? undefined,
29433
29519
  note: isPortConflict ? "Bridge port is already in use. Another gdharness instance owns the editor bridge, so this server cannot reach the editor. Usually the server this one replaced, still on its way out." : undefined,
29434
29520
  suggestion: isPortConflict ? `This server is asking for the port again every ${BRIDGE_RETRY_MS / 1000}s and takes it the moment the other one lets go, so editor tools come back on their own. Ask again, or end the older gdharness process to have it now.` : undefined
29435
29521
  };
@@ -29825,8 +29911,8 @@ class GodotServer {
29825
29911
  return this.createErrorResponse(choice.problem);
29826
29912
  }
29827
29913
  const expectsScreenshot = command === "capture_screenshot" || command === "capture_viewport";
29828
- const screenshotDir = expectsScreenshot ? mkdtempSync2(join7(tmpdir4(), "gdharness-runtime-screenshot-")) : null;
29829
- const screenshotPath = screenshotDir ? join7(screenshotDir, "capture.png") : null;
29914
+ const screenshotDir = expectsScreenshot ? mkdtempSync2(join8(tmpdir4(), "gdharness-runtime-screenshot-")) : null;
29915
+ const screenshotPath = screenshotDir ? join8(screenshotDir, "capture.png") : null;
29830
29916
  try {
29831
29917
  const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
29832
29918
  if (!reply.ok) {
@@ -29852,7 +29938,7 @@ class GodotServer {
29852
29938
  };
29853
29939
  } finally {
29854
29940
  if (screenshotDir) {
29855
- rmSync2(screenshotDir, { recursive: true, force: true });
29941
+ rmSync3(screenshotDir, { recursive: true, force: true });
29856
29942
  }
29857
29943
  }
29858
29944
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "mcpName": "io.github.Aureliolo/gdharness",
5
5
  "description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
6
6
  "type": "module",