gdharness 1.1.5 → 1.1.7

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
@@ -10190,19 +10190,19 @@ import {
10190
10190
  closeSync as closeSync2,
10191
10191
  constants,
10192
10192
  copyFileSync,
10193
- existsSync as existsSync10,
10193
+ existsSync as existsSync11,
10194
10194
  fstatSync,
10195
10195
  mkdirSync as mkdirSync10,
10196
10196
  mkdtempSync as mkdtempSync2,
10197
10197
  openSync as openSync3,
10198
10198
  readdirSync as readdirSync7,
10199
- readFileSync as readFileSync16,
10199
+ readFileSync as readFileSync17,
10200
10200
  readSync as readSync2,
10201
10201
  rmdirSync,
10202
- statSync as statSync7
10202
+ statSync as statSync8
10203
10203
  } from "node:fs";
10204
10204
  import { tmpdir as tmpdir4 } from "node:os";
10205
- import { basename as basename3, dirname as dirname11, extname as extname2, isAbsolute as isAbsolute3, join as join16, normalize as normalize2, relative as relative3 } from "node:path";
10205
+ import { basename as basename4, dirname as dirname11, extname as extname2, isAbsolute as isAbsolute3, join as join17, normalize as normalize2, relative as relative3 } from "node:path";
10206
10206
  import { setTimeout as delay3 } from "node:timers/promises";
10207
10207
  import { fileURLToPath as fileURLToPath4 } from "node:url";
10208
10208
  import { promisify as promisify5 } from "node:util";
@@ -24808,9 +24808,10 @@ function runArguments(options) {
24808
24808
  }
24809
24809
  return args;
24810
24810
  }
