gdharness 0.12.12 → 0.12.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
@@ -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.',
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 < 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 = Date.now();
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) {
@@ -36238,7 +36244,12 @@ function windowsImage(pid) {
36238
36244
  }
36239
36245
  }
36240
36246
  function stillTheRecordedRun(record) {
36241
- const running = runningAs(record.pid);
36247
+ return judgeRun(record, runningAs(record.pid), "confirmed");
36248
+ }
36249
+ function couldStillBeTheRecordedRun(record) {
36250
+ return judgeRun(record, runningAs(record.pid), "possible");
36251
+ }
36252
+ function judgeRun(record, running, needed) {
36242
36253
  if (running === null) {
36243
36254
  return false;
36244
36255
  }
@@ -36248,9 +36259,12 @@ function stillTheRecordedRun(record) {
36248
36259
  if (wanted !== null && !said.includes(wanted)) {
36249
36260
  return false;
36250
36261
  }
36251
- if (running.kind === "image" || record.projectPath === "") {
36262
+ if (record.projectPath === "") {
36252
36263
  return wanted !== null;
36253
36264
  }
36265
+ if (running.kind === "image") {
36266
+ return needed === "possible" && wanted !== null;
36267
+ }
36254
36268
  const project = process.platform === "win32" ? record.projectPath.toLowerCase() : record.projectPath;
36255
36269
  return said.includes(project);
36256
36270
  }
@@ -36324,6 +36338,7 @@ var init_setup = __esm(() => {
36324
36338
 
36325
36339
  // src/server.ts
36326
36340
  import { execFile as execFile5, spawn } from "node:child_process";
36341
+ import { randomUUID as randomUUID2 } from "node:crypto";
36327
36342
  import {
36328
36343
  closeSync,
36329
36344
  existsSync as existsSync13,
@@ -37398,7 +37413,8 @@ class GodotServer {
37398
37413
  if (!classes.ok) {
37399
37414
  return this.answer(classes);
37400
37415
  }
37401
- const reports = "res://.godot/gdharness-reports";
37416
+ const ours = `gdharness-reports/${randomUUID2()}`;
37417
+ const reports = `res://.godot/${ours}`;
37402
37418
  const ignored = readStringArray(args, "ignore") ?? [];
37403
37419
  const asked = readString(contained.value, "path") ?? "res://test";
37404
37420
  const cmdArgs = [
@@ -37435,7 +37451,7 @@ class GodotServer {
37435
37451
  resolve(false);
37436
37452
  });
37437
37453
  });
37438
- const reportsDir = join16(project.value.path, ".godot", "gdharness-reports");
37454
+ const reportsDir = join16(project.value.path, ".godot", ...ours.split("/"));
37439
37455
  let report = null;
37440
37456
  let reportProblem = null;
37441
37457
  try {
@@ -37599,6 +37615,7 @@ class GodotServer {
37599
37615
  serverVersion: SERVER_VERSION,
37600
37616
  addonIsStale: status.connected ? stale : undefined,
37601
37617
  bridgeAvailable: this.bridgeStartupError === null,
37618
+ mayYetConnect: status.connected ? undefined : mayYetConnect(status.listeningSince),
37602
37619
  startupError: this.bridgeStartupError,
37603
37620
  staleNote: stale ? addonMismatch(status.addonVersion, SERVER_VERSION) : undefined,
37604
37621
  retryingBridge: this.bridgeRetry === null ? undefined : true,
@@ -38086,7 +38103,7 @@ class GodotServer {
38086
38103
  brokeOn: null
38087
38104
  };
38088
38105
  this.drainTranscript(adopted);
38089
- if (!alive(record.pid) || !stillTheRecordedRun(record)) {
38106
+ if (!alive(record.pid) || !couldStillBeTheRecordedRun(record)) {
38090
38107
  adopted.log.finish();
38091
38108
  adopted.exitCode = null;
38092
38109
  adopted.endedUnwatched = true;
@@ -39258,6 +39275,32 @@ function registered(harness, projectPath) {
39258
39275
  const held = existing[harness.container];
39259
39276
  return typeof held === "object" && held !== null && !Array.isArray(held) && SERVER_KEY in held;
39260
39277
  }
39278
+ function recordedEnginePath(projectPath) {
39279
+ for (const harness of HARNESSES) {
39280
+ if (harness.scope !== "project" || harness.toml || harness.yaml || harness.snippet) {
39281
+ continue;
39282
+ }
39283
+ const path = configPath(harness, projectPath);
39284
+ if (!existsSync4(path)) {
39285
+ continue;
39286
+ }
39287
+ let parsed;
39288
+ try {
39289
+ parsed = JSON.parse(readFileSync(path, "utf8"));
39290
+ } catch {
39291
+ continue;
39292
+ }
39293
+ const entry = reach(reach(parsed, harness.container), SERVER_KEY);
39294
+ const named = reach(reach(entry, harness.shape === "opencode" ? "environment" : "env"), "GODOT_PATH");
39295
+ if (typeof named === "string" && named !== "" && existsSync4(named)) {
39296
+ return named;
39297
+ }
39298
+ }
39299
+ return null;
39300
+ }
39301
+ function reach(held, key) {
39302
+ return typeof held === "object" && held !== null && !Array.isArray(held) ? held[key] : undefined;
39303
+ }
39261
39304
  function disconnect(harness, projectPath) {
39262
39305
  const path = configPath(harness, projectPath);
39263
39306
  if (!existsSync4(path)) {
@@ -40047,8 +40090,8 @@ function projectArgument(at) {
40047
40090
  }
40048
40091
  return projectPath;
40049
40092
  }
40050
- async function engine() {
40051
- const godotPath = await new GodotLocator().find();
40093
+ async function engine(projectPath) {
40094
+ const godotPath = await new GodotLocator().find() ?? (projectPath === undefined ? null : recordedEnginePath(projectPath));
40052
40095
  if (godotPath === null) {
40053
40096
  throw new UsageError(`No Godot executable found. ${GodotLocator.ADVICE.join(". ")}.`);
40054
40097
  }
@@ -40171,7 +40214,7 @@ export, so turn it off before you ship: gdharness runtime off${where}`);
40171
40214
  async function setup() {
40172
40215
  const projectPath = projectArgument(1);
40173
40216
  const runtime = !args.includes("--no-runtime");
40174
- const godot = await engine();
40217
+ const godot = await engine(projectPath);
40175
40218
  for (const addon of installAddons(projectPath)) {
40176
40219
  console.log(`${addon.replaced ? "replaced" : "installed"} ${addon.path}`);
40177
40220
  }
@@ -40221,7 +40264,7 @@ async function setup() {
40221
40264
  }
40222
40265
  async function uninstall() {
40223
40266
  const projectPath = projectArgument(1);
40224
- const godot = await engine();
40267
+ const godot = await engine(projectPath);
40225
40268
  const named = namedHarnesses();
40226
40269
  const from = named.length > 0 ? named : HARNESSES;
40227
40270
  for (const harness of from) {
@@ -40272,8 +40315,8 @@ async function upgrade() {
40272
40315
  const where = projectPath === process.cwd() ? "" : ` ${projectPath}`;
40273
40316
  throw new UsageError(`gdharness is not installed in ${projectPath}. Run gdharness setup${where} instead.`);
40274
40317
  }
40318
+ const godot = await engine(projectPath);
40275
40319
  console.log(installed === version ? `already ${version}; reinstalling` : `${installed} -> ${version}`);
40276
- const godot = await engine();
40277
40320
  for (const addon of installAddons(projectPath)) {
40278
40321
  console.log(`${addon.replaced ? "replaced" : "installed"} ${addon.path}`);
40279
40322
  }
@@ -40331,7 +40374,7 @@ async function runtime3() {
40331
40374
  throw new UsageError("gdharness runtime takes on or off, then the project directory.");
40332
40375
  }
40333
40376
  const projectPath = projectArgument(2);
40334
- const godot = await engine();
40377
+ const godot = await engine(projectPath);
40335
40378
  if (state === "on") {
40336
40379
  await registerRuntime(godot, projectPath);
40337
40380
  return;
@@ -40353,7 +40396,7 @@ async function runtime3() {
40353
40396
  }
40354
40397
  async function classes() {
40355
40398
  const projectPath = projectArgument(1);
40356
- const godot = await engine();
40399
+ const godot = await engine(projectPath);
40357
40400
  const outcome = await runOperation(godot, "refresh_class_cache", {}, projectPath);
40358
40401
  said2(outcome, "rebuilding the class list");
40359
40402
  if (outcome.ok) {
package/build/index.js CHANGED
@@ -10132,6 +10132,7 @@ function defectReport(where, error, godotVersion) {
10132
10132
 
10133
10133
  // src/server.ts
10134
10134
  import { execFile as execFile3, spawn } from "node:child_process";
10135
+ import { randomUUID as randomUUID2 } from "node:crypto";
10135
10136
  import {
10136
10137
  closeSync,
10137
10138
  existsSync as existsSync8,
@@ -25610,8 +25611,13 @@ function resolveDefaultBridgeHost() {
25610
25611
  function servedPort(said) {
25611
25612
  return typeof said === "number" && Number.isInteger(said) && said > 0 && said <= 65535 ? said : undefined;
25612
25613
  }
25614
+ var CONNECT_WINDOW_MS = 35000;
25615
+ function mayYetConnect(listeningSince, now = Date.now()) {
25616
+ return listeningSince === undefined || now - listeningSince < CONNECT_WINDOW_MS;
25617
+ }
25613
25618
 
25614
25619
  class GodotBridge extends EventEmitter {
25620
+ listeningSince = null;
25615
25621
  httpServer = null;
25616
25622
  godotWss = null;
25617
25623
  socket = null;
@@ -25674,6 +25680,7 @@ class GodotBridge extends EventEmitter {
25674
25680
  });
25675
25681
  server.once("listening", () => {
25676
25682
  settled = true;
25683
+ this.listeningSince = Date.now();
25677
25684
  this.httpServer = server;
25678
25685
  this.godotWss = godotWss;
25679
25686
  const bound = server.address();
@@ -25750,7 +25757,8 @@ class GodotBridge extends EventEmitter {
25750
25757
  debugPort: this.connectionInfo?.debugPort,
25751
25758
  openedByAServer: this.connectionInfo?.openedByAServer,
25752
25759
  pendingRequests: this.pendingRequests.size,
25753
- queuedResources: this.resourceQueues.size
25760
+ queuedResources: this.resourceQueues.size,
25761
+ listeningSince: this.listeningSince ?? undefined
25754
25762
  };
25755
25763
  }
25756
25764
  invokeTool(toolName, args) {
@@ -27918,7 +27926,12 @@ function windowsImage(pid) {
27918
27926
  }
27919
27927
  }
27920
27928
  function stillTheRecordedRun(record) {
27921
- const running = runningAs(record.pid);
27929
+ return judgeRun(record, runningAs(record.pid), "confirmed");
27930
+ }
27931
+ function couldStillBeTheRecordedRun(record) {
27932
+ return judgeRun(record, runningAs(record.pid), "possible");
27933
+ }
27934
+ function judgeRun(record, running, needed) {
27922
27935
  if (running === null) {
27923
27936
  return false;
27924
27937
  }
@@ -27928,9 +27941,12 @@ function stillTheRecordedRun(record) {
27928
27941
  if (wanted !== null && !said.includes(wanted)) {
27929
27942
  return false;
27930
27943
  }
27931
- if (running.kind === "image" || record.projectPath === "") {
27944
+ if (record.projectPath === "") {
27932
27945
  return wanted !== null;
27933
27946
  }
27947
+ if (running.kind === "image") {
27948
+ return needed === "possible" && wanted !== null;
27949
+ }
27934
27950
  const project = process.platform === "win32" ? record.projectPath.toLowerCase() : record.projectPath;
27935
27951
  return said.includes(project);
27936
27952
  }
@@ -28646,7 +28662,7 @@ var TOOL_SPECS = [
28646
28662
  },
28647
28663
  {
28648
28664
  name: "editor_status",
28649
- 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.',
28650
28666
  parameters: {},
28651
28667
  requires: []
28652
28668
  },
@@ -30180,7 +30196,8 @@ class GodotServer {
30180
30196
  if (!classes.ok) {
30181
30197
  return this.answer(classes);
30182
30198
  }
30183
- const reports = "res://.godot/gdharness-reports";
30199
+ const ours = `gdharness-reports/${randomUUID2()}`;
30200
+ const reports = `res://.godot/${ours}`;
30184
30201
  const ignored = readStringArray(args, "ignore") ?? [];
30185
30202
  const asked = readString(contained.value, "path") ?? "res://test";
30186
30203
  const cmdArgs = [
@@ -30217,7 +30234,7 @@ class GodotServer {
30217
30234
  resolve(false);
30218
30235
  });
30219
30236
  });
30220
- const reportsDir = join10(project.value.path, ".godot", "gdharness-reports");
30237
+ const reportsDir = join10(project.value.path, ".godot", ...ours.split("/"));
30221
30238
  let report = null;
30222
30239
  let reportProblem = null;
30223
30240
  try {
@@ -30381,6 +30398,7 @@ class GodotServer {
30381
30398
  serverVersion: SERVER_VERSION,
30382
30399
  addonIsStale: status.connected ? stale : undefined,
30383
30400
  bridgeAvailable: this.bridgeStartupError === null,
30401
+ mayYetConnect: status.connected ? undefined : mayYetConnect(status.listeningSince),
30384
30402
  startupError: this.bridgeStartupError,
30385
30403
  staleNote: stale ? addonMismatch(status.addonVersion, SERVER_VERSION) : undefined,
30386
30404
  retryingBridge: this.bridgeRetry === null ? undefined : true,
@@ -30868,7 +30886,7 @@ class GodotServer {
30868
30886
  brokeOn: null
30869
30887
  };
30870
30888
  this.drainTranscript(adopted);
30871
- if (!alive(record.pid) || !stillTheRecordedRun(record)) {
30889
+ if (!alive(record.pid) || !couldStillBeTheRecordedRun(record)) {
30872
30890
  adopted.log.finish();
30873
30891
  adopted.exitCode = null;
30874
30892
  adopted.endedUnwatched = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "0.12.12",
3
+ "version": "0.12.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",