gdharness 1.0.13 → 1.0.14

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/cli.js CHANGED
@@ -31671,8 +31671,7 @@ function announcedAndGone(projectPath) {
31671
31671
  return [];
31672
31672
  }
31673
31673
  const since = Date.now() - GONE_WINDOW_MS;
31674
- const wanted = resolve3(projectPath);
31675
- return gone.filter((one) => one.noticedAt >= since && resolve3(one.project) === wanted);
31674
+ return gone.filter((one) => one.noticedAt >= since && isSameDirectory(one.project, projectPath));
31676
31675
  }
31677
31676
  function strangersAmong(candidates, now, startTimes = startTimesOf) {
31678
31677
  const toJudge = candidates.filter((one) => {
@@ -31778,11 +31777,10 @@ function sweepErrorReport(path, pid, now = Date.now()) {
31778
31777
  } catch {}
31779
31778
  }
31780
31779
  async function announcedSince(projectPath, before, waiting = {}) {
31781
- const wanted = resolve3(projectPath);
31782
31780
  const directories = waiting.directories ?? runtimeDirectories();
31783
31781
  const until = Date.now() + Math.max(waiting.budgetMs ?? ANNOUNCE_BUDGET_MS, 0);
31784
31782
  for (;; ) {
31785
- const fresh = discoverRuntimes(directories).find((endpoint) => !before.has(endpoint.pid) && resolve3(endpoint.project.path) === wanted && (waiting.accept?.(endpoint) ?? true));
31783
+ const fresh = discoverRuntimes(directories).find((endpoint) => !before.has(endpoint.pid) && isSameDirectory(endpoint.project.path, projectPath) && (waiting.accept?.(endpoint) ?? true));
31786
31784
  if (fresh !== undefined) {
31787
31785
  return fresh;
31788
31786
  }
@@ -31803,7 +31801,7 @@ function theOneProject(endpoints) {
31803
31801
  if (endpoints.some((endpoint) => endpoint.project.path === "")) {
31804
31802
  return;
31805
31803
  }
31806
- const paths = new Set(endpoints.map((endpoint) => resolve3(endpoint.project.path)));
31804
+ const paths = new Set(endpoints.map((endpoint) => realPathOr(resolve3(endpoint.project.path))));
31807
31805
  const only = [...paths][0];
31808
31806
  return paths.size === 1 ? only : undefined;
31809
31807
  }
@@ -31832,7 +31830,7 @@ function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
31832
31830
  if (pid !== undefined) {
31833
31831
  const named = endpoints.find((endpoint) => endpoint.pid === pid);
31834
31832
  if (named !== undefined) {
31835
- if (projectPath !== undefined && resolve3(named.project.path) !== resolve3(projectPath)) {
31833
+ if (projectPath !== undefined && !isSameDirectory(named.project.path, projectPath)) {
31836
31834
  return {
31837
31835
  problem: `The game with pid ${pid} is running ${named.project.path}, not ${resolve3(projectPath)}. Running: ${endpoints.map(describe2).join("; ")}.`
31838
31836
  };
@@ -31875,7 +31873,7 @@ function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
31875
31873
  }
31876
31874
  if (projectPath !== undefined) {
31877
31875
  const wanted = resolve3(projectPath);
31878
- const matching = endpoints.filter((endpoint) => resolve3(endpoint.project.path) === wanted);
31876
+ const matching = endpoints.filter((endpoint) => isSameDirectory(endpoint.project.path, wanted));
31879
31877
  if (matching.length === 0) {
31880
31878
  return {
31881
31879
  problem: `No running game is from ${wanted}. Running: ${endpoints.map(describe2).join("; ")}.`
@@ -31986,6 +31984,7 @@ function runtimeRequest(endpoint, command, params, timeoutMs) {
31986
31984
  }
31987
31985
  var RUNTIME_PROTOCOL = 2, ANNOUNCEMENT_PATTERN, ERROR_REPORT_PATTERN, ERROR_REPORT_KEEP_MS, GONE_WINDOW_MS = 120000, GONE_KEPT = 32, gone, STARTED_AFTER_ANNOUNCING_MS = 2000, CONFIRMED_FOR_MS = 60000, JUDGED_AFTER_MS = 60000, verdicts, ENDED_WITHIN_MS = 3000, LOOK_EVERY_MS = 50, ANNOUNCE_BUDGET_MS = 5000, nextRequestId = 1;
31988
31986
  var init_runtime_client = __esm(() => {
31987
+ init_paths();
31989
31988
  init_process_children();
31990
31989
  ANNOUNCEMENT_PATTERN = /^runtime-(\d+)\.json$/;
31991
31990
  ERROR_REPORT_PATTERN = /^runtime-(\d+)\.log$/;
@@ -41289,8 +41288,59 @@ class GodotServer {
41289
41288
  }
41290
41289
  return { heldAt: undefined, heldUnknown: true, heldNote: reply.message };
41291
41290
  }
41291
+ async ownRunOnItsWay(projectPath, budgetMs) {
41292
+ const run = this.currentRun();
41293
+ const project = projectPath ?? run?.projectPath ?? null;
41294
+ if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync15(join22(project, RUNTIME_AUTOLOAD2.path)) || !await this.runStillGoing(run)) {
41295
+ return null;
41296
+ }
41297
+ const already = runtimesAnnounced().running;
41298
+ const announcedSinceTheStart = (one) => {
41299
+ if (run.announcedBefore !== undefined) {
41300
+ return !run.announcedBefore.has(one.pid);
41301
+ }
41302
+ try {
41303
+ return statSync8(one.file).mtimeMs >= run.startedAt;
41304
+ } catch {
41305
+ return false;
41306
+ }
41307
+ };
41308
+ if (already.some((one) => announcedSinceTheStart(one) && this.isTheGameOf(run, one, project))) {
41309
+ return null;
41310
+ }
41311
+ const which = run.throughEditor ? "the game the editor is playing for it" : `pid ${run.pid ?? "unknown"}`;
41312
+ const comingUpUntil = run.startedAt + waitSizedTo(readBootNote(project)) + budgetMs;
41313
+ const remaining = comingUpUntil - Date.now();
41314
+ if (remaining <= 0) {
41315
+ const seconds = Math.round((Date.now() - run.startedAt) / 1000);
41316
+ return `The game this server started (${which}) has been running for ${seconds}s and has not announced its runtime, so there is nothing to talk to. A game that keeps the runtime off, as a project's own loader can for some scenes, never announces; editor_output says what it has printed.`;
41317
+ }
41318
+ const endpoint = await announcedSince(project, new Set(already.map((one) => one.pid)), {
41319
+ budgetMs: Math.min(budgetMs, remaining),
41320
+ accept: (one) => this.isTheGameOf(this.currentRun(), one, project),
41321
+ giveUp: () => this.currentRun() !== run || this.dapClient?.isStopped() === true || !stillRunning(run)
41322
+ });
41323
+ if (endpoint !== null) {
41324
+ const tiedMeanwhile = () => run.announcedPid !== undefined;
41325
+ if (!tiedMeanwhile()) {
41326
+ this.tie(run, endpoint);
41327
+ }
41328
+ return null;
41329
+ }
41330
+ if (this.currentRun() !== run || !await this.runStillGoing(run)) {
41331
+ return null;
41332
+ }
41333
+ return `The game this server started (${which}) is still coming up and has not announced its runtime after a wait of ${Math.min(budgetMs, remaining)}ms, so there is nothing to talk to yet. It is still running: ask again in a moment rather than starting another.`;
41334
+ }
41292
41335
  async handleRuntimeCommand(command, args, timeoutMs = this.runtimeTimeoutMs()) {
41293
41336
  const { op: _op, projectPath, pid, ...params } = asParams(args);
41337
+ const waited = typeof pid === "number" ? null : await this.ownRunOnItsWay(typeof projectPath === "string" ? projectPath : this.ownProject, timeoutMs);
41338
+ if (waited !== null) {
41339
+ return this.createErrorResponse(waited, [
41340
+ "editor_status lists it under runtimes once it has announced",
41341
+ "editor_output says what it has printed so far"
41342
+ ]);
41343
+ }
41294
41344
  const announced = runtimesAnnounced();
41295
41345
  const wanted = typeof projectPath === "string" ? projectPath : this.ownProject ?? undefined;
41296
41346
  const own = typeof pid === "number" ? undefined : this.ownGameAmong(announced.running, wanted) ?? await this.ownGameThroughTheTree(announced.running, wanted);
package/build/index.js CHANGED
@@ -24770,7 +24770,7 @@ import { dirname as dirname2, join as join3 } from "node:path";
24770
24770
  import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3, statSync, unlinkSync } from "node:fs";
24771
24771
  import { createConnection } from "node:net";
24772
24772
  import { tmpdir as tmpdir2 } from "node:os";
24773
- import { dirname, join as join2, resolve } from "node:path";
24773
+ import { dirname, join as join2, resolve as resolve2 } from "node:path";
24774
24774
  import { setTimeout as delay } from "node:timers/promises";
24775
24775
 
24776
24776
  // src/launch.ts
@@ -24837,6 +24837,65 @@ function environmentFor(options, variables = process.env) {
24837
24837
  }
24838
24838
  var SAVES_NOT_MOVED_NOTE = "On macOS the engine reads user:// off HOME and ignores the variables that move it elsewhere, measured on Godot 4.7.2, so this run wrote its saves where the player keeps theirs. A suite that saves a game has written into the same folder as the copy being played, and one that tidies up after itself may have removed real saves.";
24839
24839
 
24840
+ // src/paths.ts
24841
+ import { realpathSync } from "node:fs";
24842
+ import { isAbsolute, relative, resolve, sep } from "node:path";
24843
+ var RESOURCE_SCHEME = "res://";
24844
+ var OTHER_SCHEME = /^[a-zA-Z][a-zA-Z\d+.-]+:\/\//;
24845
+ function realPathOr(path) {
24846
+ try {
24847
+ return realpathSync.native(path);
24848
+ } catch {
24849
+ return path;
24850
+ }
24851
+ }
24852
+ function isSameDirectory(onePath, otherPath) {
24853
+ return relative(realPathOr(resolve(onePath)), realPathOr(resolve(otherPath))) === "";
24854
+ }
24855
+ function isWithinRoot(rootPath, candidatePath) {
24856
+ const step = relative(resolve(rootPath), resolve(candidatePath));
24857
+ return step !== ".." && !step.startsWith(`..${sep}`) && !isAbsolute(step);
24858
+ }
24859
+ function resolveWithinProject(projectPath, candidatePath) {
24860
+ const trimmed = candidatePath.trim();
24861
+ if (trimmed === "") {
24862
+ return { ok: false, reason: "Path is empty." };
24863
+ }
24864
+ if (trimmed.includes("\x00")) {
24865
+ return { ok: false, reason: "Path contains a null byte." };
24866
+ }
24867
+ const withoutScheme = trimmed.startsWith(RESOURCE_SCHEME) ? trimmed.slice(RESOURCE_SCHEME.length) : trimmed;
24868
+ const scheme = OTHER_SCHEME.exec(withoutScheme);
24869
+ if (scheme) {
24870
+ return {
24871
+ ok: false,
24872
+ reason: `Path '${trimmed}' uses the '${scheme[0]}' scheme, which names somewhere other than this project.`
24873
+ };
24874
+ }
24875
+ if (withoutScheme.startsWith("/") || withoutScheme.startsWith("\\") || isAbsolute(withoutScheme)) {
24876
+ return {
24877
+ ok: false,
24878
+ reason: `Path '${trimmed}' is absolute; give a path inside the project, such as 'scenes/main.tscn'.`
24879
+ };
24880
+ }
24881
+ if (withoutScheme.includes(":")) {
24882
+ return {
24883
+ ok: false,
24884
+ reason: `Path '${trimmed}' contains ':', which no file inside a project is named with.`
24885
+ };
24886
+ }
24887
+ const root = resolve(projectPath);
24888
+ const absolutePath = resolve(root, withoutScheme);
24889
+ if (!isWithinRoot(root, absolutePath)) {
24890
+ return { ok: false, reason: `Path '${trimmed}' resolves outside the project directory.` };
24891
+ }
24892
+ const relativePath = relative(root, absolutePath).split(sep).join("/");
24893
+ if (relativePath === "") {
24894
+ return { ok: false, reason: `Path '${trimmed}' is the project directory itself, not a file in it.` };
24895
+ }
24896
+ return { ok: true, absolutePath, relativePath };
24897
+ }
24898
+
24840
24899
  // src/process-children.ts
24841
24900
  import { execFile, execFileSync, spawnSync } from "node:child_process";
24842
24901
  import { basename } from "node:path";
@@ -25110,7 +25169,7 @@ function runtimeDirectories(variables = process.env) {
25110
25169
  for (const base of fallbacks) {
25111
25170
  candidates.push(join2(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
25112
25171
  }
25113
- return [...new Set(candidates.map((path) => resolve(path)))];
25172
+ return [...new Set(candidates.map((path) => resolve2(path)))];
25114
25173
  }
25115
25174
  var ERROR_REPORT_PATTERN = /^runtime-(\d+)\.log$/;
25116
25175
  function errorReportOf(pid, directories = runtimeDirectories()) {
@@ -25205,8 +25264,7 @@ function announcedAndGone(projectPath) {
25205
25264
  return [];
25206
25265
  }
25207
25266
  const since = Date.now() - GONE_WINDOW_MS;
25208
- const wanted = resolve(projectPath);
25209
- return gone.filter((one) => one.noticedAt >= since && resolve(one.project) === wanted);
25267
+ return gone.filter((one) => one.noticedAt >= since && isSameDirectory(one.project, projectPath));
25210
25268
  }
25211
25269
  var STARTED_AFTER_ANNOUNCING_MS = 2000;
25212
25270
  var CONFIRMED_FOR_MS = 60000;
@@ -25319,11 +25377,10 @@ function sweepErrorReport(path, pid, now = Date.now()) {
25319
25377
  var LOOK_EVERY_MS = 50;
25320
25378
  var ANNOUNCE_BUDGET_MS = 5000;
25321
25379
  async function announcedSince(projectPath, before, waiting = {}) {
25322
- const wanted = resolve(projectPath);
25323
25380
  const directories = waiting.directories ?? runtimeDirectories();
25324
25381
  const until = Date.now() + Math.max(waiting.budgetMs ?? ANNOUNCE_BUDGET_MS, 0);
25325
25382
  for (;; ) {
25326
- const fresh = discoverRuntimes(directories).find((endpoint) => !before.has(endpoint.pid) && resolve(endpoint.project.path) === wanted && (waiting.accept?.(endpoint) ?? true));
25383
+ const fresh = discoverRuntimes(directories).find((endpoint) => !before.has(endpoint.pid) && isSameDirectory(endpoint.project.path, projectPath) && (waiting.accept?.(endpoint) ?? true));
25327
25384
  if (fresh !== undefined) {
25328
25385
  return fresh;
25329
25386
  }
@@ -25344,7 +25401,7 @@ function theOneProject(endpoints) {
25344
25401
  if (endpoints.some((endpoint) => endpoint.project.path === "")) {
25345
25402
  return;
25346
25403
  }
25347
- const paths = new Set(endpoints.map((endpoint) => resolve(endpoint.project.path)));
25404
+ const paths = new Set(endpoints.map((endpoint) => realPathOr(resolve2(endpoint.project.path))));
25348
25405
  const only = [...paths][0];
25349
25406
  return paths.size === 1 ? only : undefined;
25350
25407
  }
@@ -25373,9 +25430,9 @@ function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
25373
25430
  if (pid !== undefined) {
25374
25431
  const named = endpoints.find((endpoint) => endpoint.pid === pid);
25375
25432
  if (named !== undefined) {
25376
- if (projectPath !== undefined && resolve(named.project.path) !== resolve(projectPath)) {
25433
+ if (projectPath !== undefined && !isSameDirectory(named.project.path, projectPath)) {
25377
25434
  return {
25378
- problem: `The game with pid ${pid} is running ${named.project.path}, not ${resolve(projectPath)}. Running: ${endpoints.map(describe2).join("; ")}.`
25435
+ problem: `The game with pid ${pid} is running ${named.project.path}, not ${resolve2(projectPath)}. Running: ${endpoints.map(describe2).join("; ")}.`
25379
25436
  };
25380
25437
  }
25381
25438
  return { endpoint: named };
@@ -25415,8 +25472,8 @@ function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
25415
25472
  };
25416
25473
  }
25417
25474
  if (projectPath !== undefined) {
25418
- const wanted = resolve(projectPath);
25419
- const matching = endpoints.filter((endpoint) => resolve(endpoint.project.path) === wanted);
25475
+ const wanted = resolve2(projectPath);
25476
+ const matching = endpoints.filter((endpoint) => isSameDirectory(endpoint.project.path, wanted));
25420
25477
  if (matching.length === 0) {
25421
25478
  return {
25422
25479
  problem: `No running game is from ${wanted}. Running: ${endpoints.map(describe2).join("; ")}.`
@@ -25560,67 +25617,6 @@ function waitSizedTo(lastBootMs, usual = ANNOUNCE_BUDGET_MS) {
25560
25617
  // src/breakpoint-note.ts
25561
25618
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
25562
25619
  import { dirname as dirname3, isAbsolute as isAbsolute2, join as join4, relative as relative2 } from "node:path";
25563
-
25564
- // src/paths.ts
25565
- import { realpathSync } from "node:fs";
25566
- import { isAbsolute, relative, resolve as resolve2, sep } from "node:path";
25567
- var RESOURCE_SCHEME = "res://";
25568
- var OTHER_SCHEME = /^[a-zA-Z][a-zA-Z\d+.-]+:\/\//;
25569
- function realPathOr(path) {
25570
- try {
25571
- return realpathSync.native(path);
25572
- } catch {
25573
- return path;
25574
- }
25575
- }
25576
- function isSameDirectory(onePath, otherPath) {
25577
- return relative(realPathOr(resolve2(onePath)), realPathOr(resolve2(otherPath))) === "";
25578
- }
25579
- function isWithinRoot(rootPath, candidatePath) {
25580
- const step = relative(resolve2(rootPath), resolve2(candidatePath));
25581
- return step !== ".." && !step.startsWith(`..${sep}`) && !isAbsolute(step);
25582
- }
25583
- function resolveWithinProject(projectPath, candidatePath) {
25584
- const trimmed = candidatePath.trim();
25585
- if (trimmed === "") {
25586
- return { ok: false, reason: "Path is empty." };
25587
- }
25588
- if (trimmed.includes("\x00")) {
25589
- return { ok: false, reason: "Path contains a null byte." };
25590
- }
25591
- const withoutScheme = trimmed.startsWith(RESOURCE_SCHEME) ? trimmed.slice(RESOURCE_SCHEME.length) : trimmed;
25592
- const scheme = OTHER_SCHEME.exec(withoutScheme);
25593
- if (scheme) {
25594
- return {
25595
- ok: false,
25596
- reason: `Path '${trimmed}' uses the '${scheme[0]}' scheme, which names somewhere other than this project.`
25597
- };
25598
- }
25599
- if (withoutScheme.startsWith("/") || withoutScheme.startsWith("\\") || isAbsolute(withoutScheme)) {
25600
- return {
25601
- ok: false,
25602
- reason: `Path '${trimmed}' is absolute; give a path inside the project, such as 'scenes/main.tscn'.`
25603
- };
25604
- }
25605
- if (withoutScheme.includes(":")) {
25606
- return {
25607
- ok: false,
25608
- reason: `Path '${trimmed}' contains ':', which no file inside a project is named with.`
25609
- };
25610
- }
25611
- const root = resolve2(projectPath);
25612
- const absolutePath = resolve2(root, withoutScheme);
25613
- if (!isWithinRoot(root, absolutePath)) {
25614
- return { ok: false, reason: `Path '${trimmed}' resolves outside the project directory.` };
25615
- }
25616
- const relativePath = relative(root, absolutePath).split(sep).join("/");
25617
- if (relativePath === "") {
25618
- return { ok: false, reason: `Path '${trimmed}' is the project directory itself, not a file in it.` };
25619
- }
25620
- return { ok: true, absolutePath, relativePath };
25621
- }
25622
-
25623
- // src/breakpoint-note.ts
25624
25620
  function breakpointNotePath(projectPath) {
25625
25621
  return join4(projectPath, ".godot", "gdharness-breakpoints.json");
25626
25622
  }
@@ -34030,8 +34026,59 @@ class GodotServer {
34030
34026
  }
34031
34027
  return { heldAt: undefined, heldUnknown: true, heldNote: reply.message };
34032
34028
  }
34029
+ async ownRunOnItsWay(projectPath, budgetMs) {
34030
+ const run = this.currentRun();
34031
+ const project = projectPath ?? run?.projectPath ?? null;
34032
+ 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)) {
34033
+ return null;
34034
+ }
34035
+ const already = runtimesAnnounced().running;
34036
+ const announcedSinceTheStart = (one) => {
34037
+ if (run.announcedBefore !== undefined) {
34038
+ return !run.announcedBefore.has(one.pid);
34039
+ }
34040
+ try {
34041
+ return statSync7(one.file).mtimeMs >= run.startedAt;
34042
+ } catch {
34043
+ return false;
34044
+ }
34045
+ };
34046
+ if (already.some((one) => announcedSinceTheStart(one) && this.isTheGameOf(run, one, project))) {
34047
+ return null;
34048
+ }
34049
+ const which = run.throughEditor ? "the game the editor is playing for it" : `pid ${run.pid ?? "unknown"}`;
34050
+ const comingUpUntil = run.startedAt + waitSizedTo(readBootNote(project)) + budgetMs;
34051
+ const remaining = comingUpUntil - Date.now();
34052
+ if (remaining <= 0) {
34053
+ const seconds = Math.round((Date.now() - run.startedAt) / 1000);
34054
+ return `The game this server started (${which}) has been running for ${seconds}s and has not announced its runtime, so there is nothing to talk to. A game that keeps the runtime off, as a project's own loader can for some scenes, never announces; editor_output says what it has printed.`;
34055
+ }
34056
+ const endpoint = await announcedSince(project, new Set(already.map((one) => one.pid)), {
34057
+ budgetMs: Math.min(budgetMs, remaining),
34058
+ accept: (one) => this.isTheGameOf(this.currentRun(), one, project),
34059
+ giveUp: () => this.currentRun() !== run || this.dapClient?.isStopped() === true || !stillRunning(run)
34060
+ });
34061
+ if (endpoint !== null) {
34062
+ const tiedMeanwhile = () => run.announcedPid !== undefined;
34063
+ if (!tiedMeanwhile()) {
34064
+ this.tie(run, endpoint);
34065
+ }
34066
+ return null;
34067
+ }
34068
+ if (this.currentRun() !== run || !await this.runStillGoing(run)) {
34069
+ return null;
34070
+ }
34071
+ return `The game this server started (${which}) is still coming up and has not announced its runtime after a wait of ${Math.min(budgetMs, remaining)}ms, so there is nothing to talk to yet. It is still running: ask again in a moment rather than starting another.`;
34072
+ }
34033
34073
  async handleRuntimeCommand(command, args, timeoutMs = this.runtimeTimeoutMs()) {
34034
34074
  const { op: _op, projectPath, pid, ...params } = asParams(args);
34075
+ const waited = typeof pid === "number" ? null : await this.ownRunOnItsWay(typeof projectPath === "string" ? projectPath : this.ownProject, timeoutMs);
34076
+ if (waited !== null) {
34077
+ return this.createErrorResponse(waited, [
34078
+ "editor_status lists it under runtimes once it has announced",
34079
+ "editor_output says what it has printed so far"
34080
+ ]);
34081
+ }
34035
34082
  const announced = runtimesAnnounced();
34036
34083
  const wanted = typeof projectPath === "string" ? projectPath : this.ownProject ?? undefined;
34037
34084
  const own = typeof pid === "number" ? undefined : this.ownGameAmong(announced.running, wanted) ?? await this.ownGameThroughTheTree(announced.running, wanted);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "1.0.13",
3
+ "version": "1.0.14",
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",