gdharness 1.0.9 → 1.0.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.
package/build/cli.js CHANGED
@@ -201,7 +201,11 @@ function userDataIn(home, variables = process.env) {
201
201
  function savesStayPut(platform = process.platform) {
202
202
  return platform === "darwin";
203
203
  }
204
- var OPENED_BY_A_SERVER = "GDHARNESS_OPENED_BY_A_SERVER", 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.";
204
+ function environmentFor(options, variables = process.env) {
205
+ const base = options.savesIn === undefined ? { ...variables } : userDataIn(options.savesIn, variables);
206
+ return { ...base, ...options.env, GDHARNESS_RUNTIME_DIR: options.runtimeDirectory };
207
+ }
208
+ var OPENED_BY_A_SERVER = "GDHARNESS_OPENED_BY_A_SERVER", RESERVED_VARIABLE_PREFIX = "GDHARNESS_", 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.";
205
209
 
206
210
  // node_modules/yaml/dist/nodes/identity.js
207
211
  var require_identity = __commonJS(function(exports) {
@@ -16012,6 +16016,16 @@ var init_tool_definitions = __esm(() => {
16012
16016
  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.`
16013
16017
  },
16014
16018
  headless: { type: "boolean", ops: ["start"], description: "start: force a window or no window." },
16019
+ savesIn: {
16020
+ type: "string",
16021
+ ops: ["start", "check"],
16022
+ description: "An absolute directory for this run's user://, so a game that saves writes nowhere near the copy somebody plays: opening a save writes the one being left, and a game that autosaves writes on its own. Godot takes no flag for it, so this is the environment, and the run is started by this server rather than played by the editor, which cannot be given one. It does not have to exist: the engine makes it, and the Godot/app_userdata/<project> tree under it, measured on 4.7.2. On macOS the engine reads user:// off HOME and ignores it, also measured, and the answer says so under savesNote rather than leaving the caller to find out from the player's save list."
16023
+ },
16024
+ env: {
16025
+ type: "object",
16026
+ ops: ["start", "check"],
16027
+ description: "Environment variables for this run alone, as a map of strings, over the server's own environment. Like savesIn, a run carrying any is started by this server rather than played by the editor. Names beginning with GDHARNESS_ are refused: they are this server's contract with the addon in the game, and the runtime directory is set here whatever the rest of the environment says, so a game that moves TEMP still announces where this server looks."
16028
+ },
16015
16029
  runtimeWaitMs: {
16016
16030
  type: "number",
16017
16031
  ops: ["start"],
@@ -37650,7 +37664,7 @@ import {
37650
37664
  statSync as statSync5
37651
37665
  } from "node:fs";
37652
37666
  import { tmpdir as tmpdir5 } from "node:os";
37653
- import { basename as basename4, dirname as dirname14, join as join21, normalize as normalize3, relative as relative3 } from "node:path";
37667
+ import { basename as basename4, dirname as dirname14, isAbsolute as isAbsolute3, join as join21, normalize as normalize3, relative as relative3 } from "node:path";
37654
37668
  import { setTimeout as delay3 } from "node:timers/promises";
37655
37669
  import { fileURLToPath as fileURLToPath4 } from "node:url";
37656
37670
  import { promisify as promisify7 } from "node:util";
@@ -37932,6 +37946,9 @@ function camelCased(params) {
37932
37946
  }
37933
37947
  return result;
37934
37948
  }
37949
+ function needsItsOwnProcess(asked) {
37950
+ return asked.savesIn !== undefined || asked.env !== undefined;
37951
+ }
37935
37952
 
37936
37953
  class GodotServer {
37937
37954
  mcp;
@@ -38627,6 +38644,51 @@ class GodotServer {
38627
38644
  }
38628
38645
  return { ok: true, value: given };
38629
38646
  }
38647
+ runEnvironment(args) {
38648
+ const savesIn = readNonEmptyString(args, "savesIn");
38649
+ if (savesIn !== undefined && !isAbsolute3(savesIn)) {
38650
+ return {
38651
+ ok: false,
38652
+ response: this.createErrorResponse(`savesIn must be an absolute path, not "${savesIn}".`, [
38653
+ "Name the directory user:// should be under, such as a temporary directory of this run's own"
38654
+ ])
38655
+ };
38656
+ }
38657
+ if (args["env"] === undefined) {
38658
+ return { ok: true, value: savesIn === undefined ? {} : { savesIn } };
38659
+ }
38660
+ const given = readParams(args, "env");
38661
+ if (given === undefined) {
38662
+ return {
38663
+ ok: false,
38664
+ response: this.createErrorResponse("env must be a map of names to string values.", [
38665
+ 'Pass the variables this run needs, such as {"MY_FLAG": "1"}'
38666
+ ])
38667
+ };
38668
+ }
38669
+ const env = {};
38670
+ for (const [name, value] of Object.entries(given)) {
38671
+ if (typeof value !== "string") {
38672
+ return {
38673
+ ok: false,
38674
+ response: this.createErrorResponse(`env values must be strings, and ${name} is ${typeof value}.`, [
38675
+ 'Pass every value as a string, such as {"WORKERS": "4"}'
38676
+ ])
38677
+ };
38678
+ }
38679
+ if (name.toUpperCase().startsWith(RESERVED_VARIABLE_PREFIX)) {
38680
+ return {
38681
+ ok: false,
38682
+ response: this.createErrorResponse(`env may not set ${name}: names beginning with ${RESERVED_VARIABLE_PREFIX} are this server's own.`, [
38683
+ "GDHARNESS_RUNTIME_DIR is where the game announces itself and this server looks for it",
38684
+ "Name the directory for user:// with savesIn rather than by moving the environment yourself"
38685
+ ])
38686
+ };
38687
+ }
38688
+ env[name] = value;
38689
+ }
38690
+ return { ok: true, value: { ...savesIn === undefined ? {} : { savesIn }, env } };
38691
+ }
38630
38692
  async debuggedGame(intent) {
38631
38693
  const game = this.activeProcess;
38632
38694
  if (game !== null && game.exitCode !== null) {
@@ -39614,6 +39676,10 @@ class GodotServer {
39614
39676
  if (!given.ok) {
39615
39677
  return given.response;
39616
39678
  }
39679
+ const asked = this.runEnvironment(args);
39680
+ if (!asked.ok) {
39681
+ return asked.response;
39682
+ }
39617
39683
  if (!sceneToRun && !hasMainScene(project.value.file)) {
39618
39684
  return this.createErrorResponse("The project sets no main scene, so there is nothing to run.", [
39619
39685
  "Pass scene to run one scene",
@@ -39630,7 +39696,7 @@ class GodotServer {
39630
39696
  }
39631
39697
  const sceneArgument = sceneToRun?.ok ? sceneToRun.relativePath : null;
39632
39698
  if (op === "check") {
39633
- return await this.checkBoot(engine.value, project.value.path, sceneArgument, args, given.value);
39699
+ return await this.checkBoot(engine.value, project.value.path, sceneArgument, args, given.value, asked.value);
39634
39700
  }
39635
39701
  await this.pickUpWhatTheEditorIsPlaying();
39636
39702
  let ended = null;
@@ -39648,7 +39714,7 @@ class GodotServer {
39648
39714
  });
39649
39715
  const runtimeWaitMs = this.announceBudget(args, project.value.path);
39650
39716
  const editorWouldPlay = !headless || editorPlaysHeadless(project.value.file);
39651
- if (this.godotBridge.isConnected() && editorWouldPlay && given.value.length === 0) {
39717
+ if (this.godotBridge.isConnected() && editorWouldPlay && given.value.length === 0 && !needsItsOwnProcess(asked.value)) {
39652
39718
  return await this.playThroughEditor(sceneArgument, refreshed.value, project.value.path, alreadyPlaying, runtimeWaitMs, ended);
39653
39719
  }
39654
39720
  const cmdArgs = runArguments({
@@ -39658,10 +39724,10 @@ class GodotServer {
39658
39724
  userArgs: given.value
39659
39725
  });
39660
39726
  this.logDebug(`Running Godot project: ${engine.value} ${cmdArgs.join(" ")}`);
39661
- const started = this.spawnKeptGame(engine.value, cmdArgs, project.value.path);
39727
+ const started = this.spawnKeptGame(engine.value, cmdArgs, project.value.path, needsItsOwnProcess(asked.value) ? environmentFor({ ...asked.value, runtimeDirectory: runtimeDirectory() }) : undefined);
39662
39728
  started.announcedBefore = alreadyPlaying;
39663
39729
  this.activeProcess = started;
39664
- const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && given.value.length > 0 ? " The editor cannot be handed arguments for a game it plays, so this one was started " + "here: the debug_* tools answer only for a game the editor is playing." : "";
39730
+ const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && given.value.length > 0 ? " The editor cannot be handed arguments for a game it plays, so this one was started " + "here: the debug_* tools answer only for a game the editor is playing." : this.godotBridge.isConnected() && editorWouldPlay && needsItsOwnProcess(asked.value) ? " The editor plays a game with its own environment, so a run given one was started " + "here: the debug_* tools answer only for a game the editor is playing." : "";
39665
39731
  const endedForThis = endedToStartThis(ended);
39666
39732
  const stranded = wereHereFirst.filter((one) => one.pid !== ended?.pid && alive(one.pid));
39667
39733
  const named = stranded.map((one) => one.protocol === null ? `pid ${one.pid}` : `pid ${one.pid} on protocol ${one.protocol}`).join(", ");
@@ -39674,6 +39740,7 @@ class GodotServer {
39674
39740
  arguments: cmdArgs,
39675
39741
  transcript: started.transcript,
39676
39742
  refreshedClasses: refreshed.value,
39743
+ savesNote: asked.value.savesIn !== undefined && savesStayPut() ? SAVES_NOT_MOVED_NOTE : undefined,
39677
39744
  endedPreviousRun: endedPreviousRun(ended),
39678
39745
  runtime: await this.runtimeUp(project.value.path, alreadyPlaying, runtimeWaitMs, given.value.length > 0),
39679
39746
  message: `Use editor_output for what it prints and editor_run stop to end it.${spawnedInstead}${endedForThis}${alsoRunning}`
@@ -40280,11 +40347,11 @@ class GodotServer {
40280
40347
  currentRun() {
40281
40348
  return this.activeProcess ?? this.adoptRecordedRun();
40282
40349
  }
40283
- async checkBoot(godotPath, projectPath, scene, args, userArgs) {
40350
+ async checkBoot(godotPath, projectPath, scene, args, userArgs, asked = {}) {
40284
40351
  const frames = readPositiveNumber(args, "frames") ?? 3;
40285
40352
  const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 60000;
40286
40353
  const cmdArgs = runArguments({ projectPath, headless: true, scene, quitAfter: frames, userArgs });
40287
- const boot = this.spawnGame(godotPath, cmdArgs);
40354
+ const boot = this.spawnGame(godotPath, cmdArgs, needsItsOwnProcess(asked) ? environmentFor({ ...asked, runtimeDirectory: runtimeDirectory() }) : undefined);
40288
40355
  const hung = await new Promise((resolve) => {
40289
40356
  const timer = setTimeout(() => {
40290
40357
  boot.process.kill();
@@ -40309,6 +40376,7 @@ class GodotServer {
40309
40376
  frames,
40310
40377
  errors,
40311
40378
  warnings,
40379
+ savesNote: asked.savesIn !== undefined && savesStayPut() ? SAVES_NOT_MOVED_NOTE : undefined,
40312
40380
  entries: forAnswer(boot.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries)
40313
40381
  });
40314
40382
  }
@@ -41643,7 +41711,7 @@ function displayPath(harness, platform = process3.platform) {
41643
41711
  return `${harness.scope === "home" ? "~/" : ""}${file.replaceAll("\\", "/")}`;
41644
41712
  }
41645
41713
  var OURS = ["GODOT_PATH", "GDHARNESS_PROJECT"];
41646
- function environmentFor(launch, held) {
41714
+ function environmentFor2(launch, held) {
41647
41715
  const kept = {};
41648
41716
  if (typeof held === "object" && held !== null && !Array.isArray(held)) {
41649
41717
  for (const [name, value] of Object.entries(held)) {
@@ -41661,7 +41729,7 @@ function entryFor(shape, launch, held) {
41661
41729
  ...before,
41662
41730
  type: "local",
41663
41731
  command: [launch.command, ...launch.args],
41664
- environment: environmentFor(launch, before["environment"]),
41732
+ environment: environmentFor2(launch, before["environment"]),
41665
41733
  enabled: true
41666
41734
  };
41667
41735
  }
@@ -41670,7 +41738,7 @@ function entryFor(shape, launch, held) {
41670
41738
  ...shape === "typed" ? { type: "stdio" } : {},
41671
41739
  command: launch.command,
41672
41740
  args: [...launch.args],
41673
- env: environmentFor(launch, before["env"])
41741
+ env: environmentFor2(launch, before["env"])
41674
41742
  };
41675
41743
  }
41676
41744
  function merged(existing, harness, launch) {
@@ -42489,6 +42557,7 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
42489
42557
  | Whether this engine has a method or property, and what it takes | \`editor_classes info\` with \`member\`, which answers that one member and which class declares it rather than the whole class |
42490
42558
  | Wait for a run to finish | \`editor_run wait\`, never a sleep or a shell loop on the pid: it answers as \`editor_output\` does, once the run is over |
42491
42559
  | End a bench and the workers it opened | \`editor_run stop\` with \`andChildren\`, never a process listing by scene name: it ends every process under the run's, to any depth, that is a game of the project, announced as one or the project's engine run with \`--path\` on it, names them under \`endedChildren\` with what identified each, and leaves and names any other |
42560
+ | Play a game without touching the player's saves | \`editor_run start\` with \`savesIn\`, a directory for this run's \`user://\`: the editor plays with its own environment, so a run given one is started by the server, and the answer says under \`savesNote\` when the platform ignores it |
42492
42561
  | A picture, for a person who asked to see one | \`runtime_capture\` |
42493
42562
 
42494
42563
  These need the runtime autoload, which \`gdharness setup\` registers. \`runtime_capture\` needs a
package/build/index.js CHANGED
@@ -10185,7 +10185,7 @@ import {
10185
10185
  statSync as statSync5
10186
10186
  } from "node:fs";
10187
10187
  import { tmpdir as tmpdir4 } from "node:os";
10188
- import { basename as basename3, dirname as dirname9, join as join15, normalize as normalize2, relative as relative3 } from "node:path";
10188
+ import { basename as basename3, dirname as dirname9, isAbsolute as isAbsolute3, join as join15, normalize as normalize2, relative as relative3 } from "node:path";
10189
10189
  import { setTimeout as delay3 } from "node:timers/promises";
10190
10190
  import { fileURLToPath as fileURLToPath2 } from "node:url";
10191
10191
  import { promisify as promisify5 } from "node:util";
@@ -24811,6 +24811,11 @@ function userDataIn(home, variables = process.env) {
24811
24811
  function savesStayPut(platform = process.platform) {
24812
24812
  return platform === "darwin";
24813
24813
  }
24814
+ var RESERVED_VARIABLE_PREFIX = "GDHARNESS_";
24815
+ function environmentFor(options, variables = process.env) {
24816
+ const base = options.savesIn === undefined ? { ...variables } : userDataIn(options.savesIn, variables);
24817
+ return { ...base, ...options.env, GDHARNESS_RUNTIME_DIR: options.runtimeDirectory };
24818
+ }
24814
24819
  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.";
24815
24820
 
24816
24821
  // src/process-children.ts
@@ -29819,6 +29824,16 @@ var TOOL_SPECS = [
29819
29824
  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.`
29820
29825
  },
29821
29826
  headless: { type: "boolean", ops: ["start"], description: "start: force a window or no window." },
29827
+ savesIn: {
29828
+ type: "string",
29829
+ ops: ["start", "check"],
29830
+ description: "An absolute directory for this run's user://, so a game that saves writes nowhere near the copy somebody plays: opening a save writes the one being left, and a game that autosaves writes on its own. Godot takes no flag for it, so this is the environment, and the run is started by this server rather than played by the editor, which cannot be given one. It does not have to exist: the engine makes it, and the Godot/app_userdata/<project> tree under it, measured on 4.7.2. On macOS the engine reads user:// off HOME and ignores it, also measured, and the answer says so under savesNote rather than leaving the caller to find out from the player's save list."
29831
+ },
29832
+ env: {
29833
+ type: "object",
29834
+ ops: ["start", "check"],
29835
+ description: "Environment variables for this run alone, as a map of strings, over the server's own environment. Like savesIn, a run carrying any is started by this server rather than played by the editor. Names beginning with GDHARNESS_ are refused: they are this server's contract with the addon in the game, and the runtime directory is set here whatever the rest of the environment says, so a game that moves TEMP still announces where this server looks."
29836
+ },
29822
29837
  runtimeWaitMs: {
29823
29838
  type: "number",
29824
29839
  ops: ["start"],
@@ -30669,6 +30684,9 @@ function camelCased(params) {
30669
30684
  }
30670
30685
  return result;
30671
30686
  }
30687
+ function needsItsOwnProcess(asked) {
30688
+ return asked.savesIn !== undefined || asked.env !== undefined;
30689
+ }
30672
30690
 
30673
30691
  class GodotServer {
30674
30692
  mcp;
@@ -31364,6 +31382,51 @@ class GodotServer {
31364
31382
  }
31365
31383
  return { ok: true, value: given };
31366
31384
  }
31385
+ runEnvironment(args) {
31386
+ const savesIn = readNonEmptyString(args, "savesIn");
31387
+ if (savesIn !== undefined && !isAbsolute3(savesIn)) {
31388
+ return {
31389
+ ok: false,
31390
+ response: this.createErrorResponse(`savesIn must be an absolute path, not "${savesIn}".`, [
31391
+ "Name the directory user:// should be under, such as a temporary directory of this run's own"
31392
+ ])
31393
+ };
31394
+ }
31395
+ if (args["env"] === undefined) {
31396
+ return { ok: true, value: savesIn === undefined ? {} : { savesIn } };
31397
+ }
31398
+ const given = readParams(args, "env");
31399
+ if (given === undefined) {
31400
+ return {
31401
+ ok: false,
31402
+ response: this.createErrorResponse("env must be a map of names to string values.", [
31403
+ 'Pass the variables this run needs, such as {"MY_FLAG": "1"}'
31404
+ ])
31405
+ };
31406
+ }
31407
+ const env = {};
31408
+ for (const [name, value] of Object.entries(given)) {
31409
+ if (typeof value !== "string") {
31410
+ return {
31411
+ ok: false,
31412
+ response: this.createErrorResponse(`env values must be strings, and ${name} is ${typeof value}.`, [
31413
+ 'Pass every value as a string, such as {"WORKERS": "4"}'
31414
+ ])
31415
+ };
31416
+ }
31417
+ if (name.toUpperCase().startsWith(RESERVED_VARIABLE_PREFIX)) {
31418
+ return {
31419
+ ok: false,
31420
+ response: this.createErrorResponse(`env may not set ${name}: names beginning with ${RESERVED_VARIABLE_PREFIX} are this server's own.`, [
31421
+ "GDHARNESS_RUNTIME_DIR is where the game announces itself and this server looks for it",
31422
+ "Name the directory for user:// with savesIn rather than by moving the environment yourself"
31423
+ ])
31424
+ };
31425
+ }
31426
+ env[name] = value;
31427
+ }
31428
+ return { ok: true, value: { ...savesIn === undefined ? {} : { savesIn }, env } };
31429
+ }
31367
31430
  async debuggedGame(intent) {
31368
31431
  const game = this.activeProcess;
31369
31432
  if (game !== null && game.exitCode !== null) {
@@ -32351,6 +32414,10 @@ class GodotServer {
32351
32414
  if (!given.ok) {
32352
32415
  return given.response;
32353
32416
  }
32417
+ const asked = this.runEnvironment(args);
32418
+ if (!asked.ok) {
32419
+ return asked.response;
32420
+ }
32354
32421
  if (!sceneToRun && !hasMainScene(project.value.file)) {
32355
32422
  return this.createErrorResponse("The project sets no main scene, so there is nothing to run.", [
32356
32423
  "Pass scene to run one scene",
@@ -32367,7 +32434,7 @@ class GodotServer {
32367
32434
  }
32368
32435
  const sceneArgument = sceneToRun?.ok ? sceneToRun.relativePath : null;
32369
32436
  if (op === "check") {
32370
- return await this.checkBoot(engine.value, project.value.path, sceneArgument, args, given.value);
32437
+ return await this.checkBoot(engine.value, project.value.path, sceneArgument, args, given.value, asked.value);
32371
32438
  }
32372
32439
  await this.pickUpWhatTheEditorIsPlaying();
32373
32440
  let ended = null;
@@ -32385,7 +32452,7 @@ class GodotServer {
32385
32452
  });
32386
32453
  const runtimeWaitMs = this.announceBudget(args, project.value.path);
32387
32454
  const editorWouldPlay = !headless || editorPlaysHeadless(project.value.file);
32388
- if (this.godotBridge.isConnected() && editorWouldPlay && given.value.length === 0) {
32455
+ if (this.godotBridge.isConnected() && editorWouldPlay && given.value.length === 0 && !needsItsOwnProcess(asked.value)) {
32389
32456
  return await this.playThroughEditor(sceneArgument, refreshed.value, project.value.path, alreadyPlaying, runtimeWaitMs, ended);
32390
32457
  }
32391
32458
  const cmdArgs = runArguments({
@@ -32395,10 +32462,10 @@ class GodotServer {
32395
32462
  userArgs: given.value
32396
32463
  });
32397
32464
  this.logDebug(`Running Godot project: ${engine.value} ${cmdArgs.join(" ")}`);
32398
- const started = this.spawnKeptGame(engine.value, cmdArgs, project.value.path);
32465
+ const started = this.spawnKeptGame(engine.value, cmdArgs, project.value.path, needsItsOwnProcess(asked.value) ? environmentFor({ ...asked.value, runtimeDirectory: runtimeDirectory() }) : undefined);
32399
32466
  started.announcedBefore = alreadyPlaying;
32400
32467
  this.activeProcess = started;
32401
- const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && given.value.length > 0 ? " The editor cannot be handed arguments for a game it plays, so this one was started " + "here: the debug_* tools answer only for a game the editor is playing." : "";
32468
+ const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && given.value.length > 0 ? " The editor cannot be handed arguments for a game it plays, so this one was started " + "here: the debug_* tools answer only for a game the editor is playing." : this.godotBridge.isConnected() && editorWouldPlay && needsItsOwnProcess(asked.value) ? " The editor plays a game with its own environment, so a run given one was started " + "here: the debug_* tools answer only for a game the editor is playing." : "";
32402
32469
  const endedForThis = endedToStartThis(ended);
32403
32470
  const stranded = wereHereFirst.filter((one) => one.pid !== ended?.pid && alive(one.pid));
32404
32471
  const named = stranded.map((one) => one.protocol === null ? `pid ${one.pid}` : `pid ${one.pid} on protocol ${one.protocol}`).join(", ");
@@ -32411,6 +32478,7 @@ class GodotServer {
32411
32478
  arguments: cmdArgs,
32412
32479
  transcript: started.transcript,
32413
32480
  refreshedClasses: refreshed.value,
32481
+ savesNote: asked.value.savesIn !== undefined && savesStayPut() ? SAVES_NOT_MOVED_NOTE : undefined,
32414
32482
  endedPreviousRun: endedPreviousRun(ended),
32415
32483
  runtime: await this.runtimeUp(project.value.path, alreadyPlaying, runtimeWaitMs, given.value.length > 0),
32416
32484
  message: `Use editor_output for what it prints and editor_run stop to end it.${spawnedInstead}${endedForThis}${alsoRunning}`
@@ -33017,11 +33085,11 @@ class GodotServer {
33017
33085
  currentRun() {
33018
33086
  return this.activeProcess ?? this.adoptRecordedRun();
33019
33087
  }
33020
- async checkBoot(godotPath, projectPath, scene, args, userArgs) {
33088
+ async checkBoot(godotPath, projectPath, scene, args, userArgs, asked = {}) {
33021
33089
  const frames = readPositiveNumber(args, "frames") ?? 3;
33022
33090
  const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 60000;
33023
33091
  const cmdArgs = runArguments({ projectPath, headless: true, scene, quitAfter: frames, userArgs });
33024
- const boot = this.spawnGame(godotPath, cmdArgs);
33092
+ const boot = this.spawnGame(godotPath, cmdArgs, needsItsOwnProcess(asked) ? environmentFor({ ...asked, runtimeDirectory: runtimeDirectory() }) : undefined);
33025
33093
  const hung = await new Promise((resolve) => {
33026
33094
  const timer = setTimeout(() => {
33027
33095
  boot.process.kill();
@@ -33046,6 +33114,7 @@ class GodotServer {
33046
33114
  frames,
33047
33115
  errors,
33048
33116
  warnings,
33117
+ savesNote: asked.savesIn !== undefined && savesStayPut() ? SAVES_NOT_MOVED_NOTE : undefined,
33049
33118
  entries: forAnswer(boot.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries)
33050
33119
  });
33051
33120
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "1.0.9",
3
+ "version": "1.0.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",