gdharness 0.12.13 → 0.12.15

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
@@ -15759,7 +15759,7 @@ var init_tool_definitions = __esm(() => {
15759
15759
  },
15760
15760
  {
15761
15761
  name: "editor_status",
15762
- description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable.",
15762
+ description: 'Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means "not reached yet" as often as it means "no editor". True is worth waiting out; false is an editor that is not there. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read.',
15763
15763
  parameters: {},
15764
15764
  requires: []
15765
15765
  },
@@ -34405,17 +34405,21 @@ function resolveDefaultBridgeHost() {
34405
34405
  function servedPort(said) {
34406
34406
  return typeof said === "number" && Number.isInteger(said) && said > 0 && said <= 65535 ? said : undefined;
34407
34407
  }
34408
+ function mayYetConnect(listeningSince, now = Date.now()) {
34409
+ return listeningSince === undefined || now - listeningSince.getTime() < CONNECT_WINDOW_MS;
34410
+ }
34408
34411
  function getDefaultBridge(ownProject = null) {
34409
34412
  defaultBridge ??= new GodotBridge(resolveDefaultBridgePort(), resolveDefaultBridgeHost(), DEFAULT_TIMEOUT_MS, ownProject);
34410
34413
  return defaultBridge;
34411
34414
  }
34412
- var DEFAULT_PORT = 6505, DEFAULT_HOST = "127.0.0.1", DEFAULT_TIMEOUT_MS = 30000, KEEPALIVE_INTERVAL_MS = 1e4, SECOND_CONNECTION_CLOSE_CODE = 4000, OTHER_PROJECT_CLOSE_CODE = 4001, GodotBridge, defaultBridge = null;
34415
+ var DEFAULT_PORT = 6505, DEFAULT_HOST = "127.0.0.1", DEFAULT_TIMEOUT_MS = 30000, KEEPALIVE_INTERVAL_MS = 1e4, SECOND_CONNECTION_CLOSE_CODE = 4000, OTHER_PROJECT_CLOSE_CODE = 4001, CONNECT_WINDOW_MS = 35000, GodotBridge, defaultBridge = null;
34413
34416
  var init_godot_bridge = __esm(() => {
34414
34417
  init_wrapper();
34415
34418
  init_errors();
34416
34419
  init_paths();
34417
34420
  init_ports();
34418
34421
  GodotBridge = class GodotBridge extends EventEmitter {
34422
+ listeningSince = null;
34419
34423
  httpServer = null;
34420
34424
  godotWss = null;
34421
34425
  socket = null;
@@ -34478,6 +34482,7 @@ var init_godot_bridge = __esm(() => {
34478
34482
  });
34479
34483
  server.once("listening", () => {
34480
34484
  settled = true;
34485
+ this.listeningSince = new Date;
34481
34486
  this.httpServer = server;
34482
34487
  this.godotWss = godotWss;
34483
34488
  const bound = server.address();
@@ -34554,7 +34559,8 @@ var init_godot_bridge = __esm(() => {
34554
34559
  debugPort: this.connectionInfo?.debugPort,
34555
34560
  openedByAServer: this.connectionInfo?.openedByAServer,
34556
34561
  pendingRequests: this.pendingRequests.size,
34557
- queuedResources: this.resourceQueues.size
34562
+ queuedResources: this.resourceQueues.size,
34563
+ listeningSince: this.listeningSince ?? undefined
34558
34564
  };
34559
34565
  }
34560
34566
  invokeTool(toolName, args) {
@@ -37337,8 +37343,11 @@ class GodotServer {
37337
37343
  return this.createErrorResponse(`Cannot create ${dirname9(output.relativePath)} to export into: ${errorMessage(error)}`);
37338
37344
  }
37339
37345
  const debug = readBoolean(args, "debug") ?? false;
37346
+ const exportLogs = mkdtempSync3(join16(tmpdir5(), "gdharness-export-"));
37340
37347
  const exportArgs = [
37341
37348
  "--headless",
37349
+ "--log-file",
37350
+ join16(exportLogs, "engine.log"),
37342
37351
  "--path",
37343
37352
  project.value.path,
37344
37353
  debug ? "--export-debug" : "--export-release",
@@ -37360,6 +37369,8 @@ class GodotServer {
37360
37369
  log.append("stdout", failed.stdout);
37361
37370
  log.append("stderr", failed.stderr);
37362
37371
  exitCode = typeof failed.code === "number" ? failed.code : -1;
37372
+ } finally {
37373
+ rmSync9(exportLogs, { recursive: true, force: true });
37363
37374
  }
37364
37375
  log.finish();
37365
37376
  const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
@@ -37609,6 +37620,7 @@ class GodotServer {
37609
37620
  serverVersion: SERVER_VERSION,
37610
37621
  addonIsStale: status.connected ? stale : undefined,
37611
37622
  bridgeAvailable: this.bridgeStartupError === null,
37623
+ mayYetConnect: status.connected ? undefined : mayYetConnect(status.listeningSince),
37612
37624
  startupError: this.bridgeStartupError,
37613
37625
  staleNote: stale ? addonMismatch(status.addonVersion, SERVER_VERSION) : undefined,
37614
37626
  retryingBridge: this.bridgeRetry === null ? undefined : true,
@@ -39268,6 +39280,32 @@ function registered(harness, projectPath) {
39268
39280
  const held = existing[harness.container];
39269
39281
  return typeof held === "object" && held !== null && !Array.isArray(held) && SERVER_KEY in held;
39270
39282
  }
39283
+ function recordedEnginePath(projectPath) {
39284
+ for (const harness of HARNESSES) {
39285
+ if (harness.scope !== "project" || harness.toml || harness.yaml || harness.snippet) {
39286
+ continue;
39287
+ }
39288
+ const path = configPath(harness, projectPath);
39289
+ if (!existsSync4(path)) {
39290
+ continue;
39291
+ }
39292
+ let parsed;
39293
+ try {
39294
+ parsed = JSON.parse(readFileSync(path, "utf8"));
39295
+ } catch {
39296
+ continue;
39297
+ }
39298
+ const entry = reach(reach(parsed, harness.container), SERVER_KEY);
39299
+ const named = reach(reach(entry, harness.shape === "opencode" ? "environment" : "env"), "GODOT_PATH");
39300
+ if (typeof named === "string" && named !== "" && existsSync4(named)) {
39301
+ return named;
39302
+ }
39303
+ }
39304
+ return null;
39305
+ }
39306
+ function reach(held, key) {
39307
+ return typeof held === "object" && held !== null && !Array.isArray(held) ? held[key] : undefined;
39308
+ }
39271
39309
  function disconnect(harness, projectPath) {
39272
39310
  const path = configPath(harness, projectPath);
39273
39311
  if (!existsSync4(path)) {
@@ -40057,8 +40095,8 @@ function projectArgument(at) {
40057
40095
  }
40058
40096
  return projectPath;
40059
40097
  }
40060
- async function engine() {
40061
- const godotPath = await new GodotLocator().find();
40098
+ async function engine(projectPath) {
40099
+ const godotPath = await new GodotLocator().find() ?? (projectPath === undefined ? null : recordedEnginePath(projectPath));
40062
40100
  if (godotPath === null) {
40063
40101
  throw new UsageError(`No Godot executable found. ${GodotLocator.ADVICE.join(". ")}.`);
40064
40102
  }
@@ -40181,7 +40219,7 @@ export, so turn it off before you ship: gdharness runtime off${where}`);
40181
40219
  async function setup() {
40182
40220
  const projectPath = projectArgument(1);
40183
40221
  const runtime = !args.includes("--no-runtime");
40184
- const godot = await engine();
40222
+ const godot = await engine(projectPath);
40185
40223
  for (const addon of installAddons(projectPath)) {
40186
40224
  console.log(`${addon.replaced ? "replaced" : "installed"} ${addon.path}`);
40187
40225
  }
@@ -40231,7 +40269,7 @@ async function setup() {
40231
40269
  }
40232
40270
  async function uninstall() {
40233
40271
  const projectPath = projectArgument(1);
40234
- const godot = await engine();
40272
+ const godot = await engine(projectPath);
40235
40273
  const named = namedHarnesses();
40236
40274
  const from = named.length > 0 ? named : HARNESSES;
40237
40275
  for (const harness of from) {
@@ -40282,8 +40320,8 @@ async function upgrade() {
40282
40320
  const where = projectPath === process.cwd() ? "" : ` ${projectPath}`;
40283
40321
  throw new UsageError(`gdharness is not installed in ${projectPath}. Run gdharness setup${where} instead.`);
40284
40322
  }
40323
+ const godot = await engine(projectPath);
40285
40324
  console.log(installed === version ? `already ${version}; reinstalling` : `${installed} -> ${version}`);
40286
- const godot = await engine();
40287
40325
  for (const addon of installAddons(projectPath)) {
40288
40326
  console.log(`${addon.replaced ? "replaced" : "installed"} ${addon.path}`);
40289
40327
  }
@@ -40341,7 +40379,7 @@ async function runtime3() {
40341
40379
  throw new UsageError("gdharness runtime takes on or off, then the project directory.");
40342
40380
  }
40343
40381
  const projectPath = projectArgument(2);
40344
- const godot = await engine();
40382
+ const godot = await engine(projectPath);
40345
40383
  if (state === "on") {
40346
40384
  await registerRuntime(godot, projectPath);
40347
40385
  return;
@@ -40363,7 +40401,7 @@ async function runtime3() {
40363
40401
  }
40364
40402
  async function classes() {
40365
40403
  const projectPath = projectArgument(1);
40366
- const godot = await engine();
40404
+ const godot = await engine(projectPath);
40367
40405
  const outcome = await runOperation(godot, "refresh_class_cache", {}, projectPath);
40368
40406
  said2(outcome, "rebuilding the class list");
40369
40407
  if (outcome.ok) {
package/build/index.js CHANGED
@@ -25611,8 +25611,13 @@ function resolveDefaultBridgeHost() {
25611
25611
  function servedPort(said) {
25612
25612
  return typeof said === "number" && Number.isInteger(said) && said > 0 && said <= 65535 ? said : undefined;
25613
25613
  }
25614
+ var CONNECT_WINDOW_MS = 35000;
25615
+ function mayYetConnect(listeningSince, now = Date.now()) {
25616
+ return listeningSince === undefined || now - listeningSince.getTime() < CONNECT_WINDOW_MS;
25617
+ }
25614
25618
 
25615
25619
  class GodotBridge extends EventEmitter {
25620
+ listeningSince = null;
25616
25621
  httpServer = null;
25617
25622
  godotWss = null;
25618
25623
  socket = null;
@@ -25675,6 +25680,7 @@ class GodotBridge extends EventEmitter {
25675
25680
  });
25676
25681
  server.once("listening", () => {
25677
25682
  settled = true;
25683
+ this.listeningSince = new Date;
25678
25684
  this.httpServer = server;
25679
25685
  this.godotWss = godotWss;
25680
25686
  const bound = server.address();
@@ -25751,7 +25757,8 @@ class GodotBridge extends EventEmitter {
25751
25757
  debugPort: this.connectionInfo?.debugPort,
25752
25758
  openedByAServer: this.connectionInfo?.openedByAServer,
25753
25759
  pendingRequests: this.pendingRequests.size,
25754
- queuedResources: this.resourceQueues.size
25760
+ queuedResources: this.resourceQueues.size,
25761
+ listeningSince: this.listeningSince ?? undefined
25755
25762
  };
25756
25763
  }
25757
25764
  invokeTool(toolName, args) {
@@ -28655,7 +28662,7 @@ var TOOL_SPECS = [
28655
28662
  },
28656
28663
  {
28657
28664
  name: "editor_status",
28658
- description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable.",
28665
+ description: 'Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means "not reached yet" as often as it means "no editor". True is worth waiting out; false is an editor that is not there. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read.',
28659
28666
  parameters: {},
28660
28667
  requires: []
28661
28668
  },
@@ -30119,8 +30126,11 @@ class GodotServer {
30119
30126
  return this.createErrorResponse(`Cannot create ${dirname4(output.relativePath)} to export into: ${errorMessage(error)}`);
30120
30127
  }
30121
30128
  const debug = readBoolean(args, "debug") ?? false;
30129
+ const exportLogs = mkdtempSync2(join10(tmpdir4(), "gdharness-export-"));
30122
30130
  const exportArgs = [
30123
30131
  "--headless",
30132
+ "--log-file",
30133
+ join10(exportLogs, "engine.log"),
30124
30134
  "--path",
30125
30135
  project.value.path,
30126
30136
  debug ? "--export-debug" : "--export-release",
@@ -30142,6 +30152,8 @@ class GodotServer {
30142
30152
  log.append("stdout", failed.stdout);
30143
30153
  log.append("stderr", failed.stderr);
30144
30154
  exitCode = typeof failed.code === "number" ? failed.code : -1;
30155
+ } finally {
30156
+ rmSync5(exportLogs, { recursive: true, force: true });
30145
30157
  }
30146
30158
  log.finish();
30147
30159
  const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
@@ -30391,6 +30403,7 @@ class GodotServer {
30391
30403
  serverVersion: SERVER_VERSION,
30392
30404
  addonIsStale: status.connected ? stale : undefined,
30393
30405
  bridgeAvailable: this.bridgeStartupError === null,
30406
+ mayYetConnect: status.connected ? undefined : mayYetConnect(status.listeningSince),
30394
30407
  startupError: this.bridgeStartupError,
30395
30408
  staleNote: stale ? addonMismatch(status.addonVersion, SERVER_VERSION) : undefined,
30396
30409
  retryingBridge: this.bridgeRetry === null ? undefined : true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "0.12.13",
3
+ "version": "0.12.15",
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",