24811
- function editorArguments(projectPath, ports, logFile) {
24811
+ function editorArguments(projectPath, ports, logFile, hidden) {
24812
24812
  return [
24813
24813
  "-e",
24814
+ ...hidden ? ["--headless"] : [],
24814
24815
  "--path",
24815
24816
  projectPath,
24816
24817
  "--lsp-port",
@@ -26748,6 +26749,9 @@ async function handleDAPTool(client, toolName, args) {
26748
26749
  }
26749
26750
  }
26750
26751
 
26752
+ // src/desktop.ts
26753
+ var HIDDEN_DESKTOP = "gdharness";
26754
+
26751
26755
  // src/dictionary.ts
26752
26756
  function emptyRecord() {
26753
26757
  return Object.create(null);
@@ -27921,7 +27925,6 @@ var HEADLESS_OPERATIONS = dictionary({
27921
27925
  status: "get_import_status",
27922
27926
  options: "get_import_options",
27923
27927
  set_options: "set_import_options",
27924
- reimport: "reimport_resource",
27925
27928
  uid: "get_uid",
27926
27929
  refresh_classes: "refresh_class_cache"
27927
27930
  }),
@@ -27935,7 +27938,7 @@ var HEADLESS_OPERATIONS = dictionary({
27935
27938
  })
27936
27939
  });
27937
27940
  var ENGINE_PASSES = dictionary({
27938
- project_import: dictionary({ refresh_uids: "import" })
27941
+ project_import: dictionary({ refresh_uids: "import", reimport: "import" })
27939
27942
  });
27940
27943
  var EDITOR_READS = dictionary({
27941
27944
  project_settings: dictionary({ get: "get_project_setting" })
@@ -28253,7 +28256,8 @@ function whyNoReport(printed, asked) {
28253
28256
  return printed.some((line) => NOTHING_RAN.test(line)) ? `no test cases found at ${asked}` : null;
28254
28257
  }
28255
28258
  var ESCAPE = String.fromCharCode(27);
28256
- var PRINTED_ACTUAL = new RegExp(` but was\\n '([\\s\\S]*?)'((?:${ESCAPE}\\[[0-9;]*m)*)\\tat ('[^'\\n]*' in \\S+:\\d+)`, "g");
28259
+ var LOCATION = `'[^'\\n]*' in \\S+:\\d+`;
28260
+ var PRINTED_ACTUAL = new RegExp(` but was\\n '((?:(?!\\tat ${LOCATION})[\\s\\S])*?)'((?:${ESCAPE}\\[[0-9;]*m)*)\\tat (${LOCATION})`, "g");
28257
28261
  var COLOUR = new RegExp(`${ESCAPE}\\[([0-9;]*)m`, "g");
28258
28262
  var SPELLED = {
28259
28263
  "<LF>": `
@@ -28297,6 +28301,7 @@ function readDiff(printed) {
28297
28301
  return { merged, actual };
28298
28302
  }
28299
28303
  var BOTH_SIDES_ALIKE_NOTE = "[gdharness: the expected and the actual printed alike because gdUnit4 drops the marks that tell them apart when it writes its report, and the runner printed no copy of this one, so neither side above is the value the assertion found.]";
28304
+ var VALUE_NOT_RECOVERED_NOTE = '[gdharness: the value after "but was" may not be the one the assertion found. gdUnit4 writes a failing string into its report as a character diff against the expected one with the marks dropped, which merges the two, and the runner printed no copy of this one to read the value back from.]';
28300
28305
  function withActualsPrinted(failed, printed) {
28301
28306
  const diffs = [...printed.replaceAll(`\r
28302
28307
  `, `
@@ -28309,6 +28314,7 @@ function withActualsPrinted(failed, printed) {
28309
28314
  return entry;
28310
28315
  }
28311
28316
  let detail = entry.detail;
28317
+ let recovered = false;
28312
28318
  for (const diff of diffs) {
28313
28319
  const wrong = ` but was
28314
28320
  '${diff.merged.replaceAll("\r", "")}'
@@ -28317,12 +28323,13 @@ function withActualsPrinted(failed, printed) {
28317
28323
  detail = detail.replace(wrong, () => ` but was
28318
28324
  '${diff.actual}'
28319
28325
  at ${diff.at}`);
28326
+ recovered = true;
28320
28327
  }
28321
28328
  }
28322
- const alike = /Expecting:\n '([\s\S]*)'\n but was\n '\1'\n\tat /.test(detail);
28323
- if (alike) {
28329
+ if (!recovered && /Expecting:\n '[\s\S]*'\n but was\n '[\s\S]*'\n\tat /.test(detail)) {
28330
+ const alike = /Expecting:\n '([\s\S]*)'\n but was\n '\1'\n\tat /.test(detail);
28324
28331
  detail = `${detail}
28325
- ${BOTH_SIDES_ALIKE_NOTE}`;
28332
+ ${alike ? BOTH_SIDES_ALIKE_NOTE : VALUE_NOT_RECOVERED_NOTE}`;
28326
28333
  }
28327
28334
  return detail === entry.detail ? entry : { ...entry, detail };
28328
28335
  });
@@ -28936,7 +28943,9 @@ async function launchOutsideTheTree(spec, runtime = process.execPath, keeper = K
28936
28943
  command: spec.command,
28937
28944
  args: spec.args,
28938
28945
  envChanges: changesFrom(spec.env, process.env),
28939
- ...spec.run === undefined ? {} : { run: spec.run }
28946
+ ...spec.run === undefined ? {} : { run: spec.run },
28947
+ ...spec.desktop === undefined ? {} : { desktop: spec.desktop },
28948
+ ...spec.noActivate === true ? { noActivate: true } : {}
28940
28949
  });
28941
28950
  let printed = "";
28942
28951
  let complained = "";
@@ -29121,8 +29130,28 @@ function searchProject(projectPath, options) {
29121
29130
  return result;
29122
29131
  }
29123
29132
 
29133
+ // src/reimport.ts
29134
+ import { createHash as createHash3 } from "node:crypto";
29135
+ import { existsSync as existsSync9, readFileSync as readFileSync12, rmSync as rmSync5, statSync as statSync5, utimesSync, writeFileSync as writeFileSync8 } from "node:fs";
29136
+ import { basename as basename2, join as join13 } from "node:path";
29137
+ function forceNextImport(projectPath, resourcePath) {
29138
+ const relative = resourcePath.replace(/^res:\/\//, "");
29139
+ const sidecar = join13(projectPath, `${relative}.import`);
29140
+ if (!existsSync9(sidecar)) {
29141
+ return;
29142
+ }
29143
+ const { atime, mtimeMs } = statSync5(sidecar);
29144
+ writeFileSync8(sidecar, readFileSync12(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
29145
+ utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
29146
+ rmSync5(importRecord(projectPath, resourcePath), { force: true });
29147
+ }
29148
+ function importRecord(projectPath, resourcePath) {
29149
+ const digest = createHash3("md5").update(resourcePath).digest("hex");
29150
+ return join13(projectPath, ".godot", "imported", `${basename2(resourcePath)}-${digest}.md5`);
29151
+ }
29152
+
29124
29153
  // src/resources.ts
29125
- import { readFileSync as readFileSync12 } from "node:fs";
29154
+ import { readFileSync as readFileSync13 } from "node:fs";
29126
29155
  import { extname, resolve as resolve4 } from "node:path";
29127
29156
  var STATIC_RESOURCES = [
29128
29157
  {
@@ -29317,7 +29346,7 @@ function readResourceText(uri, getProjectPath) {
29317
29346
  const parsedUri = parseGodotUri(uri);
29318
29347
  if (parsedUri.kind === "project-info") {
29319
29348
  const projectFilePath = resolveProjectFile(projectPath, "project.godot");
29320
- const rawProject = readFileSync12(projectFilePath, "utf-8");
29349
+ const rawProject = readFileSync13(projectFilePath, "utf-8");
29321
29350
  const parsedProject = parseProjectGodot(rawProject);
29322
29351
  return {
29323
29352
  mimeType: "application/json",
@@ -29326,7 +29355,7 @@ function readResourceText(uri, getProjectPath) {
29326
29355
  }
29327
29356
  const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
29328
29357
  ensureAllowedExtension(parsedUri.kind, filePath);
29329
- const text = readFileSync12(filePath, "utf-8");
29358
+ const text = readFileSync13(filePath, "utf-8");
29330
29359
  return {
29331
29360
  mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
29332
29361
  text
@@ -29363,16 +29392,16 @@ function setupResourceHandlers(mcp, getProjectPath) {
29363
29392
  }
29364
29393
 
29365
29394
  // src/restart-note.ts
29366
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync13, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "node:fs";
29367
- import { dirname as dirname8, join as join13 } from "node:path";
29395
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync14, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "node:fs";
29396
+ import { dirname as dirname8, join as join14 } from "node:path";
29368
29397
  function restartNotePath(projectPath) {
29369
- return join13(projectPath, ".godot", "gdharness-restart.json");
29398
+ return join14(projectPath, ".godot", "gdharness-restart.json");
29370
29399
  }
29371
29400
  function noteRestartBegun(note) {
29372
29401
  const path = restartNotePath(note.projectPath);
29373
29402
  try {
29374
29403
  mkdirSync7(dirname8(path), { recursive: true });
29375
- writeFileSync8(path, `${JSON.stringify(note, null, 2)}
29404
+ writeFileSync9(path, `${JSON.stringify(note, null, 2)}
29376
29405
  `, "utf8");
29377
29406
  } catch (error) {
29378
29407
  console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
@@ -29380,7 +29409,7 @@ function noteRestartBegun(note) {
29380
29409
  }
29381
29410
  function restartOwed(projectPath) {
29382
29411
  try {
29383
- const parsed = JSON.parse(readFileSync13(restartNotePath(projectPath), "utf8"));
29412
+ const parsed = JSON.parse(readFileSync14(restartNotePath(projectPath), "utf8"));
29384
29413
  if (typeof parsed !== "object" || parsed === null) {
29385
29414
  return null;
29386
29415
  }
@@ -29395,7 +29424,7 @@ function restartOwed(projectPath) {
29395
29424
  }
29396
29425
  function restartSettled(projectPath) {
29397
29426
  try {
29398
- rmSync5(restartNotePath(projectPath), { force: true });
29427
+ rmSync6(restartNotePath(projectPath), { force: true });
29399
29428
  } catch (error) {
29400
29429
  console.error(`[SERVER] Could not clear the restart note: ${errorMessage(error)}`);
29401
29430
  }
@@ -29403,33 +29432,33 @@ function restartSettled(projectPath) {
29403
29432
 
29404
29433
  // src/run-record.ts
29405
29434
  import { execFileSync as execFileSync2 } from "node:child_process";
29406
- import { createHash as createHash3 } from "node:crypto";
29435
+ import { createHash as createHash4 } from "node:crypto";
29407
29436
  import {
29408
29437
  mkdirSync as mkdirSync8,
29409
29438
  openSync as openSync2,
29410
29439
  readdirSync as readdirSync5,
29411
- readFileSync as readFileSync14,
29440
+ readFileSync as readFileSync15,
29412
29441
  realpathSync as realpathSync3,
29413
- rmSync as rmSync6,
29414
- statSync as statSync5,
29415
- writeFileSync as writeFileSync9
29442
+ rmSync as rmSync7,
29443
+ statSync as statSync6,
29444
+ writeFileSync as writeFileSync10
29416
29445
  } from "node:fs";
29417
- import { basename as basename2, dirname as dirname9, join as join14, resolve as resolve5 } from "node:path";
29446
+ import { basename as basename3, dirname as dirname9, join as join15, resolve as resolve5 } from "node:path";
29418
29447
  function runsDirectory() {
29419
- return join14(runtimeDirectory(), "runs");
29448
+ return join15(runtimeDirectory(), "runs");
29420
29449
  }
29421
29450
  function realAsFarAsItExists(path) {
29422
29451
  const missing = [];
29423
29452
  let at = path;
29424
29453
  for (;; ) {
29425
29454
  try {
29426
- return join14(realpathSync3.native(at), ...missing);
29455
+ return join15(realpathSync3.native(at), ...missing);
29427
29456
  } catch {
29428
29457
  const parent = dirname9(at);
29429
29458
  if (parent === at) {
29430
29459
  return path;
29431
29460
  }
29432
- missing.unshift(basename2(at));
29461
+ missing.unshift(basename3(at));
29433
29462
  at = parent;
29434
29463
  }
29435
29464
  }
@@ -29437,27 +29466,27 @@ function realAsFarAsItExists(path) {
29437
29466
  function projectKey(projectPath) {
29438
29467
  const real = realAsFarAsItExists(resolve5(projectPath)).replace(/[\\/]+$/, "");
29439
29468
  const folded = process.platform === "win32" || process.platform === "darwin" ? real.toLowerCase() : real;
29440
- return createHash3("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
29469
+ return createHash4("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
29441
29470
  }
29442
29471
  function runRecordPath(projectPath, directory = runtimeDirectory()) {
29443
- return join14(directory, "runs", `note-${projectKey(projectPath)}.json`);
29472
+ return join15(directory, "runs", `note-${projectKey(projectPath)}.json`);
29444
29473
  }
29445
29474
  var SHARED_NOTE = "run.json";
29446
29475
  function openTranscript(startedAt) {
29447
29476
  mkdirSync8(runsDirectory(), { recursive: true });
29448
- const path = join14(runsDirectory(), `run-${startedAt}.log`);
29477
+ const path = join15(runsDirectory(), `run-${startedAt}.log`);
29449
29478
  return { path, fd: openSync2(path, "a") };
29450
29479
  }
29451
29480
  function editorNotePath(projectPath, directory = runtimeDirectory()) {
29452
- return join14(directory, "runs", `editor-${projectKey(projectPath)}.json`);
29481
+ return join15(directory, "runs", `editor-${projectKey(projectPath)}.json`);
29453
29482
  }
29454
29483
  function writeEditorRunNote(note) {
29455
29484
  mkdirSync8(runsDirectory(), { recursive: true });
29456
- writeFileSync9(editorNotePath(note.projectPath), JSON.stringify(note, null, 2), "utf8");
29485
+ writeFileSync10(editorNotePath(note.projectPath), JSON.stringify(note, null, 2), "utf8");
29457
29486
  }
29458
29487
  function readEditorRunNote(projectPath) {
29459
29488
  for (const directory of runtimeDirectories()) {
29460
- const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join14(directory, "runs", "editor-run.json"));
29489
+ const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join15(directory, "runs", "editor-run.json"));
29461
29490
  if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
29462
29491
  return note;
29463
29492
  }
@@ -29467,7 +29496,7 @@ function readEditorRunNote(projectPath) {
29467
29496
  function editorNoteAt(path) {
29468
29497
  let parsed;
29469
29498
  try {
29470
- parsed = JSON.parse(readFileSync14(path, "utf8"));
29499
+ parsed = JSON.parse(readFileSync15(path, "utf8"));
29471
29500
  } catch {
29472
29501
  return null;
29473
29502
  }
@@ -29489,7 +29518,7 @@ function readRunRecord(projectPath) {
29489
29518
  if (own !== null) {
29490
29519
  return own;
29491
29520
  }
29492
- const shared = recordAt(join14(directory, "runs", SHARED_NOTE));
29521
+ const shared = recordAt(join15(directory, "runs", SHARED_NOTE));
29493
29522
  if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
29494
29523
  return shared;
29495
29524
  }
@@ -29501,13 +29530,13 @@ function everyRunRecord() {
29501
29530
  for (const directory of runtimeDirectories()) {
29502
29531
  let entries;
29503
29532
  try {
29504
- entries = readdirSync5(join14(directory, "runs"));
29533
+ entries = readdirSync5(join15(directory, "runs"));
29505
29534
  } catch {
29506
29535
  continue;
29507
29536
  }
29508
29537
  for (const entry of entries) {
29509
29538
  if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
29510
- const record = recordAt(join14(directory, "runs", entry));
29539
+ const record = recordAt(join15(directory, "runs", entry));
29511
29540
  if (record !== null) {
29512
29541
  found.push(record);
29513
29542
  }
@@ -29519,7 +29548,7 @@ function everyRunRecord() {
29519
29548
  function recordAt(path) {
29520
29549
  let parsed;
29521
29550
  try {
29522
- parsed = JSON.parse(readFileSync14(path, "utf8"));
29551
+ parsed = JSON.parse(readFileSync15(path, "utf8"));
29523
29552
  } catch {
29524
29553
  return null;
29525
29554
  }
@@ -29565,7 +29594,7 @@ function runningAs(pid) {
29565
29594
  const began = startedAt(pid, null);
29566
29595
  const when = began === null ? {} : { startedAt: began };
29567
29596
  if (process.platform === "linux") {
29568
- const raw = readFileSync14(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
29597
+ const raw = readFileSync15(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
29569
29598
  return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
29570
29599
  }
29571
29600
  const args = execFileSync2("ps", ["-p", String(pid), "-o", "args="], {
@@ -29668,7 +29697,7 @@ function judgeRun(record, running, needed) {
29668
29697
  if (running.startedAt !== undefined && running.startedAt - record.startedAt > SAME_RUN_WINDOW_MS) {
29669
29698
  return false;
29670
29699
  }
29671
- const engine = record.command === undefined ? null : basename2(record.command);
29700
+ const engine = record.command === undefined ? null : basename3(record.command);
29672
29701
  const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
29673
29702
  const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
29674
29703
  if (wanted !== null && !said.includes(wanted)) {
@@ -29704,19 +29733,19 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
29704
29733
  }
29705
29734
  function clearRunRecord(projectPath) {
29706
29735
  for (const directory of runtimeDirectories()) {
29707
- const shared = join14(directory, "runs", SHARED_NOTE);
29736
+ const shared = join15(directory, "runs", SHARED_NOTE);
29708
29737
  const sharedRecord = recordAt(shared);
29709
29738
  const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
29710
29739
  for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
29711
29740
  try {
29712
- rmSync6(path, { force: true });
29741
+ rmSync7(path, { force: true });
29713
29742
  } catch {}
29714
29743
  }
29715
29744
  }
29716
29745
  }
29717
29746
  function ageMs(path, now) {
29718
29747
  try {
29719
- return now - statSync5(path).mtimeMs;
29748
+ return now - statSync6(path).mtimeMs;
29720
29749
  } catch {
29721
29750
  return 0;
29722
29751
  }
@@ -29734,29 +29763,29 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
29734
29763
  if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
29735
29764
  continue;
29736
29765
  }
29737
- const path = join14(directory, entry);
29766
+ const path = join15(directory, entry);
29738
29767
  if (kept.has(path) || ageMs(path, now) < keepMs) {
29739
29768
  continue;
29740
29769
  }
29741
29770
  try {
29742
- rmSync6(path);
29771
+ rmSync7(path);
29743
29772
  } catch {}
29744
29773
  }
29745
29774
  }
29746
29775
 
29747
29776
  // src/setup.ts
29748
- import { createHash as createHash4 } from "node:crypto";
29777
+ import { createHash as createHash5 } from "node:crypto";
29749
29778
  import {
29750
29779
  cpSync,
29751
- existsSync as existsSync9,
29780
+ existsSync as existsSync10,
29752
29781
  mkdirSync as mkdirSync9,
29753
29782
  readdirSync as readdirSync6,
29754
- readFileSync as readFileSync15,
29755
- rmSync as rmSync7,
29756
- statSync as statSync6,
29757
- writeFileSync as writeFileSync10
29783
+ readFileSync as readFileSync16,
29784
+ rmSync as rmSync8,
29785
+ statSync as statSync7,
29786
+ writeFileSync as writeFileSync11
29758
29787
  } from "node:fs";
29759
- import { dirname as dirname10, join as join15 } from "node:path";
29788
+ import { dirname as dirname10, join as join16 } from "node:path";
29760
29789
  import { fileURLToPath as fileURLToPath3 } from "node:url";
29761
29790
  var ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
29762
29791
  var EDITOR_PLUGINS = ["gdharness_editor", "auto_reload"];
@@ -29767,27 +29796,27 @@ var RUNTIME_AUTOLOAD = {
29767
29796
  var VERSION_MARKER = ".gdharness-version";
29768
29797
  var DIGEST_MARKER = ".gdharness-digest";
29769
29798
  function editorAddonDigest(from = shippedAddonsDirectory()) {
29770
- const hash = createHash4("sha256");
29799
+ const hash = createHash5("sha256");
29771
29800
  const walk = (directory, relative) => {
29772
29801
  for (const name of readdirSync6(directory).sort()) {
29773
29802
  if (name === VERSION_MARKER || name === DIGEST_MARKER) {
29774
29803
  continue;
29775
29804
  }
29776
- const path = join15(directory, name);
29805
+ const path = join16(directory, name);
29777
29806
  const inside = `${relative}/${name}`;
29778
- if (statSync6(path).isDirectory()) {
29807
+ if (statSync7(path).isDirectory()) {
29779
29808
  walk(path, inside);
29780
29809
  } else {
29781
29810
  hash.update(`${inside}
29782
29811
  `);
29783
- hash.update(readFileSync15(path));
29812
+ hash.update(readFileSync16(path));
29784
29813
  hash.update(`
29785
29814
  `);
29786
29815
  }
29787
29816
  }
29788
29817
  };
29789
29818
  for (const name of EDITOR_PLUGINS) {
29790
- walk(join15(from, name), name);
29819
+ walk(join16(from, name), name);
29791
29820
  }
29792
29821
  return hash.digest("hex");
29793
29822
  }
@@ -29803,14 +29832,14 @@ function shippedEditorDigest() {
29803
29832
  return shippedDigest ?? undefined;
29804
29833
  }
29805
29834
  function shippedAddonsDirectory() {
29806
- return join15(dirname10(fileURLToPath3(import.meta.url)), "godot", "addons");
29835
+ return join16(dirname10(fileURLToPath3(import.meta.url)), "godot", "addons");
29807
29836
  }
29808
29837
  function installedAddonVersion(projectPath) {
29809
- const marker = join15(projectPath, "addons", ADDONS[0], VERSION_MARKER);
29810
- if (!existsSync9(marker)) {
29838
+ const marker = join16(projectPath, "addons", ADDONS[0], VERSION_MARKER);
29839
+ if (!existsSync10(marker)) {
29811
29840
  return null;
29812
29841
  }
29813
- const said = readFileSync15(marker, "utf8").trim();
29842
+ const said = readFileSync16(marker, "utf8").trim();
29814
29843
  return said === "" ? null : said;
29815
29844
  }
29816
29845
 
@@ -30001,7 +30030,7 @@ var TOOL_SPECS = [
30001
30030
  },
30002
30031
  {
30003
30032
  name: "project_settings",
30004
- description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses.",
30033
+ description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed from the file (named under editorAdopted), or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start.",
30005
30034
  parameters: {
30006
30035
  projectPath: PROJECT_PATH,
30007
30036
  setting: { type: "string", description: 'Setting path, such as "display/window/size/viewport_width".' },
@@ -30113,19 +30142,30 @@ var TOOL_SPECS = [
30113
30142
  description: 'set_options: import options keyed as the .import file spells them, {"compress/mode": 1}.',
30114
30143
  additionalProperties: true
30115
30144
  },
30116
- reimport: { type: "boolean", description: "set_options: reimport afterwards. Default true." },
30117
- force: { type: "boolean", description: "reimport: reimport even what is current. Default false." }
30145
+ reimport: {
30146
+ type: "boolean",
30147
+ description: "set_options: reimport afterwards, answered under reimport as project_import reimport answers. Default true."
30148
+ },
30149
+ force: { type: "boolean", description: "reimport: reimport even what is current. Default false." },
30150
+ timeoutMs: {
30151
+ type: "integer",
30152
+ minimum: 1,
30153
+ description: "reimport, set_options: how long to wait for the editor to finish reimporting. Default 300000."
30154
+ }
30118
30155
  },
30119
30156
  requires: ["projectPath"],
30120
30157
  operations: {
30121
30158
  status: {
30122
- summary: "which resources are outdated or failed, or one resource with resourcePath, judged by content as the editor judges it: a source whose hash differs from the one its import recorded, an import whose output under .godot/imported is gone (missing_outputs), and a glTF scene whose import does not depend on an image it names, because it was built before that image was imported (imported_without), each with its reason",
30159
+ summary: "which resources are outdated or failed, or one resource with resourcePath, each with its reason. Judged by content with the checks the editor makes before it reimports: a sidecar changed since its import, as setting an option without a reimport leaves it; a sidecar with no uid or written for another file; an output under .godot/imported that is gone (missing_outputs) or changed; a source whose hash differs from the one its import recorded, or with no record. Beyond the editor, a glTF scene whose import does not depend on an image it names, because it was built before that image was imported (imported_without; a scene whose import runs an import script is not judged on images). failed is an import that failed, which the editor never tries again on its own. Without resourcePath, a sidecar whose source is gone is listed as missing_source",
30123
30160
  requires: []
30124
30161
  },
30125
30162
  options: { summary: "the import options of one resource", requires: ["resourcePath"] },
30126
- set_options: { summary: "change import options", requires: ["resourcePath", "options"] },
30163
+ set_options: {
30164
+ summary: "change import options in the sidecar, then apply them with a reimport of that resource",
30165
+ requires: ["resourcePath", "options"]
30166
+ },
30127
30167
  reimport: {
30128
- summary: "reimport one resource, or everything modified without resourcePath",
30168
+ summary: "reimport what status says is outdated or failed, one resource with resourcePath, or what is current as well with force. Through the open editor when one serves the project, so it reloads what it holds, waiting for it to finish; otherwise through the engine's own import pass. Answered from a second status read: reimported lists what is current now, notReimported what is not, with its status and reason, and via says which of the two did the work. stillImporting is an editor that had not finished within timeoutMs",
30129
30169
  requires: []
30130
30170
  },
30131
30171
  uid: { summary: "the UID of one file", requires: ["resourcePath"] },
@@ -30427,8 +30467,15 @@ var TOOL_SPECS = [
30427
30467
  },
30428
30468
  {
30429
30469
  name: "editor_launch",
30430
- description: "Opens the Godot editor on a project, in a window on this machine, or restarts the one already connected. An editor goes on serving the addon it read at startup, so restart is what puts a gdharness upgrade into effect; it saves open scenes on the way out and answers with the version that came back. Only an editor with a window can be restarted, because the engine hands back none of the arguments it was started with. editor_status says which editor is connected and whether it is holding an old addon. Opening a project is a save and so is closing one, which is the part that is not obvious from outside: the editor imports the project on open and writes project.godot back, and Godot writes only what differs from its own defaults, so a key named deliberately at its default value is dropped either way. A restart says which under settingsDropped in its own answer because it waits for the editor to return. An open cannot, since it answers as soon as the process exists and the save happens during the import minutes later, so editor_status carries that reading once the editor has connected. Nothing else will say a key went until something depends on one. A restart of an editor this server opened is a quit and then a launch, and this server can be ended between them, usually by the reconnect the restart was being taken for: the editor is gone and nothing replaces it. That is written down before the quit, so the next server says so under restartInterrupted in editor_status rather than waiting for an editor that is not coming, and open finishes it, saying so under finishesRestart.",
30431
- parameters: { projectPath: { ...PROJECT_PATH, ops: ["open"] } },
30470
+ description: "Opens the Godot editor on a project, or restarts the one already connected. The editor is an ordinary visible one on the desktop in use, and on Windows it is shown without being activated, so opening or reopening it does not take the keyboard from whatever the user is typing in. hidden: true opens it headless instead and, on Windows, on a desktop of its own, where the games it plays go too; a hidden editor serves the bridge, the language server and the debug adapter as a visible one does. A game a visible editor plays opens as a window on the desktop in use. An editor goes on serving the addon it read at startup, so restart is what puts a gdharness upgrade into effect, and nothing else here restarts one; it saves open scenes on the way out and answers with the version that came back. An editor a server opened is started again by the server, visible unless hidden: true is passed again. One opened by hand restarts itself, which only one with a window can do, because the engine hands back none of the arguments it was started with. editor_status says which editor is connected and whether it is holding an old addon. Opening a project is a save and so is closing one, which is the part that is not obvious from outside: the editor imports the project on open and writes project.godot back, and Godot writes only what differs from its own defaults, so a key named deliberately at its default value is dropped either way. A restart says which under settingsDropped in its own answer because it waits for the editor to return. An open cannot, since it answers as soon as the process exists and the save happens during the import minutes later, so editor_status carries that reading once the editor has connected. Nothing else will say a key went until something depends on one. A restart of an editor this server opened is a quit and then a launch, and this server can be ended between them, usually by the reconnect the restart was being taken for: the editor is gone and nothing replaces it. That is written down before the quit, so the next server says so under restartInterrupted in editor_status rather than waiting for an editor that is not coming, and open finishes it, saying so under finishesRestart.",
30471
+ parameters: {
30472
+ projectPath: { ...PROJECT_PATH, ops: ["open"] },
30473
+ hidden: {
30474
+ type: "boolean",
30475
+ ops: ["open", "restart"],
30476
+ description: "Open the editor headless and, on Windows, on a desktop of its own, so no window of it or of the games it plays appears. Default false: one visible editor, shown without taking the focus."
30477
+ }
30478
+ },
30432
30479
  requires: [],
30433
30480
  operations: {
30434
30481
  open: { summary: "open the editor on a project", requires: ["projectPath"] },
@@ -30453,6 +30500,11 @@ var TOOL_SPECS = [
30453
30500
  description: `The game's own arguments, what OS.get_cmdline_user_args() answers, such as ["--level=2"]. The separator is added here. A run with any is started by this server rather than by the editor, which fixes the game's command line when it opens the project, so the debug_* tools do not answer for it.`
30454
30501
  },
30455
30502
  headless: { type: "boolean", ops: ["start"], description: "start: force a window or no window." },
30503
+ visible: {
30504
+ type: "boolean",
30505
+ ops: ["start"],
30506
+ description: "start: on Windows, show the window on the desktop in use. By default a windowed run this server starts goes on a desktop of its own, where it renders and runtime_capture works but the window never shows and never takes the keyboard. A game a visible editor plays opens on the desktop in use, and one a hidden editor (editor_launch hidden: true) plays goes on its desktop. Elsewhere than Windows the window shows either way. Default false."
30507
+ },
30456
30508
  savesIn: {
30457
30509
  type: "string",
30458
30510
  ops: ["start", "check"],
@@ -30783,7 +30835,7 @@ var TOOL_SPECS = [
30783
30835
  },
30784
30836
  {
30785
30837
  name: "runtime_capture",
30786
- description: "A picture of the running game: the whole screen or one viewport, as an image. Needs the game running with a window.",
30838
+ description: "A picture of the running game: the whole screen or one viewport, as an image. Needs the game running with a window. A capture asked for before the game's first frame is drawn waits for that frame, since the game announces itself before it and the viewport holds nothing it drew until then.",
30787
30839
  parameters: {
30788
30840
  projectPath: RUNNING_PROJECT_PATH,
30789
30841
  pid: RUNNING_PID,
@@ -31091,15 +31143,15 @@ function captureDestinationRefusal(outputPath, projects) {
31091
31143
  return `outputPath ${outputPath} does not end in .png, which is what a capture is.`;
31092
31144
  }
31093
31145
  const directory = realPathOr(dirname11(outputPath));
31094
- if (!existsSync10(directory)) {
31146
+ if (!existsSync11(directory)) {
31095
31147
  return `outputPath's directory ${dirname11(outputPath)} does not exist; a capture creates no directories.`;
31096
31148
  }
31097
- const where = join16(directory, basename3(outputPath));
31149
+ const where = join17(directory, basename4(outputPath));
31098
31150
  const inside = projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
31099
31151
  if (inside !== undefined) {
31100
31152
  return `outputPath ${outputPath} is inside the project at ${inside}; a capture is saved outside it, so it never becomes part of the game.`;
31101
31153
  }
31102
- if (existsSync10(where)) {
31154
+ if (existsSync11(where)) {
31103
31155
  return `outputPath ${outputPath} already exists; a capture is not written over a file. Name a new one.`;
31104
31156
  }
31105
31157
  return null;
@@ -31477,6 +31529,45 @@ function scanWaitAnswer(scanned) {
31477
31529
  var PLAYING_STATUS_MS = 1000;
31478
31530
  var SCAN_START_MS = 2000;
31479
31531
  var SCAN_SETTLED_MS = 500;
31532
+ var REIMPORT_TIMEOUT_MS = 300000;
31533
+ var AUDIO_BUS_OPS = new Set([
31534
+ "add_audio_bus",
31535
+ "set_audio_bus_effect",
31536
+ "set_audio_bus_volume"
31537
+ ]);
31538
+ function changedSettings(before, after) {
31539
+ if (before === null || after === null) {
31540
+ return null;
31541
+ }
31542
+ const names = new Set([...before.keys(), ...after.keys()]);
31543
+ return [...names].filter((name) => JSON.stringify(before.get(name)) !== JSON.stringify(after.get(name))).sort();
31544
+ }
31545
+ var STALLED_AFTER_MS = 30000;
31546
+ function timedOutVerdict(timeoutMs, silentForMs) {
31547
+ const quiet = Math.round(silentForMs);
31548
+ return quiet >= Math.min(STALLED_AFTER_MS, timeoutMs / 2) ? {
31549
+ verdict: `hung: killed after ${timeoutMs} ms, having printed nothing for the last ${quiet} ms`,
31550
+ hung: true
31551
+ } : {
31552
+ verdict: `timed out after ${timeoutMs} ms while still running: it last printed ${quiet} ms before it was killed, so pass a longer timeoutMs or a narrower path`,
31553
+ hung: false
31554
+ };
31555
+ }
31556
+ function importStatuses(payload) {
31557
+ const listed = payload["resources"];
31558
+ if (!Array.isArray(listed)) {
31559
+ return [];
31560
+ }
31561
+ return listed.map((entry) => {
31562
+ const one = asParams(entry);
31563
+ const reason = readString(one, "reason");
31564
+ return {
31565
+ path: readString(one, "path") ?? "",
31566
+ status: readString(one, "status") ?? "unknown",
31567
+ ...reason === undefined ? {} : { reason }
31568
+ };
31569
+ });
31570
+ }
31480
31571
  var WAIT_FOR_RUN_MS = 600000;
31481
31572
  var RUN_POLL_MS = 250;
31482
31573
  var PATH_SOLUTIONS = [
@@ -31538,7 +31629,7 @@ function aboveTheRunner(entry) {
31538
31629
  }
31539
31630
  function elsewhereIn(projectPath, asked) {
31540
31631
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
31541
- if (wanted === "" || existsSync10(join16(projectPath, wanted))) {
31632
+ if (wanted === "" || existsSync11(join17(projectPath, wanted))) {
31542
31633
  return "";
31543
31634
  }
31544
31635
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
@@ -31552,7 +31643,7 @@ function elsewhereIn(projectPath, asked) {
31552
31643
  return near.length === 0 ? "" : ` The project does have ${there}.`;
31553
31644
  }
31554
31645
  function hasMainScene(projectFile) {
31555
- const scene = parseProjectGodot(readFileSync16(projectFile, "utf8"))["application"]?.["run/main_scene"];
31646
+ const scene = parseProjectGodot(readFileSync17(projectFile, "utf8"))["application"]?.["run/main_scene"];
31556
31647
  return typeof scene === "string" && scene !== "";
31557
31648
  }
31558
31649
  function runtimeAddonAnnouncesEditors(projectPath) {
@@ -31560,13 +31651,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
31560
31651
  return false;
31561
31652
  }
31562
31653
  try {
31563
- return readFileSync16(join16(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
31654
+ return readFileSync17(join17(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
31564
31655
  } catch {
31565
31656
  return false;
31566
31657
  }
31567
31658
  }
31568
31659
  function editorPlaysHeadless(projectFile) {
31569
- const runArgs = parseProjectGodot(readFileSync16(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
31660
+ const runArgs = parseProjectGodot(readFileSync17(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
31570
31661
  return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
31571
31662
  }
31572
31663
  function camelCased(params) {
@@ -31585,7 +31676,7 @@ function needsItsOwnProcess(asked) {
31585
31676
  class GodotServer {
31586
31677
  mcp;
31587
31678
  locator = new GodotLocator;
31588
- operationsScript = join16(__dirname2, "godot", "operations", "godot_operations.gd");
31679
+ operationsScript = join17(__dirname2, "godot", "operations", "godot_operations.gd");
31589
31680
  godotBridge;
31590
31681
  tools = buildToolDefinitions();
31591
31682
  activeProcess = null;
@@ -31871,9 +31962,12 @@ class GodotServer {
31871
31962
  try {
31872
31963
  return await this.dispatch(tool, op, args);
31873
31964
  } catch (error) {
31874
- if (error instanceof McpError || error instanceof Refusal) {
31965
+ if (error instanceof McpError) {
31875
31966
  throw error;
31876
31967
  }
31968
+ if (error instanceof Refusal) {
31969
+ return this.createErrorResponse(error.message);
31970
+ }
31877
31971
  const where = op === "" ? tool : `${tool} op=${op}`;
31878
31972
  console.error(`[SERVER] Unmodelled failure in ${where}:`, error);
31879
31973
  const seenBefore = this.defectsSeen.before(where, errorMessage(error));
@@ -32030,7 +32124,14 @@ class GodotServer {
32030
32124
  }
32031
32125
  async dispatch(tool, op, args) {
32032
32126
  if (ENGINE_PASSES[tool]?.[op] !== undefined) {
32033
- return await this.handleRefreshUids(args);
32127
+ return op === "reimport" ? await this.handleReimport(args) : await this.handleRefreshUids(args);
32128
+ }
32129
+ const settingsWrite = tool === "project_settings" && op !== "get" ? HEADLESS_OPERATIONS[tool]?.[op] : undefined;
32130
+ if (settingsWrite !== undefined) {
32131
+ return await this.handleProjectSettingsWrite(op, settingsWrite, args);
32132
+ }
32133
+ if (tool === "project_import" && op === "set_options") {
32134
+ return await this.handleSetImportOptions(args);
32034
32135
  }
32035
32136
  const headless = HEADLESS_OPERATIONS[tool]?.[op];
32036
32137
  if (headless !== undefined) {
@@ -32146,7 +32247,7 @@ class GodotServer {
32146
32247
  return await bridge("set_theme_font_size", { themePath: args["resourcePath"] });
32147
32248
  }
32148
32249
  case "editor_launch":
32149
- return op === "restart" ? await this.handleRestartEditor() : await this.handleLaunchEditor(args);
32250
+ return op === "restart" ? await this.handleRestartEditor(args) : await this.handleLaunchEditor(args);
32150
32251
  case "editor_run":
32151
32252
  if (op === "stop") {
32152
32253
  return await this.handleStopProject(args);
@@ -32289,8 +32390,8 @@ class GodotServer {
32289
32390
  if (path === undefined) {
32290
32391
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
32291
32392
  }
32292
- const file = join16(path, "project.godot");
32293
- if (!existsSync10(file)) {
32393
+ const file = join17(path, "project.godot");
32394
+ if (!existsSync11(file)) {
32294
32395
  return {
32295
32396
  ok: false,
32296
32397
  response: this.createErrorResponse(`Not a Godot project: ${path}`, [
@@ -32561,6 +32662,222 @@ class GodotServer {
32561
32662
  }
32562
32663
  }, scanned));
32563
32664
  }
32665
+ async handleReimport(args) {
32666
+ const project = this.project(args);
32667
+ if (!project.ok) {
32668
+ return project.response;
32669
+ }
32670
+ const contained = this.containProjectFiles(args);
32671
+ if (!contained.ok) {
32672
+ return contained.response;
32673
+ }
32674
+ const outcome = await this.reimport(project.value.path, readNonEmptyString(contained.value, "resourcePath"), args["force"] === true, readPositiveNumber(args, "timeoutMs") ?? REIMPORT_TIMEOUT_MS);
32675
+ return outcome.ok ? this.jsonTextResponse(outcome.payload) : outcome.response;
32676
+ }
32677
+ editorHasOpen(projectPath) {
32678
+ const open = this.godotBridge.getStatus().projectPath;
32679
+ return this.godotBridge.isConnected() && open !== undefined && open !== "" && isSameDirectory(open, projectPath);
32680
+ }
32681
+ async handleProjectSettingsWrite(op, operation, args) {
32682
+ const project = this.project(args);
32683
+ if (!project.ok) {
32684
+ return project.response;
32685
+ }
32686
+ const projectPath = project.value.path;
32687
+ const served = this.editorHasOpen(projectPath);
32688
+ const before = served ? this.settingKeysOf(projectPath) : null;
32689
+ const written = await this.headless(operation, args);
32690
+ if (!served || written.isError === true) {
32691
+ return written;
32692
+ }
32693
+ const answer = asParams(JSON.parse(written.content[0]?.text ?? "{}"));
32694
+ if (AUDIO_BUS_OPS.has(op)) {
32695
+ return this.jsonTextResponse({ ...answer, ...await this.editorTakes("adopt_audio_bus_layout", {}) });
32696
+ }
32697
+ const changed = changedSettings(before, this.settingKeysOf(projectPath));
32698
+ if (changed === null) {
32699
+ return this.jsonTextResponse({
32700
+ ...answer,
32701
+ editorNote: "project.godot could not be read on both sides of the write, so what it changed is unknown and the open editor was not told: it may still hold the old values and write them back the next time it saves. editor_launch restart loads the file."
32702
+ });
32703
+ }
32704
+ if (changed.length === 0) {
32705
+ return written;
32706
+ }
32707
+ const taken = await this.editorTakes("adopt_project_settings", { settings: changed });
32708
+ return this.jsonTextResponse({
32709
+ ...answer,
32710
+ ...taken,
32711
+ ...taken["editorNote"] === undefined ? { editorAdopted: changed } : {}
32712
+ });
32713
+ }
32714
+ async editorTakes(command, args) {
32715
+ try {
32716
+ await this.godotBridge.invokeTool(command, args);
32717
+ return {};
32718
+ } catch (error) {
32719
+ const message = errorMessage(error);
32720
+ const why = message.includes("Unknown tool") ? "runs an addon from before writes were handed to it" : `could not take the change (${message})`;
32721
+ return {
32722
+ editorNote: `The open editor ${why}, so it still holds the old values, answers them when asked, and writes them back over the file the next time it saves. editor_launch restart loads the file and the addon this server ships.`
32723
+ };
32724
+ }
32725
+ }
32726
+ async handleSetImportOptions(args) {
32727
+ const project = this.project(args);
32728
+ if (!project.ok) {
32729
+ return project.response;
32730
+ }
32731
+ const contained = this.containProjectFiles(args);
32732
+ if (!contained.ok) {
32733
+ return contained.response;
32734
+ }
32735
+ const {
32736
+ op: _op,
32737
+ projectPath: _projectPath,
32738
+ reimport: _reimport,
32739
+ timeoutMs: _timeoutMs,
32740
+ ...params
32741
+ } = contained.value;
32742
+ const written = await this.operation("set_import_options", params, project.value.path);
32743
+ if (!written.ok || args["reimport"] === false) {
32744
+ return this.answer(written.ok ? {
32745
+ ...written,
32746
+ payload: {
32747
+ ...written.payload,
32748
+ note: "Not reimported: the editor imports it with these options on its next scan, or project_import reimport does now."
32749
+ }
32750
+ } : written);
32751
+ }
32752
+ const applied = await this.reimport(project.value.path, readNonEmptyString(contained.value, "resourcePath"), true, readPositiveNumber(args, "timeoutMs") ?? REIMPORT_TIMEOUT_MS);
32753
+ if (!applied.ok) {
32754
+ return applied.response;
32755
+ }
32756
+ return this.jsonTextResponse({ ...written.payload, reimport: applied.payload });
32757
+ }
32758
+ async reimport(projectPath, resourcePath, force, timeoutMs) {
32759
+ const ask = resourcePath === undefined ? { includeUpToDate: force } : { resourcePath };
32760
+ const before = await this.operation("get_import_status", ask, projectPath);
32761
+ if (!before.ok) {
32762
+ return { ok: false, response: this.answer(before) };
32763
+ }
32764
+ const listed = importStatuses(before.payload);
32765
+ const missingSource = listed.filter((one) => one.status === "missing_source").map((one) => one.path);
32766
+ if (resourcePath !== undefined && missingSource.length > 0) {
32767
+ return {
32768
+ ok: false,
32769
+ response: this.createErrorResponse(`${missingSource[0]} is not on disk, so there is nothing to import.`)
32770
+ };
32771
+ }
32772
+ const targets = listed.filter((one) => one.status !== "missing_source" && (force || one.status !== "up_to_date")).map((one) => one.path);
32773
+ if (targets.length === 0) {
32774
+ return {
32775
+ ok: true,
32776
+ payload: {
32777
+ reimported: [],
32778
+ notReimported: [],
32779
+ note: resourcePath === undefined ? "Nothing needed reimporting. Pass force to reimport what is current." : `${listed[0]?.path ?? resourcePath} is current. Pass force to reimport it anyway.`
32780
+ }
32781
+ };
32782
+ }
32783
+ const viaEditor = this.editorHasOpen(projectPath);
32784
+ const extra = {};
32785
+ if (viaEditor) {
32786
+ const inEditor = await this.reimportInTheEditor(targets, timeoutMs);
32787
+ if (!inEditor.ok) {
32788
+ return inEditor;
32789
+ }
32790
+ if (!inEditor.finished) {
32791
+ return {
32792
+ ok: true,
32793
+ payload: {
32794
+ via: "editor",
32795
+ stillImporting: true,
32796
+ asked: targets,
32797
+ note: `The editor had not finished after ${timeoutMs} ms. project_import status says what it has done so far; pass a longer timeoutMs to wait it out.`
32798
+ }
32799
+ };
32800
+ }
32801
+ } else {
32802
+ const engine = await this.engine();
32803
+ if (!engine.ok) {
32804
+ return { ok: false, response: engine.response };
32805
+ }
32806
+ for (const target of targets) {
32807
+ forceNextImport(projectPath, target);
32808
+ }
32809
+ const scanned = await this.untilTheEditorsScanIsWritten(projectPath);
32810
+ const imported = await runImport(engine.value, projectPath);
32811
+ if (!imported.ok) {
32812
+ return { ok: false, response: this.answer(withScanWait(imported, scanned)) };
32813
+ }
32814
+ if (imported.messages.length > 0) {
32815
+ extra["engine_messages"] = imported.messages;
32816
+ }
32817
+ }
32818
+ const after = await this.operation("get_import_status", resourcePath === undefined ? { includeUpToDate: true } : { resourcePath }, projectPath);
32819
+ if (!after.ok) {
32820
+ return { ok: false, response: this.answer(after) };
32821
+ }
32822
+ const now = new Map(importStatuses(after.payload).map((one) => [one.path, one]));
32823
+ const reimported = targets.filter((path) => now.get(path)?.status === "up_to_date");
32824
+ const notReimported = targets.filter((path) => now.get(path)?.status !== "up_to_date").map((path) => ({
32825
+ path,
32826
+ status: now.get(path)?.status ?? "unknown",
32827
+ ...now.get(path)?.reason === undefined ? {} : { reason: now.get(path)?.reason }
32828
+ }));
32829
+ return {
32830
+ ok: true,
32831
+ payload: {
32832
+ via: viaEditor ? "editor" : "engine",
32833
+ reimported,
32834
+ notReimported,
32835
+ ...missingSource.length > 0 ? { missingSource } : {},
32836
+ ...extra
32837
+ }
32838
+ };
32839
+ }
32840
+ async reimportInTheEditor(paths, timeoutMs) {
32841
+ const started = Date.now();
32842
+ let completedBefore;
32843
+ for (;; ) {
32844
+ while (Date.now() - started < timeoutMs && await this.editorIsScanningOrImporting()) {
32845
+ await new Promise((settle) => setTimeout(settle, 100));
32846
+ }
32847
+ let answer;
32848
+ try {
32849
+ answer = asParams(await this.godotBridge.invokeTool("reimport_files", { paths: [...paths] }));
32850
+ } catch (error) {
32851
+ const message = errorMessage(error);
32852
+ return {
32853
+ ok: false,
32854
+ response: message.includes("Unknown tool") ? this.createErrorResponse("The editor is running an addon from before reimport_files, so it cannot be asked to reimport.", [
32855
+ "editor_launch restart loads the addon this server ships",
32856
+ "Or close the editor, and project_import reimport runs the engine import instead"
32857
+ ]) : this.createErrorResponse(`The editor answered reimport_files with an error: ${message}`)
32858
+ };
32859
+ }
32860
+ if (answer["started"] !== false) {
32861
+ completedBefore = readNumber(answer, "reimportsCompletedBefore");
32862
+ break;
32863
+ }
32864
+ if (Date.now() - started >= timeoutMs) {
32865
+ return { ok: true, finished: false };
32866
+ }
32867
+ await new Promise((settle) => setTimeout(settle, 100));
32868
+ }
32869
+ while (Date.now() - started < timeoutMs) {
32870
+ await new Promise((settle) => setTimeout(settle, 250));
32871
+ try {
32872
+ const status = asParams(await this.godotBridge.invokeTool("scan_status", {}));
32873
+ const completed = readNumber(status, "reimportsCompleted");
32874
+ if (completedBefore !== undefined && completed !== undefined && completed > completedBefore) {
32875
+ return { ok: true, finished: true };
32876
+ }
32877
+ } catch {}
32878
+ }
32879
+ return { ok: true, finished: false };
32880
+ }
32564
32881
  async operation(operation, params, projectPath) {
32565
32882
  const engine = await this.engine();
32566
32883
  if (!engine.ok) {
@@ -32602,11 +32919,11 @@ class GodotServer {
32602
32919
  if (!engine.ok) {
32603
32920
  return engine.response;
32604
32921
  }
32605
- const application = parseProjectGodot(readFileSync16(project.value.file, "utf8"))["application"] ?? {};
32922
+ const application = parseProjectGodot(readFileSync17(project.value.file, "utf8"))["application"] ?? {};
32606
32923
  const name = application["config/name"];
32607
32924
  const mainScene = application["run/main_scene"];
32608
32925
  const info = {
32609
- name: typeof name === "string" && name !== "" ? name : basename3(project.value.path),
32926
+ name: typeof name === "string" && name !== "" ? name : basename4(project.value.path),
32610
32927
  path: project.value.path,
32611
32928
  mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
32612
32929
  godotVersion: await this.godotVersion(engine.value),
@@ -32670,11 +32987,11 @@ class GodotServer {
32670
32987
  return this.createErrorResponse(`Cannot create ${dirname11(output.relativePath)} to export into: ${errorMessage(error)}`);
32671
32988
  }
32672
32989
  const debug = readBoolean(args, "debug") ?? false;
32673
- const exportLogs = mkdtempSync2(join16(tmpdir4(), "gdharness-export-"));
32990
+ const exportLogs = mkdtempSync2(join17(tmpdir4(), "gdharness-export-"));
32674
32991
  const exportArgs = [
32675
32992
  "--headless",
32676
32993
  "--log-file",
32677
- join16(exportLogs, "engine.log"),
32994
+ join17(exportLogs, "engine.log"),
32678
32995
  "--path",
32679
32996
  project.value.path,
32680
32997
  debug ? "--export-debug" : "--export-release",
@@ -32706,7 +33023,7 @@ class GodotServer {
32706
33023
  log.finish();
32707
33024
  const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
32708
33025
  const verdict = {
32709
- exported: ending.exitCode === 0 && log.count("error") === 0 && existsSync10(output.absolutePath),
33026
+ exported: ending.exitCode === 0 && log.count("error") === 0 && existsSync11(output.absolutePath),
32710
33027
  preset,
32711
33028
  outputPath: output.relativePath,
32712
33029
  debug,
@@ -32742,7 +33059,7 @@ class GodotServer {
32742
33059
  return contained.response;
32743
33060
  }
32744
33061
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
32745
- if (!existsSync10(join16(project.value.path, runner))) {
33062
+ if (!existsSync11(join17(project.value.path, runner))) {
32746
33063
  return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
32747
33064
  "Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
32748
33065
  ]);
@@ -32778,14 +33095,21 @@ class GodotServer {
32778
33095
  ];
32779
33096
  const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
32780
33097
  this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
32781
- const userData = mkdtempSync2(join16(tmpdir4(), "gdharness-tests-"));
33098
+ const userData = mkdtempSync2(join17(tmpdir4(), "gdharness-tests-"));
32782
33099
  const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
32783
33100
  const console2 = [];
33101
+ let lastSaid = Date.now();
32784
33102
  run.process.stdout?.on("data", (data) => {
32785
33103
  console2.push(data);
33104
+ lastSaid = Date.now();
32786
33105
  });
32787
- const hung = await new Promise((resolve) => {
33106
+ run.process.stderr?.on("data", () => {
33107
+ lastSaid = Date.now();
33108
+ });
33109
+ let silentForMs = 0;
33110
+ const timedOut = await new Promise((resolve) => {
32788
33111
  const timer = setTimeout(() => {
33112
+ silentForMs = Date.now() - lastSaid;
32789
33113
  run.process.kill();
32790
33114
  resolve(true);
32791
33115
  }, timeoutMs);
@@ -32798,14 +33122,14 @@ class GodotServer {
32798
33122
  resolve(false);
32799
33123
  });
32800
33124
  });
32801
- const reportsDir = join16(project.value.path, ".godot", ...ours.split("/"));
33125
+ const reportsDir = join17(project.value.path, ".godot", ...ours.split("/"));
32802
33126
  let report = null;
32803
33127
  let reportProblem = null;
32804
33128
  try {
32805
- const written = existsSync10(reportsDir) ? readdirSync7(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
33129
+ const written = existsSync11(reportsDir) ? readdirSync7(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
32806
33130
  const newest = written.at(-1);
32807
33131
  if (newest !== undefined) {
32808
- report = parseJUnit(readFileSync16(join16(reportsDir, newest, "results.xml"), "utf8"));
33132
+ report = parseJUnit(readFileSync17(join17(reportsDir, newest, "results.xml"), "utf8"));
32809
33133
  }
32810
33134
  } catch (error) {
32811
33135
  reportProblem = errorMessage(error);
@@ -32827,8 +33151,10 @@ class GodotServer {
32827
33151
  const exitCode = run.exitCode;
32828
33152
  const printed = run.log.select({ severity: "info", sinceLastCall: false, limit: 200 }).entries;
32829
33153
  const said = printed.map((entry) => entry.text);
32830
- const nothingRan = hung ? null : whyNoReport(said, asked);
32831
- const verdict = hung ? `hung: killed after ${timeoutMs} ms` : nothingRan ?? (exitCode === null ? undefined : verdicts[exitCode]) ?? (run.exitSignal === null ? `exit ${exitCode ?? "unknown"}` : howItExited(run));
33154
+ const nothingRan = timedOut ? null : whyNoReport(said, asked);
33155
+ const cutShort = timedOut ? timedOutVerdict(timeoutMs, silentForMs) : null;
33156
+ const hung = cutShort?.hung ?? false;
33157
+ const verdict = cutShort !== null ? cutShort.verdict : nothingRan ?? (exitCode === null ? undefined : verdicts[exitCode]) ?? (run.exitSignal === null ? `exit ${exitCode ?? "unknown"}` : howItExited(run));
32832
33158
  const savesNote = savesStayPut() && nothingRan === null ? SAVES_NOT_MOVED_NOTE : undefined;
32833
33159
  if (report === null) {
32834
33160
  const note = nothingRan === null ? `The test run wrote no report (${verdict}${reportProblem ? `; ${reportProblem}` : ""}).` : `No tests ran: ${verdict}.${elsewhereIn(project.value.path, asked)}`;
@@ -32844,6 +33170,7 @@ class GodotServer {
32844
33170
  exitCode,
32845
33171
  exitSignal: run.exitSignal ?? undefined,
32846
33172
  hung,
33173
+ ...timedOut ? { timedOut, silentForMs } : {},
32847
33174
  arguments: cmdArgs,
32848
33175
  entries: forAnswer(printed.slice(0, 60)),
32849
33176
  savesNote,
@@ -32868,8 +33195,9 @@ class GodotServer {
32868
33195
  const unclean = report.suites.filter((suite) => suite.failures > 0 || suite.errors > 0 || suite.skipped > 0 || orphansIn(suite.path) > 0);
32869
33196
  const notRun = report.suites.reduce((sum, suite) => sum + Math.max(0, suite.discovered - suite.tests), 0);
32870
33197
  return this.jsonTextResponse({
32871
- passed: !hung && exitCode === 0 && report.failures === 0 && report.errors === 0,
33198
+ passed: !timedOut && exitCode === 0 && report.failures === 0 && report.errors === 0,
32872
33199
  verdict,
33200
+ ...timedOut ? { timedOut, hung, silentForMs } : {},
32873
33201
  exitCode,
32874
33202
  exitSignal: run.exitSignal ?? undefined,
32875
33203
  tests: report.tests,
@@ -32948,11 +33276,11 @@ class GodotServer {
32948
33276
  const cached = cachedClasses(projectPath);
32949
33277
  const contradicted = cached === null ? [] : contradictedDiagnostics(messages, cached, (resourcePath) => {
32950
33278
  const contained = resolveWithinProject(projectPath, resourcePath);
32951
- if (!contained.ok || !existsSync10(contained.absolutePath)) {
33279
+ if (!contained.ok || !existsSync11(contained.absolutePath)) {
32952
33280
  return null;
32953
33281
  }
32954
33282
  try {
32955
- return readFileSync16(contained.absolutePath, "utf8");
33283
+ return readFileSync17(contained.absolutePath, "utf8");
32956
33284
  } catch {
32957
33285
  return null;
32958
33286
  }
@@ -33002,11 +33330,11 @@ class GodotServer {
33002
33330
  }
33003
33331
  sourceOfClass(projectPath, resourcePath) {
33004
33332
  const contained = resolveWithinProject(projectPath, resourcePath);
33005
- if (!contained.ok || !existsSync10(contained.absolutePath)) {
33333
+ if (!contained.ok || !existsSync11(contained.absolutePath)) {
33006
33334
  return null;
33007
33335
  }
33008
33336
  try {
33009
- return readFileSync16(contained.absolutePath, "utf8");
33337
+ return readFileSync17(contained.absolutePath, "utf8");
33010
33338
  } catch {
33011
33339
  return null;
33012
33340
  }
@@ -33275,12 +33603,12 @@ class GodotServer {
33275
33603
  return null;
33276
33604
  }
33277
33605
  try {
33278
- return settingKeys(readFileSync16(join16(projectPath, "project.godot"), "utf8"));
33606
+ return settingKeys(readFileSync17(join17(projectPath, "project.godot"), "utf8"));
33279
33607
  } catch {
33280
33608
  return null;
33281
33609
  }
33282
33610
  }
33283
- async handleRestartEditor() {
33611
+ async handleRestartEditor(args) {
33284
33612
  const before = this.godotBridge.getStatus();
33285
33613
  if (!before.connected) {
33286
33614
  return this.createErrorResponse("No editor is connected, so there is nothing to restart.", [
@@ -33290,7 +33618,7 @@ class GodotServer {
33290
33618
  }
33291
33619
  const settingsBefore = this.settingKeysOf(before.projectPath);
33292
33620
  const mine = before.openedByAServer === true && before.projectPath !== undefined;
33293
- const asked = mine ? await this.startItAgain(before.projectPath ?? "", before.editorPid) : await this.handleViaBridge("restart_editor", {});
33621
+ const asked = mine ? await this.startItAgain(before.projectPath ?? "", before.editorPid, args["hidden"] === true) : await this.handleViaBridge("restart_editor", {});
33294
33622
  if (asked.isError === true) {
33295
33623
  return asked;
33296
33624
  }
@@ -33328,7 +33656,7 @@ class GodotServer {
33328
33656
  whatTheEditorDropped(before, after) {
33329
33657
  return settingsDroppedReport(before, after);
33330
33658
  }
33331
- async startItAgain(projectPath, editorPid) {
33659
+ async startItAgain(projectPath, editorPid, hidden) {
33332
33660
  const engine = await this.engine();
33333
33661
  if (!engine.ok) {
33334
33662
  return engine.response;
@@ -33350,7 +33678,7 @@ class GodotServer {
33350
33678
  return asked;
33351
33679
  }
33352
33680
  await this.waitForBridge(() => !alive(editorPid), Date.now() + EDITOR_RESTART_TIMEOUT_MS);
33353
- const opened = await this.openAnEditor(engine.value, projectPath, ports);
33681
+ const opened = await this.openAnEditor(engine.value, projectPath, ports, hidden);
33354
33682
  restartSettled(projectPath);
33355
33683
  if (opened.error !== null) {
33356
33684
  return this.createErrorResponse(`The editor was asked to go and could not be started again: ${opened.error}`, [`editor_launch opens one on ${projectPath}`]);
@@ -33388,7 +33716,7 @@ class GodotServer {
33388
33716
  const ports = await this.portsForAnEditor();
33389
33717
  const before = this.settingKeysOf(project.value.path);
33390
33718
  const unfinished = restartOwed(project.value.path);
33391
- const opened = await this.openAnEditor(engine.value, project.value.path, ports);
33719
+ const opened = await this.openAnEditor(engine.value, project.value.path, ports, args["hidden"] === true);
33392
33720
  if (opened.error !== null) {
33393
33721
  return this.createErrorResponse(`Could not start the editor: ${opened.error}`);
33394
33722
  }
@@ -33404,18 +33732,19 @@ class GodotServer {
33404
33732
  settingsNote: before === null ? "project.godot could not be read before this editor was opened, so nothing here will be able to say what the import saved away. Opening a project rewrites the file and drops any key sitting at its own default." : before.size === 0 ? undefined : "Opening a project imports it and saves project.godot, and Godot drops any key sitting at its own default. editor_status names anything lost under settingsDropped once this editor has connected."
33405
33733
  });
33406
33734
  }
33407
- async openAnEditor(engine, projectPath, ports) {
33735
+ async openAnEditor(engine, projectPath, ports, hidden) {
33408
33736
  clearEditorLog(projectPath);
33409
33737
  const launched = await launchOutsideTheTree({
33410
33738
  command: engine,
33411
- args: editorArguments(projectPath, ports, editorLogPath(projectPath)),
33739
+ args: editorArguments(projectPath, ports, editorLogPath(projectPath), hidden),
33412
33740
  env: {
33413
33741
  ...process.env,
33414
33742
  GDHARNESS_RUNTIME_DIR: runtimeDirectory(),
33415
33743
  GDHARNESS_LSP_PORT: String(ports.lsp),
33416
33744
  GDHARNESS_DAP_PORT: String(ports.dap),
33417
33745
  [OPENED_BY_A_SERVER]: "1"
33418
- }
33746
+ },
33747
+ ...hidden ? { desktop: HIDDEN_DESKTOP } : { noActivate: true }
33419
33748
  });
33420
33749
  if ("error" in launched) {
33421
33750
  return { pid: null, error: launched.error };
@@ -33497,7 +33826,7 @@ class GodotServer {
33497
33826
  userArgs: given.value
33498
33827
  });
33499
33828
  this.logDebug(`Running Godot project: ${engine.value} ${cmdArgs.join(" ")}`);
33500
- const started = await this.launchKeptGame(engine.value, cmdArgs, project.value.path, needsItsOwnProcess(asked.value) ? environmentFor({ ...asked.value, runtimeDirectory: runtimeDirectory() }) : undefined);
33829
+ const started = await this.launchKeptGame(engine.value, cmdArgs, project.value.path, needsItsOwnProcess(asked.value) ? environmentFor({ ...asked.value, runtimeDirectory: runtimeDirectory() }) : undefined, headless || args["visible"] === true ? undefined : HIDDEN_DESKTOP);
33501
33830
  if ("error" in started) {
33502
33831
  return this.createErrorResponse(`The game could not be started: ${started.error}`, [
33503
33832
  "editor_status says which Godot this server runs and whether it answers"
@@ -33527,7 +33856,7 @@ class GodotServer {
33527
33856
  }
33528
33857
  async runtimeUp(projectPath, before, budget, withArgs) {
33529
33858
  const budgetMs = budget.ms;
33530
- if (!existsSync10(join16(projectPath, RUNTIME_AUTOLOAD.path))) {
33859
+ if (!existsSync11(join17(projectPath, RUNTIME_AUTOLOAD.path))) {
33531
33860
  return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
33532
33861
  }
33533
33862
  let asking = null;
@@ -33590,7 +33919,7 @@ class GodotServer {
33590
33919
  }
33591
33920
  let announcedAt;
33592
33921
  try {
33593
- announcedAt = statSync7(endpoint.file).mtimeMs;
33922
+ announcedAt = statSync8(endpoint.file).mtimeMs;
33594
33923
  } catch {
33595
33924
  return;
33596
33925
  }
@@ -33837,7 +34166,7 @@ class GodotServer {
33837
34166
  });
33838
34167
  return started;
33839
34168
  }
33840
- async launchKeptGame(godotPath, cmdArgs, projectPath, env) {
34169
+ async launchKeptGame(godotPath, cmdArgs, projectPath, env, desktop) {
33841
34170
  const startedAt = Date.now();
33842
34171
  const transcript = openTranscript(startedAt);
33843
34172
  closeSync2(transcript.fd);
@@ -33845,7 +34174,8 @@ class GodotServer {
33845
34174
  command: godotPath,
33846
34175
  args: cmdArgs,
33847
34176
  ...env === undefined ? {} : { env },
33848
- run: { transcript: transcript.path, startedAt, projectPath }
34177
+ run: { transcript: transcript.path, startedAt, projectPath },
34178
+ ...desktop === undefined ? {} : { desktop }
33849
34179
  });
33850
34180
  if ("error" in launched) {
33851
34181
  return launched;
@@ -34340,7 +34670,7 @@ class GodotServer {
34340
34670
  notes.push(endedWithoutACode({
34341
34671
  throughEditor: run.throughEditor,
34342
34672
  announced: announced !== undefined || run.announcedPid !== undefined,
34343
- couldAnnounce: run.projectPath !== null && existsSync10(join16(run.projectPath, RUNTIME_AUTOLOAD.path)),
34673
+ couldAnnounce: run.projectPath !== null && existsSync11(join17(run.projectPath, RUNTIME_AUTOLOAD.path)),
34344
34674
  endedHere: typeof run.endedHere === "string",
34345
34675
  pickedUp: run.pickedUp === true
34346
34676
  }));
@@ -34853,7 +35183,7 @@ class GodotServer {
34853
35183
  async ownRunOnItsWay(projectPath, budgetMs) {
34854
35184
  const run = this.currentRun();
34855
35185
  const project = projectPath ?? run?.projectPath ?? null;
34856
- if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync10(join16(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
35186
+ if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync11(join17(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
34857
35187
  return null;
34858
35188
  }
34859
35189
  const already = runtimesAnnounced().running;
@@ -34862,7 +35192,7 @@ class GodotServer {
34862
35192
  return !run.announcedBefore.has(one.pid);
34863
35193
  }
34864
35194
  try {
34865
- return statSync7(one.file).mtimeMs >= run.startedAt;
35195
+ return statSync8(one.file).mtimeMs >= run.startedAt;
34866
35196
  } catch {
34867
35197
  return false;
34868
35198
  }
@@ -34932,8 +35262,8 @@ class GodotServer {
34932
35262
  return this.createErrorResponse(refusal);
34933
35263
  }
34934
35264
  }
34935
- const screenshotDir = expectsScreenshot ? mkdtempSync2(join16(tmpdir4(), "gdharness-runtime-screenshot-")) : null;
34936
- const screenshotPath = screenshotDir ? join16(screenshotDir, "capture.png") : null;
35265
+ const screenshotDir = expectsScreenshot ? mkdtempSync2(join17(tmpdir4(), "gdharness-runtime-screenshot-")) : null;
35266
+ const screenshotPath = screenshotDir ? join17(screenshotDir, "capture.png") : null;
34937
35267
  try {
34938
35268
  const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, !expectsScreenshot);
34939
35269
  if (!reply.ok && reply.requestId !== undefined) {
@@ -34974,7 +35304,7 @@ class GodotServer {
34974
35304
  type: "text",
34975
35305
  text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}${saveTo === null ? "" : `. Saved to ${saveTo}`}`
34976
35306
  },
34977
- { type: "image", data: readFileSync16(screenshotPath).toString("base64"), mimeType: "image/png" }
35307
+ { type: "image", data: readFileSync17(screenshotPath).toString("base64"), mimeType: "image/png" }
34978
35308
  ]
34979
35309
  };
34980
35310
  } finally {