gdharness 0.5.9 → 0.5.10

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.
Files changed (3) hide show
  1. package/build/cli.js +132 -60
  2. package/build/index.js +109 -44
  3. package/package.json +1 -1
package/build/cli.js CHANGED
@@ -7229,6 +7229,16 @@ class GameLog {
7229
7229
  this.entries.push(entry);
7230
7230
  this.lastHeadline = headline ? { index: entry.index, detail } : null;
7231
7231
  }
7232
+ record(severity, text) {
7233
+ this.entries.push({
7234
+ index: this.entries.length,
7235
+ severity,
7236
+ source: "debugger",
7237
+ text,
7238
+ detail: []
7239
+ });
7240
+ this.lastHeadline = null;
7241
+ }
7232
7242
  get all() {
7233
7243
  return this.entries;
7234
7244
  }
@@ -30785,6 +30795,10 @@ var init_ports = __esm(() => {
30785
30795
  // src/dap_client.ts
30786
30796
  import { createConnection } from "node:net";
30787
30797
  import { setTimeout as delay } from "node:timers/promises";
30798
+ function said(body, field) {
30799
+ const value = body?.[field];
30800
+ return typeof value === "string" ? value : "";
30801
+ }
30788
30802
 
30789
30803
  class GodotDAPClient {
30790
30804
  socket = null;
@@ -30799,7 +30813,7 @@ class GodotDAPClient {
30799
30813
  initialized = false;
30800
30814
  attached = false;
30801
30815
  lastThreadId = 1;
30802
- stopped = false;
30816
+ halt = null;
30803
30817
  breakpoints = new Map;
30804
30818
  constructor(port = portFromEnv("GDHARNESS_DAP_PORT", DEFAULT_DAP_PORT), host = "127.0.0.1") {
30805
30819
  this.port = port;
@@ -30848,7 +30862,7 @@ class GodotDAPClient {
30848
30862
  this.connected = false;
30849
30863
  this.initialized = false;
30850
30864
  this.attached = false;
30851
- this.stopped = false;
30865
+ this.halt = null;
30852
30866
  this.socket = null;
30853
30867
  this.failPendingRequests(new Error("DAP connection closed"));
30854
30868
  });
@@ -30868,7 +30882,7 @@ class GodotDAPClient {
30868
30882
  this.connected = false;
30869
30883
  this.initialized = false;
30870
30884
  this.attached = false;
30871
- this.stopped = false;
30885
+ this.halt = null;
30872
30886
  return;
30873
30887
  }
30874
30888
  if (this.connected) {
@@ -30897,7 +30911,7 @@ class GodotDAPClient {
30897
30911
  this.connected = false;
30898
30912
  this.initialized = false;
30899
30913
  this.attached = false;
30900
- this.stopped = false;
30914
+ this.halt = null;
30901
30915
  }
30902
30916
  async ensureConnected() {
30903
30917
  if (!this.connected) {
@@ -30974,12 +30988,16 @@ class GodotDAPClient {
30974
30988
  if (typeof threadId === "number") {
30975
30989
  this.lastThreadId = threadId;
30976
30990
  }
30977
- this.stopped = true;
30991
+ this.halt = {
30992
+ reason: said(body, "reason"),
30993
+ description: said(body, "description"),
30994
+ text: said(body, "text")
30995
+ };
30978
30996
  return;
30979
30997
  }
30980
30998
  if (eventName === "terminated" || eventName === "exited") {
30981
30999
  this.attached = false;
30982
- this.stopped = false;
31000
+ this.halt = null;
30983
31001
  }
30984
31002
  }
30985
31003
  async initialize() {
@@ -31053,7 +31071,7 @@ class GodotDAPClient {
31053
31071
  await this.attach();
31054
31072
  const resolvedThreadId = await this.resolveThreadId(threadId);
31055
31073
  await this.sendRequest("continue", { threadId: resolvedThreadId });
31056
- this.stopped = false;
31074
+ this.halt = null;
31057
31075
  }
31058
31076
  async stepOver(threadId) {
31059
31077
  await this.attach();
@@ -31124,7 +31142,10 @@ class GodotDAPClient {
31124
31142
  return this.connected;
31125
31143
  }
31126
31144
  isStopped() {
31127
- return this.stopped;
31145
+ return this.halt !== null;
31146
+ }
31147
+ whereItStopped() {
31148
+ return this.halt;
31128
31149
  }
31129
31150
  async resolveThreadId(threadId) {
31130
31151
  if (typeof threadId === "number" && threadId > 0) {
@@ -31153,7 +31174,7 @@ class GodotDAPClient {
31153
31174
  this.connected = false;
31154
31175
  this.initialized = false;
31155
31176
  this.attached = false;
31156
- this.stopped = false;
31177
+ this.halt = null;
31157
31178
  this.reader = new FrameReader;
31158
31179
  socket?.destroy();
31159
31180
  this.failPendingRequests(new Error(`Godot DAP ${detail}. The connection was dropped.`));
@@ -35741,11 +35762,31 @@ var init_runtime_client = __esm(() => {
35741
35762
  ANNOUNCEMENT_PATTERN = /^runtime-(\d+)\.json$/;
35742
35763
  });
35743
35764
 
35765
+ // src/setup.ts
35766
+ import { cpSync as cpSync2, existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync11, rmSync as rmSync7, writeFileSync as writeFileSync8 } from "node:fs";
35767
+ import { dirname as dirname8, join as join14 } from "node:path";
35768
+ function installedAddonVersion(projectPath) {
35769
+ const marker = join14(projectPath, "addons", ADDONS2[0], VERSION_MARKER2);
35770
+ if (!existsSync11(marker)) {
35771
+ return null;
35772
+ }
35773
+ const said = readFileSync11(marker, "utf8").trim();
35774
+ return said === "" ? null : said;
35775
+ }
35776
+ var ADDONS2, VERSION_MARKER2 = ".gdharness-version";
35777
+ var init_setup = __esm(() => {
35778
+ init_class_cache();
35779
+ init_headless();
35780
+ init_resources();
35781
+ init_server_version();
35782
+ ADDONS2 = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
35783
+ });
35784
+
35744
35785
  // src/server.ts
35745
35786
  import { execFile as execFile5, spawn } from "node:child_process";
35746
- import { existsSync as existsSync11, mkdtempSync as mkdtempSync3, readdirSync as readdirSync6, readFileSync as readFileSync11, realpathSync as realpathSync2, rmSync as rmSync7 } from "node:fs";
35787
+ import { existsSync as existsSync12, mkdtempSync as mkdtempSync3, readdirSync as readdirSync6, readFileSync as readFileSync12, realpathSync as realpathSync2, rmSync as rmSync8 } from "node:fs";
35747
35788
  import { tmpdir as tmpdir5 } from "node:os";
35748
- import { basename as basename2, dirname as dirname8, join as join14, normalize as normalize3 } from "node:path";
35789
+ import { basename as basename2, dirname as dirname9, join as join15, normalize as normalize3 } from "node:path";
35749
35790
  import { setTimeout as delay2 } from "node:timers/promises";
35750
35791
  import { fileURLToPath as fileURLToPath4 } from "node:url";
35751
35792
  import { promisify as promisify5 } from "node:util";
@@ -35771,11 +35812,11 @@ function realPathOr(path) {
35771
35812
  }
35772
35813
  }
35773
35814
  function hasMainScene(projectFile) {
35774
- const scene = parseProjectGodot(readFileSync11(projectFile, "utf8"))["application"]?.["run/main_scene"];
35815
+ const scene = parseProjectGodot(readFileSync12(projectFile, "utf8"))["application"]?.["run/main_scene"];
35775
35816
  return typeof scene === "string" && scene !== "";
35776
35817
  }
35777
35818
  function editorPlaysHeadless(projectFile) {
35778
- const runArgs = parseProjectGodot(readFileSync11(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
35819
+ const runArgs = parseProjectGodot(readFileSync12(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
35779
35820
  return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
35780
35821
  }
35781
35822
  function camelCased(params) {
@@ -35791,7 +35832,7 @@ function camelCased(params) {
35791
35832
  class GodotServer {
35792
35833
  mcp;
35793
35834
  locator = new GodotLocator2;
35794
- operationsScript = join14(__dirname2, "godot", "operations", "godot_operations.gd");
35835
+ operationsScript = join15(__dirname2, "godot", "operations", "godot_operations.gd");
35795
35836
  godotBridge;
35796
35837
  tools = buildToolDefinitions();
35797
35838
  activeProcess = null;
@@ -36029,28 +36070,44 @@ class GodotServer {
36029
36070
  if (this.callsSinceNotice < UPDATE_NOTICE_EVERY && this.noticedUpdate) {
36030
36071
  return answer;
36031
36072
  }
36073
+ const moved = this.projectHasMovedOn();
36074
+ if (moved !== null) {
36075
+ this.noticedUpdate = true;
36076
+ this.callsSinceNotice = 0;
36077
+ return this.saying(answer, {
36078
+ project_upgraded_under_this_server: {
36079
+ server_is: SERVER_VERSION,
36080
+ project_is: moved,
36081
+ what_to_do: "The project was upgraded while this server has been running, so it is still answering " + "as the version it was started as. Tell the user to reconnect the MCP server, which is " + "the only thing that replaces it: a server cannot restart itself."
36082
+ }
36083
+ });
36084
+ }
36032
36085
  const notice = this.updates.notice();
36033
36086
  if (notice === null) {
36034
36087
  return answer;
36035
36088
  }
36036
36089
  this.noticedUpdate = true;
36037
36090
  this.callsSinceNotice = 0;
36091
+ return this.saying(answer, {
36092
+ update_available: {
36093
+ ...notice,
36094
+ what_to_do: "Tell the user a newer gdharness is out, with what changed, and offer to take it. " + "Only run the upgrade command if they say yes: it restarts their editor and the " + "MCP server has to be reconnected afterwards."
36095
+ }
36096
+ });
36097
+ }
36098
+ saying(answer, block) {
36038
36099
  return {
36039
36100
  ...answer,
36040
- content: [
36041
- ...answer.content,
36042
- {
36043
- type: "text",
36044
- text: JSON.stringify({
36045
- update_available: {
36046
- ...notice,
36047
- what_to_do: "Tell the user a newer gdharness is out, with what changed, and offer to take it. " + "Only run the upgrade command if they say yes: it restarts their editor and the " + "MCP server has to be reconnected afterwards."
36048
- }
36049
- }, null, 2)
36050
- }
36051
- ]
36101
+ content: [...answer.content, { type: "text", text: JSON.stringify(block, null, 2) }]
36052
36102
  };
36053
36103
  }
36104
+ projectHasMovedOn() {
36105
+ if (this.ownProject === null) {
36106
+ return null;
36107
+ }
36108
+ const installed = installedAddonVersion(this.ownProject);
36109
+ return installed === null || installed === SERVER_VERSION ? null : installed;
36110
+ }
36054
36111
  withFeedbackNotice(answer) {
36055
36112
  this.callsSinceFeedback += 1;
36056
36113
  if (this.callsSinceFeedback < FEEDBACK_NOTICE_EVERY) {
@@ -36296,8 +36353,8 @@ class GodotServer {
36296
36353
  if (path === undefined) {
36297
36354
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
36298
36355
  }
36299
- const file = join14(path, "project.godot");
36300
- if (!existsSync11(file)) {
36356
+ const file = join15(path, "project.godot");
36357
+ if (!existsSync12(file)) {
36301
36358
  return {
36302
36359
  ok: false,
36303
36360
  response: this.createErrorResponse(`Not a Godot project: ${path}`, [
@@ -36442,7 +36499,7 @@ class GodotServer {
36442
36499
  if (!engine.ok) {
36443
36500
  return engine.response;
36444
36501
  }
36445
- const application = parseProjectGodot(readFileSync11(project.value.file, "utf8"))["application"] ?? {};
36502
+ const application = parseProjectGodot(readFileSync12(project.value.file, "utf8"))["application"] ?? {};
36446
36503
  const name = application["config/name"];
36447
36504
  const mainScene = application["run/main_scene"];
36448
36505
  const info = {
@@ -36531,7 +36588,7 @@ class GodotServer {
36531
36588
  log.finish();
36532
36589
  const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
36533
36590
  const verdict = {
36534
- exported: exitCode === 0 && log.count("error") === 0 && existsSync11(output.absolutePath),
36591
+ exported: exitCode === 0 && log.count("error") === 0 && existsSync12(output.absolutePath),
36535
36592
  preset,
36536
36593
  outputPath: output.relativePath,
36537
36594
  debug,
@@ -36561,7 +36618,7 @@ class GodotServer {
36561
36618
  return contained.response;
36562
36619
  }
36563
36620
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
36564
- if (!existsSync11(join14(project.value.path, runner))) {
36621
+ if (!existsSync12(join15(project.value.path, runner))) {
36565
36622
  return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
36566
36623
  "Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
36567
36624
  ]);
@@ -36594,7 +36651,7 @@ class GodotServer {
36594
36651
  ];
36595
36652
  const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
36596
36653
  this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
36597
- const userData = mkdtempSync3(join14(tmpdir5(), "gdharness-tests-"));
36654
+ const userData = mkdtempSync3(join15(tmpdir5(), "gdharness-tests-"));
36598
36655
  const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
36599
36656
  const hung = await new Promise((resolve) => {
36600
36657
  const timer = setTimeout(() => {
@@ -36610,20 +36667,20 @@ class GodotServer {
36610
36667
  resolve(false);
36611
36668
  });
36612
36669
  });
36613
- const reportsDir = join14(project.value.path, ".godot", "gdharness-reports");
36670
+ const reportsDir = join15(project.value.path, ".godot", "gdharness-reports");
36614
36671
  let report = null;
36615
36672
  let reportProblem = null;
36616
36673
  try {
36617
- const written = existsSync11(reportsDir) ? readdirSync6(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
36674
+ const written = existsSync12(reportsDir) ? readdirSync6(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
36618
36675
  const newest = written.at(-1);
36619
36676
  if (newest !== undefined) {
36620
- report = parseJUnit(readFileSync11(join14(reportsDir, newest, "results.xml"), "utf8"));
36677
+ report = parseJUnit(readFileSync12(join15(reportsDir, newest, "results.xml"), "utf8"));
36621
36678
  }
36622
36679
  } catch (error) {
36623
36680
  reportProblem = errorMessage(error);
36624
36681
  } finally {
36625
- rmSync7(reportsDir, { recursive: true, force: true });
36626
- rmSync7(userData, { recursive: true, force: true });
36682
+ rmSync8(reportsDir, { recursive: true, force: true });
36683
+ rmSync8(userData, { recursive: true, force: true });
36627
36684
  }
36628
36685
  const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries;
36629
36686
  const verdicts = {
@@ -36737,6 +36794,7 @@ class GodotServer {
36737
36794
  staleNote: stale ? addonMismatch(status.addonVersion, SERVER_VERSION) : undefined,
36738
36795
  retryingBridge: this.bridgeRetry === null ? undefined : true,
36739
36796
  announcedAt: this.announcedAt ?? undefined,
36797
+ projectIs: this.projectHasMovedOn() ?? undefined,
36740
36798
  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,
36741
36799
  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
36742
36800
  };
@@ -36949,7 +37007,8 @@ class GodotServer {
36949
37007
  log,
36950
37008
  startedAt: Date.now(),
36951
37009
  exitCode: null,
36952
- throughEditor: true
37010
+ throughEditor: true,
37011
+ brokeOn: null
36953
37012
  };
36954
37013
  this.activeProcess = played;
36955
37014
  return this.jsonTextResponse({
@@ -36969,6 +37028,15 @@ class GodotServer {
36969
37028
  `) ? line : `${line}
36970
37029
  `);
36971
37030
  }
37031
+ this.recordWhatItBrokeOn(game);
37032
+ }
37033
+ recordWhatItBrokeOn(game) {
37034
+ const halt = this.dapClient?.whereItStopped() ?? null;
37035
+ if (halt?.reason !== "exception" || halt.text === "" || game.brokeOn === halt.text) {
37036
+ return;
37037
+ }
37038
+ game.brokeOn = halt.text;
37039
+ game.log.record("error", halt.text);
36972
37040
  }
36973
37041
  async endActiveGame() {
36974
37042
  const running = this.activeProcess;
@@ -36990,7 +37058,8 @@ class GodotServer {
36990
37058
  log,
36991
37059
  startedAt: Date.now(),
36992
37060
  exitCode: null,
36993
- throughEditor: false
37061
+ throughEditor: false,
37062
+ brokeOn: null
36994
37063
  };
36995
37064
  child.stdout.on("data", (data) => {
36996
37065
  log.append("stdout", data);
@@ -37055,6 +37124,7 @@ class GodotServer {
37055
37124
  contains: readNonEmptyString(args, "contains"),
37056
37125
  limit: readPositiveNumber(args, "limit") ?? 200
37057
37126
  });
37127
+ const halt = this.activeProcess.throughEditor ? this.dapClient?.whereItStopped() ?? null : null;
37058
37128
  return this.jsonTextResponse({
37059
37129
  running: this.activeProcess.exitCode === null,
37060
37130
  exitCode: this.activeProcess.exitCode,
@@ -37063,6 +37133,7 @@ class GodotServer {
37063
37133
  errors: this.activeProcess.log.count("error"),
37064
37134
  warnings: this.activeProcess.log.count("warning"),
37065
37135
  clean: this.activeProcess.log.count("error") === 0,
37136
+ heldAt: halt,
37066
37137
  omitted: selected.omitted,
37067
37138
  entries: selected.entries
37068
37139
  });
@@ -37132,8 +37203,8 @@ class GodotServer {
37132
37203
  return this.createErrorResponse(choice.problem);
37133
37204
  }
37134
37205
  const expectsScreenshot = command === "capture_screenshot" || command === "capture_viewport";
37135
- const screenshotDir = expectsScreenshot ? mkdtempSync3(join14(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
37136
- const screenshotPath = screenshotDir ? join14(screenshotDir, "capture.png") : null;
37206
+ const screenshotDir = expectsScreenshot ? mkdtempSync3(join15(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
37207
+ const screenshotPath = screenshotDir ? join15(screenshotDir, "capture.png") : null;
37137
37208
  try {
37138
37209
  const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
37139
37210
  if (!reply.ok) {
@@ -37154,12 +37225,12 @@ class GodotServer {
37154
37225
  return {
37155
37226
  content: [
37156
37227
  { type: "text", text: `Screenshot captured: ${dimensions}` },
37157
- { type: "image", data: readFileSync11(screenshotPath).toString("base64"), mimeType: "image/png" }
37228
+ { type: "image", data: readFileSync12(screenshotPath).toString("base64"), mimeType: "image/png" }
37158
37229
  ]
37159
37230
  };
37160
37231
  } finally {
37161
37232
  if (screenshotDir) {
37162
- rmSync7(screenshotDir, { recursive: true, force: true });
37233
+ rmSync8(screenshotDir, { recursive: true, force: true });
37163
37234
  }
37164
37235
  }
37165
37236
  }
@@ -37237,10 +37308,11 @@ var init_server3 = __esm(() => {
37237
37308
  init_resources();
37238
37309
  init_runtime_client();
37239
37310
  init_server_version();
37311
+ init_setup();
37240
37312
  init_tool_definitions();
37241
37313
  init_update_check();
37242
37314
  run5 = promisify5(execFile5);
37243
- __dirname2 = dirname8(fileURLToPath4(import.meta.url));
37315
+ __dirname2 = dirname9(fileURLToPath4(import.meta.url));
37244
37316
  PATH_SOLUTIONS = [
37245
37317
  'Give the path relative to the project, such as "scenes/main.tscn" or "res://scenes/main.tscn"',
37246
37318
  "Point projectPath at the project the file belongs to"
@@ -37297,8 +37369,8 @@ var init_server3 = __esm(() => {
37297
37369
  });
37298
37370
 
37299
37371
  // src/cli.ts
37300
- import { existsSync as existsSync12 } from "fs";
37301
- import { join as join15, resolve as resolve5 } from "path";
37372
+ import { existsSync as existsSync13 } from "fs";
37373
+ import { join as join16, resolve as resolve5 } from "path";
37302
37374
 
37303
37375
  // src/errors.ts
37304
37376
  class Refusal extends Error {
@@ -38700,7 +38772,7 @@ class UsageError extends Refusal {
38700
38772
  function projectArgument(at) {
38701
38773
  const named = args.slice(at).find((value) => !value.startsWith("--"));
38702
38774
  const projectPath = resolve5(named ?? ".");
38703
- if (!existsSync12(join15(projectPath, "project.godot"))) {
38775
+ if (!existsSync13(join16(projectPath, "project.godot"))) {
38704
38776
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
38705
38777
  }
38706
38778
  return projectPath;
@@ -38712,7 +38784,7 @@ async function engine() {
38712
38784
  }
38713
38785
  return { godotPath, script: shippedOperationsScript(), debug: GODOT_DEBUG_MODE_DEFAULT2 };
38714
38786
  }
38715
- function said(outcome, what) {
38787
+ function said2(outcome, what) {
38716
38788
  if (!outcome.ok) {
38717
38789
  throw new Refusal(`${what}: ${outcome.message}`);
38718
38790
  }
@@ -38813,15 +38885,15 @@ async function setup() {
38813
38885
  const enabled = await enablePlugins(godot, projectPath, EDITOR_PLUGINS);
38814
38886
  for (const [index, outcome] of enabled.entries()) {
38815
38887
  const name = EDITOR_PLUGINS[index] ?? "";
38816
- said(outcome, `enabling ${name}`);
38888
+ said2(outcome, `enabling ${name}`);
38817
38889
  console.log(`${name}: ${outcome.ok ? String(outcome.payload["action"]) : "failed"}`);
38818
38890
  }
38819
38891
  if (runtime) {
38820
- said(await setRuntime(godot, projectPath, true), "registering the runtime autoload");
38892
+ said2(await setRuntime(godot, projectPath, true), "registering the runtime autoload");
38821
38893
  console.log(`${RUNTIME_AUTOLOAD.name} autoload registered`);
38822
38894
  }
38823
38895
  const classes = await runOperation(godot, "refresh_class_cache", {}, projectPath);
38824
- said(classes, "rebuilding the class list");
38896
+ said2(classes, "rebuilding the class list");
38825
38897
  if (classes.ok) {
38826
38898
  console.log(`class list rebuilt: ${String(classes.payload["classes"])} classes`);
38827
38899
  }
@@ -38844,7 +38916,7 @@ async function setup() {
38844
38916
  console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
38845
38917
  }
38846
38918
  if (!args.includes("--no-skill")) {
38847
- const directories = skillDirectories(chosen.harnesses, projectPath, existsSync12);
38919
+ const directories = skillDirectories(chosen.harnesses, projectPath, existsSync13);
38848
38920
  for (const written of writeSkill(directories, getLocalVersion())) {
38849
38921
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
38850
38922
  }
@@ -38883,11 +38955,11 @@ async function uninstall() {
38883
38955
  const disabled = await disablePlugins(godot, projectPath, enabled);
38884
38956
  for (const [index, outcome] of disabled.entries()) {
38885
38957
  const name = enabled[index] ?? "";
38886
- said(outcome, `disabling ${name}`);
38958
+ said2(outcome, `disabling ${name}`);
38887
38959
  console.log(`${name}: ${outcome.ok ? String(outcome.payload["action"]) : "failed"}`);
38888
38960
  }
38889
38961
  if (before.runtimeAutoload) {
38890
- said(await setRuntime(godot, projectPath, false), "removing the runtime autoload");
38962
+ said2(await setRuntime(godot, projectPath, false), "removing the runtime autoload");
38891
38963
  console.log(`${RUNTIME_AUTOLOAD.name} autoload removed`);
38892
38964
  }
38893
38965
  const addons = removeAddons(projectPath);
@@ -38915,12 +38987,12 @@ async function upgrade() {
38915
38987
  }
38916
38988
  const enabled = await enablePlugins(godot, projectPath, EDITOR_PLUGINS);
38917
38989
  for (const [index, outcome] of enabled.entries()) {
38918
- said(outcome, `enabling ${EDITOR_PLUGINS[index] ?? ""}`);
38990
+ said2(outcome, `enabling ${EDITOR_PLUGINS[index] ?? ""}`);
38919
38991
  }
38920
38992
  if (before.runtimeAutoload) {
38921
- said(await setRuntime(godot, projectPath, true), "registering the runtime autoload");
38993
+ said2(await setRuntime(godot, projectPath, true), "registering the runtime autoload");
38922
38994
  }
38923
- said(await runOperation(godot, "refresh_class_cache", {}, projectPath), "rebuilding the class list");
38995
+ said2(await runOperation(godot, "refresh_class_cache", {}, projectPath), "rebuilding the class list");
38924
38996
  const launch = launchFor(version, godot.godotPath, projectPath);
38925
38997
  const already = HARNESSES.filter((harness) => registered(harness, projectPath));
38926
38998
  for (const group of groupByFile(already, projectPath)) {
@@ -38929,7 +39001,7 @@ async function upgrade() {
38929
39001
  if (already.length === 0) {
38930
39002
  console.log("no harness config names gdharness, so none was re-pinned");
38931
39003
  }
38932
- for (const written of writeSkill(skillDirectories(already, projectPath, existsSync12), version)) {
39004
+ for (const written of writeSkill(skillDirectories(already, projectPath, existsSync13), version)) {
38933
39005
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
38934
39006
  }
38935
39007
  console.log(`
@@ -38965,14 +39037,14 @@ async function runtime3() {
38965
39037
  }
38966
39038
  const projectPath = projectArgument(2);
38967
39039
  const godot = await engine();
38968
- said(await setRuntime(godot, projectPath, state === "on"), `turning the runtime ${state}`);
39040
+ said2(await setRuntime(godot, projectPath, state === "on"), `turning the runtime ${state}`);
38969
39041
  console.log(`${RUNTIME_AUTOLOAD.name} autoload ${state === "on" ? "registered" : "removed"}`);
38970
39042
  }
38971
39043
  async function classes() {
38972
39044
  const projectPath = projectArgument(1);
38973
39045
  const godot = await engine();
38974
39046
  const outcome = await runOperation(godot, "refresh_class_cache", {}, projectPath);
38975
- said(outcome, "rebuilding the class list");
39047
+ said2(outcome, "rebuilding the class list");
38976
39048
  if (outcome.ok) {
38977
39049
  console.log(JSON.stringify(outcome.payload, null, 2));
38978
39050
  }
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 rmSync3 } from "node:fs";
10129
+ import { existsSync as existsSync7, mkdtempSync as mkdtempSync2, readdirSync as readdirSync5, readFileSync as readFileSync8, realpathSync as realpathSync2, rmSync as rmSync4 } from "node:fs";
10130
10130
  import { tmpdir as tmpdir4 } from "node:os";
10131
- import { basename, dirname as dirname3, join as join8, normalize as normalize2 } from "node:path";
10131
+ import { basename, dirname as dirname4, join as join9, 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";
@@ -24878,6 +24878,10 @@ var DEFAULT_DAP_PORT = 6006;
24878
24878
  var DAP_REQUEST_TIMEOUT_MS = 1e4;
24879
24879
  var SCOPES_TIMEOUT_MS = 20000;
24880
24880
  var VARIABLES_POLL_TIMEOUT_MS = 2000;
24881
+ function said(body, field) {
24882
+ const value = body?.[field];
24883
+ return typeof value === "string" ? value : "";
24884
+ }
24881
24885
 
24882
24886
  class GodotDAPClient {
24883
24887
  socket = null;
@@ -24892,7 +24896,7 @@ class GodotDAPClient {
24892
24896
  initialized = false;
24893
24897
  attached = false;
24894
24898
  lastThreadId = 1;
24895
- stopped = false;
24899
+ halt = null;
24896
24900
  breakpoints = new Map;
24897
24901
  constructor(port = portFromEnv("GDHARNESS_DAP_PORT", DEFAULT_DAP_PORT), host = "127.0.0.1") {
24898
24902
  this.port = port;
@@ -24941,7 +24945,7 @@ class GodotDAPClient {
24941
24945
  this.connected = false;
24942
24946
  this.initialized = false;
24943
24947
  this.attached = false;
24944
- this.stopped = false;
24948
+ this.halt = null;
24945
24949
  this.socket = null;
24946
24950
  this.failPendingRequests(new Error("DAP connection closed"));
24947
24951
  });
@@ -24961,7 +24965,7 @@ class GodotDAPClient {
24961
24965
  this.connected = false;
24962
24966
  this.initialized = false;
24963
24967
  this.attached = false;
24964
- this.stopped = false;
24968
+ this.halt = null;
24965
24969
  return;
24966
24970
  }
24967
24971
  if (this.connected) {
@@ -24990,7 +24994,7 @@ class GodotDAPClient {
24990
24994
  this.connected = false;
24991
24995
  this.initialized = false;
24992
24996
  this.attached = false;
24993
- this.stopped = false;
24997
+ this.halt = null;
24994
24998
  }
24995
24999
  async ensureConnected() {
24996
25000
  if (!this.connected) {
@@ -25067,12 +25071,16 @@ class GodotDAPClient {
25067
25071
  if (typeof threadId === "number") {
25068
25072
  this.lastThreadId = threadId;
25069
25073
  }
25070
- this.stopped = true;
25074
+ this.halt = {
25075
+ reason: said(body, "reason"),
25076
+ description: said(body, "description"),
25077
+ text: said(body, "text")
25078
+ };
25071
25079
  return;
25072
25080
  }
25073
25081
  if (eventName === "terminated" || eventName === "exited") {
25074
25082
  this.attached = false;
25075
- this.stopped = false;
25083
+ this.halt = null;
25076
25084
  }
25077
25085
  }
25078
25086
  async initialize() {
@@ -25146,7 +25154,7 @@ class GodotDAPClient {
25146
25154
  await this.attach();
25147
25155
  const resolvedThreadId = await this.resolveThreadId(threadId);
25148
25156
  await this.sendRequest("continue", { threadId: resolvedThreadId });
25149
- this.stopped = false;
25157
+ this.halt = null;
25150
25158
  }
25151
25159
  async stepOver(threadId) {
25152
25160
  await this.attach();
@@ -25217,7 +25225,10 @@ class GodotDAPClient {
25217
25225
  return this.connected;
25218
25226
  }
25219
25227
  isStopped() {
25220
- return this.stopped;
25228
+ return this.halt !== null;
25229
+ }
25230
+ whereItStopped() {
25231
+ return this.halt;
25221
25232
  }
25222
25233
  async resolveThreadId(threadId) {
25223
25234
  if (typeof threadId === "number" && threadId > 0) {
@@ -25246,7 +25257,7 @@ class GodotDAPClient {
25246
25257
  this.connected = false;
25247
25258
  this.initialized = false;
25248
25259
  this.attached = false;
25249
- this.stopped = false;
25260
+ this.halt = null;
25250
25261
  this.reader = new FrameReader;
25251
25262
  socket?.destroy();
25252
25263
  this.failPendingRequests(new Error(`Godot DAP ${detail}. The connection was dropped.`));
@@ -25402,6 +25413,16 @@ class GameLog {
25402
25413
  this.entries.push(entry);
25403
25414
  this.lastHeadline = headline ? { index: entry.index, detail } : null;
25404
25415
  }
25416
+ record(severity, text) {
25417
+ this.entries.push({
25418
+ index: this.entries.length,
25419
+ severity,
25420
+ source: "debugger",
25421
+ text,
25422
+ detail: []
25423
+ });
25424
+ this.lastHeadline = null;
25425
+ }
25405
25426
  get all() {
25406
25427
  return this.entries;
25407
25428
  }
@@ -27557,6 +27578,20 @@ function runtimeRequest(endpoint, command, params, timeoutMs) {
27557
27578
  });
27558
27579
  }
27559
27580
 
27581
+ // src/setup.ts
27582
+ import { cpSync, existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "node:fs";
27583
+ import { dirname as dirname3, join as join8 } from "node:path";
27584
+ var ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
27585
+ var VERSION_MARKER = ".gdharness-version";
27586
+ function installedAddonVersion(projectPath) {
27587
+ const marker = join8(projectPath, "addons", ADDONS[0], VERSION_MARKER);
27588
+ if (!existsSync6(marker)) {
27589
+ return null;
27590
+ }
27591
+ const said = readFileSync7(marker, "utf8").trim();
27592
+ return said === "" ? null : said;
27593
+ }
27594
+
27560
27595
  // src/tool-definitions.ts
27561
27596
  var PROJECT_PATH = {
27562
27597
  type: "string",
@@ -28472,7 +28507,7 @@ function buildToolDefinitions() {
28472
28507
  var UPDATE_NOTICE_EVERY = 500;
28473
28508
  var FEEDBACK_NOTICE_EVERY = 250;
28474
28509
  var run3 = promisify3(execFile3);
28475
- var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
28510
+ var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
28476
28511
  var EDITOR_RESTART_TIMEOUT_MS = 90000;
28477
28512
  var BRIDGE_RETRY_MS = 2000;
28478
28513
  var PATH_SOLUTIONS = [
@@ -28550,11 +28585,11 @@ function realPathOr(path) {
28550
28585
  }
28551
28586
  }
28552
28587
  function hasMainScene(projectFile) {
28553
- const scene = parseProjectGodot(readFileSync7(projectFile, "utf8"))["application"]?.["run/main_scene"];
28588
+ const scene = parseProjectGodot(readFileSync8(projectFile, "utf8"))["application"]?.["run/main_scene"];
28554
28589
  return typeof scene === "string" && scene !== "";
28555
28590
  }
28556
28591
  function editorPlaysHeadless(projectFile) {
28557
- const runArgs = parseProjectGodot(readFileSync7(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
28592
+ const runArgs = parseProjectGodot(readFileSync8(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
28558
28593
  return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
28559
28594
  }
28560
28595
  function camelCased(params) {
@@ -28570,7 +28605,7 @@ function camelCased(params) {
28570
28605
  class GodotServer {
28571
28606
  mcp;
28572
28607
  locator = new GodotLocator;
28573
- operationsScript = join8(__dirname2, "godot", "operations", "godot_operations.gd");
28608
+ operationsScript = join9(__dirname2, "godot", "operations", "godot_operations.gd");
28574
28609
  godotBridge;
28575
28610
  tools = buildToolDefinitions();
28576
28611
  activeProcess = null;
@@ -28808,28 +28843,44 @@ class GodotServer {
28808
28843
  if (this.callsSinceNotice < UPDATE_NOTICE_EVERY && this.noticedUpdate) {
28809
28844
  return answer;
28810
28845
  }
28846
+ const moved = this.projectHasMovedOn();
28847
+ if (moved !== null) {
28848
+ this.noticedUpdate = true;
28849
+ this.callsSinceNotice = 0;
28850
+ return this.saying(answer, {
28851
+ project_upgraded_under_this_server: {
28852
+ server_is: SERVER_VERSION,
28853
+ project_is: moved,
28854
+ what_to_do: "The project was upgraded while this server has been running, so it is still answering " + "as the version it was started as. Tell the user to reconnect the MCP server, which is " + "the only thing that replaces it: a server cannot restart itself."
28855
+ }
28856
+ });
28857
+ }
28811
28858
  const notice = this.updates.notice();
28812
28859
  if (notice === null) {
28813
28860
  return answer;
28814
28861
  }
28815
28862
  this.noticedUpdate = true;
28816
28863
  this.callsSinceNotice = 0;
28864
+ return this.saying(answer, {
28865
+ update_available: {
28866
+ ...notice,
28867
+ what_to_do: "Tell the user a newer gdharness is out, with what changed, and offer to take it. " + "Only run the upgrade command if they say yes: it restarts their editor and the " + "MCP server has to be reconnected afterwards."
28868
+ }
28869
+ });
28870
+ }
28871
+ saying(answer, block) {
28817
28872
  return {
28818
28873
  ...answer,
28819
- content: [
28820
- ...answer.content,
28821
- {
28822
- type: "text",
28823
- text: JSON.stringify({
28824
- update_available: {
28825
- ...notice,
28826
- what_to_do: "Tell the user a newer gdharness is out, with what changed, and offer to take it. " + "Only run the upgrade command if they say yes: it restarts their editor and the " + "MCP server has to be reconnected afterwards."
28827
- }
28828
- }, null, 2)
28829
- }
28830
- ]
28874
+ content: [...answer.content, { type: "text", text: JSON.stringify(block, null, 2) }]
28831
28875
  };
28832
28876
  }
28877
+ projectHasMovedOn() {
28878
+ if (this.ownProject === null) {
28879
+ return null;
28880
+ }
28881
+ const installed = installedAddonVersion(this.ownProject);
28882
+ return installed === null || installed === SERVER_VERSION ? null : installed;
28883
+ }
28833
28884
  withFeedbackNotice(answer) {
28834
28885
  this.callsSinceFeedback += 1;
28835
28886
  if (this.callsSinceFeedback < FEEDBACK_NOTICE_EVERY) {
@@ -29075,8 +29126,8 @@ class GodotServer {
29075
29126
  if (path === undefined) {
29076
29127
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
29077
29128
  }
29078
- const file = join8(path, "project.godot");
29079
- if (!existsSync6(file)) {
29129
+ const file = join9(path, "project.godot");
29130
+ if (!existsSync7(file)) {
29080
29131
  return {
29081
29132
  ok: false,
29082
29133
  response: this.createErrorResponse(`Not a Godot project: ${path}`, [
@@ -29221,7 +29272,7 @@ class GodotServer {
29221
29272
  if (!engine.ok) {
29222
29273
  return engine.response;
29223
29274
  }
29224
- const application = parseProjectGodot(readFileSync7(project.value.file, "utf8"))["application"] ?? {};
29275
+ const application = parseProjectGodot(readFileSync8(project.value.file, "utf8"))["application"] ?? {};
29225
29276
  const name = application["config/name"];
29226
29277
  const mainScene = application["run/main_scene"];
29227
29278
  const info = {
@@ -29310,7 +29361,7 @@ class GodotServer {
29310
29361
  log.finish();
29311
29362
  const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
29312
29363
  const verdict = {
29313
- exported: exitCode === 0 && log.count("error") === 0 && existsSync6(output.absolutePath),
29364
+ exported: exitCode === 0 && log.count("error") === 0 && existsSync7(output.absolutePath),
29314
29365
  preset,
29315
29366
  outputPath: output.relativePath,
29316
29367
  debug,
@@ -29340,7 +29391,7 @@ class GodotServer {
29340
29391
  return contained.response;
29341
29392
  }
29342
29393
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
29343
- if (!existsSync6(join8(project.value.path, runner))) {
29394
+ if (!existsSync7(join9(project.value.path, runner))) {
29344
29395
  return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
29345
29396
  "Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
29346
29397
  ]);
@@ -29373,7 +29424,7 @@ class GodotServer {
29373
29424
  ];
29374
29425
  const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
29375
29426
  this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
29376
- const userData = mkdtempSync2(join8(tmpdir4(), "gdharness-tests-"));
29427
+ const userData = mkdtempSync2(join9(tmpdir4(), "gdharness-tests-"));
29377
29428
  const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
29378
29429
  const hung = await new Promise((resolve) => {
29379
29430
  const timer = setTimeout(() => {
@@ -29389,20 +29440,20 @@ class GodotServer {
29389
29440
  resolve(false);
29390
29441
  });
29391
29442
  });
29392
- const reportsDir = join8(project.value.path, ".godot", "gdharness-reports");
29443
+ const reportsDir = join9(project.value.path, ".godot", "gdharness-reports");
29393
29444
  let report = null;
29394
29445
  let reportProblem = null;
29395
29446
  try {
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))) : [];
29447
+ const written = existsSync7(reportsDir) ? readdirSync5(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
29397
29448
  const newest = written.at(-1);
29398
29449
  if (newest !== undefined) {
29399
- report = parseJUnit(readFileSync7(join8(reportsDir, newest, "results.xml"), "utf8"));
29450
+ report = parseJUnit(readFileSync8(join9(reportsDir, newest, "results.xml"), "utf8"));
29400
29451
  }
29401
29452
  } catch (error) {
29402
29453
  reportProblem = errorMessage(error);
29403
29454
  } finally {
29404
- rmSync3(reportsDir, { recursive: true, force: true });
29405
- rmSync3(userData, { recursive: true, force: true });
29455
+ rmSync4(reportsDir, { recursive: true, force: true });
29456
+ rmSync4(userData, { recursive: true, force: true });
29406
29457
  }
29407
29458
  const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries;
29408
29459
  const verdicts = {
@@ -29516,6 +29567,7 @@ class GodotServer {
29516
29567
  staleNote: stale ? addonMismatch(status.addonVersion, SERVER_VERSION) : undefined,
29517
29568
  retryingBridge: this.bridgeRetry === null ? undefined : true,
29518
29569
  announcedAt: this.announcedAt ?? undefined,
29570
+ projectIs: this.projectHasMovedOn() ?? undefined,
29519
29571
  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,
29520
29572
  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
29521
29573
  };
@@ -29728,7 +29780,8 @@ class GodotServer {
29728
29780
  log,
29729
29781
  startedAt: Date.now(),
29730
29782
  exitCode: null,
29731
- throughEditor: true
29783
+ throughEditor: true,
29784
+ brokeOn: null
29732
29785
  };
29733
29786
  this.activeProcess = played;
29734
29787
  return this.jsonTextResponse({
@@ -29748,6 +29801,15 @@ class GodotServer {
29748
29801
  `) ? line : `${line}
29749
29802
  `);
29750
29803
  }
29804
+ this.recordWhatItBrokeOn(game);
29805
+ }
29806
+ recordWhatItBrokeOn(game) {
29807
+ const halt = this.dapClient?.whereItStopped() ?? null;
29808
+ if (halt?.reason !== "exception" || halt.text === "" || game.brokeOn === halt.text) {
29809
+ return;
29810
+ }
29811
+ game.brokeOn = halt.text;
29812
+ game.log.record("error", halt.text);
29751
29813
  }
29752
29814
  async endActiveGame() {
29753
29815
  const running = this.activeProcess;
@@ -29769,7 +29831,8 @@ class GodotServer {
29769
29831
  log,
29770
29832
  startedAt: Date.now(),
29771
29833
  exitCode: null,
29772
- throughEditor: false
29834
+ throughEditor: false,
29835
+ brokeOn: null
29773
29836
  };
29774
29837
  child.stdout.on("data", (data) => {
29775
29838
  log.append("stdout", data);
@@ -29834,6 +29897,7 @@ class GodotServer {
29834
29897
  contains: readNonEmptyString(args, "contains"),
29835
29898
  limit: readPositiveNumber(args, "limit") ?? 200
29836
29899
  });
29900
+ const halt = this.activeProcess.throughEditor ? this.dapClient?.whereItStopped() ?? null : null;
29837
29901
  return this.jsonTextResponse({
29838
29902
  running: this.activeProcess.exitCode === null,
29839
29903
  exitCode: this.activeProcess.exitCode,
@@ -29842,6 +29906,7 @@ class GodotServer {
29842
29906
  errors: this.activeProcess.log.count("error"),
29843
29907
  warnings: this.activeProcess.log.count("warning"),
29844
29908
  clean: this.activeProcess.log.count("error") === 0,
29909
+ heldAt: halt,
29845
29910
  omitted: selected.omitted,
29846
29911
  entries: selected.entries
29847
29912
  });
@@ -29911,8 +29976,8 @@ class GodotServer {
29911
29976
  return this.createErrorResponse(choice.problem);
29912
29977
  }
29913
29978
  const expectsScreenshot = command === "capture_screenshot" || command === "capture_viewport";
29914
- const screenshotDir = expectsScreenshot ? mkdtempSync2(join8(tmpdir4(), "gdharness-runtime-screenshot-")) : null;
29915
- const screenshotPath = screenshotDir ? join8(screenshotDir, "capture.png") : null;
29979
+ const screenshotDir = expectsScreenshot ? mkdtempSync2(join9(tmpdir4(), "gdharness-runtime-screenshot-")) : null;
29980
+ const screenshotPath = screenshotDir ? join9(screenshotDir, "capture.png") : null;
29916
29981
  try {
29917
29982
  const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
29918
29983
  if (!reply.ok) {
@@ -29933,12 +29998,12 @@ class GodotServer {
29933
29998
  return {
29934
29999
  content: [
29935
30000
  { type: "text", text: `Screenshot captured: ${dimensions}` },
29936
- { type: "image", data: readFileSync7(screenshotPath).toString("base64"), mimeType: "image/png" }
30001
+ { type: "image", data: readFileSync8(screenshotPath).toString("base64"), mimeType: "image/png" }
29937
30002
  ]
29938
30003
  };
29939
30004
  } finally {
29940
30005
  if (screenshotDir) {
29941
- rmSync3(screenshotDir, { recursive: true, force: true });
30006
+ rmSync4(screenshotDir, { recursive: true, force: true });
29942
30007
  }
29943
30008
  }
29944
30009
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "0.5.9",
3
+ "version": "0.5.10",
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",