gdharness 0.5.2 → 0.5.3

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
@@ -35460,32 +35460,41 @@ function parseAnnouncement(file, pid) {
35460
35460
  try {
35461
35461
  fields = asParams(JSON.parse(readFileSync10(file, "utf8")));
35462
35462
  } catch {
35463
- return null;
35463
+ return { kind: "rubbish" };
35464
35464
  }
35465
35465
  const project = readParams(fields, "project");
35466
35466
  const port = readNumber(fields, "port");
35467
35467
  const address = readString(fields, "address");
35468
- if (readNumber(fields, "protocol") !== RUNTIME_PROTOCOL || readNumber(fields, "pid") !== pid || port === undefined || !Number.isInteger(port) || port < 1 || port > 65535 || address === undefined || project === undefined) {
35469
- return null;
35468
+ const protocol = readNumber(fields, "protocol");
35469
+ if (readNumber(fields, "pid") !== pid || port === undefined || !Number.isInteger(port) || port < 1 || port > 65535 || address === undefined || project === undefined) {
35470
+ return { kind: "rubbish" };
35470
35471
  }
35471
- return {
35472
- pid,
35473
- port,
35474
- address,
35475
- project: { name: readString(project, "name") ?? "", path: readString(project, "path") ?? "" },
35476
- file
35472
+ const named = {
35473
+ name: readString(project, "name") ?? "",
35474
+ path: readString(project, "path") ?? ""
35477
35475
  };
35476
+ if (protocol !== RUNTIME_PROTOCOL) {
35477
+ return { kind: "unspoken", unspoken: { pid, protocol: protocol ?? 0, project: named } };
35478
+ }
35479
+ return { kind: "runtime", endpoint: { pid, port, address, project: named, file } };
35478
35480
  }
35479
- function discoverRuntimes(directories = runtimeDirectories()) {
35480
- const found = new Map;
35481
+ function runtimesAnnounced(directories = runtimeDirectories()) {
35482
+ const running = new Map;
35483
+ const unspoken = new Map;
35481
35484
  for (const directory of directories) {
35482
- for (const endpoint of announcedIn(directory)) {
35483
- if (!found.has(endpoint.pid)) {
35484
- found.set(endpoint.pid, endpoint);
35485
+ for (const found of announcedIn(directory)) {
35486
+ if (found.kind === "runtime" && !running.has(found.endpoint.pid)) {
35487
+ running.set(found.endpoint.pid, found.endpoint);
35488
+ } else if (found.kind === "unspoken" && !unspoken.has(found.unspoken.pid)) {
35489
+ unspoken.set(found.unspoken.pid, found.unspoken);
35485
35490
  }
35486
35491
  }
35487
35492
  }
35488
- return [...found.values()].sort((a, b) => b.pid - a.pid);
35493
+ const newest = (a, b) => b.pid - a.pid;
35494
+ return { running: [...running.values()].sort(newest), unspoken: [...unspoken.values()].sort(newest) };
35495
+ }
35496
+ function discoverRuntimes(directories = runtimeDirectories()) {
35497
+ return runtimesAnnounced(directories).running;
35489
35498
  }
35490
35499
  function announcedIn(directory) {
35491
35500
  if (!existsSync9(directory)) {
@@ -35499,22 +35508,30 @@ function announcedIn(directory) {
35499
35508
  }
35500
35509
  const file = join11(directory, entry);
35501
35510
  const pid = Number.parseInt(match[1] ?? "", 10);
35502
- const endpoint = processAlive(pid) ? parseAnnouncement(file, pid) : null;
35503
- if (endpoint) {
35504
- found.push(endpoint);
35505
- } else {
35511
+ const announced = processAlive(pid) ? parseAnnouncement(file, pid) : { kind: "rubbish" };
35512
+ if (announced.kind === "rubbish") {
35506
35513
  try {
35507
35514
  unlinkSync(file);
35508
35515
  } catch {}
35516
+ continue;
35509
35517
  }
35518
+ found.push(announced);
35510
35519
  }
35511
35520
  return found;
35512
35521
  }
35513
35522
  function describe2(endpoint) {
35514
35523
  return `pid ${endpoint.pid} on ${endpoint.address}:${endpoint.port} (${endpoint.project.name || "unnamed"} at ${endpoint.project.path})`;
35515
35524
  }
35516
- function chooseRuntime(endpoints, projectPath) {
35525
+ function tooNew(unspoken) {
35526
+ const named = unspoken.map((game) => `pid ${game.pid} speaks ${game.protocol} (${game.project.name || "unnamed"} at ${game.project.path})`).join("; ");
35527
+ const half = unspoken.some((game) => game.protocol > RUNTIME_PROTOCOL) ? "this server is the older half: reconnect it so it spawns the installed version" : "the addon is the older half: reinstall it and restart the game";
35528
+ return `A game is running, in a protocol this server does not speak ${named}. This server speaks ${RUNTIME_PROTOCOL}, so ${half}.`;
35529
+ }
35530
+ function chooseRuntime(endpoints, projectPath, unspoken = []) {
35517
35531
  if (endpoints.length === 0) {
35532
+ if (unspoken.length > 0) {
35533
+ return { problem: tooNew(unspoken) };
35534
+ }
35518
35535
  return {
35519
35536
  problem: "No game with the runtime addon is running. Start one with editor_run, or play the project from the editor with the addon enabled."
35520
35537
  };
@@ -36965,7 +36982,8 @@ class GodotServer {
36965
36982
  }
36966
36983
  async handleRuntimeCommand(command, args, timeoutMs = this.runtimeTimeoutMs()) {
36967
36984
  const { op: _op, projectPath, ...params } = asParams(args);
36968
- const choice = chooseRuntime(discoverRuntimes(), typeof projectPath === "string" ? projectPath : undefined);
36985
+ const announced = runtimesAnnounced();
36986
+ const choice = chooseRuntime(announced.running, typeof projectPath === "string" ? projectPath : undefined, announced.unspoken);
36969
36987
  if ("problem" in choice) {
36970
36988
  return this.createErrorResponse(choice.problem);
36971
36989
  }
package/build/index.js CHANGED
@@ -27308,32 +27308,41 @@ function parseAnnouncement(file, pid) {
27308
27308
  try {
27309
27309
  fields = asParams(JSON.parse(readFileSync6(file, "utf8")));
27310
27310
  } catch {
27311
- return null;
27311
+ return { kind: "rubbish" };
27312
27312
  }
27313
27313
  const project = readParams(fields, "project");
27314
27314
  const port = readNumber(fields, "port");
27315
27315
  const address = readString(fields, "address");
27316
- if (readNumber(fields, "protocol") !== RUNTIME_PROTOCOL || readNumber(fields, "pid") !== pid || port === undefined || !Number.isInteger(port) || port < 1 || port > 65535 || address === undefined || project === undefined) {
27317
- return null;
27316
+ const protocol = readNumber(fields, "protocol");
27317
+ if (readNumber(fields, "pid") !== pid || port === undefined || !Number.isInteger(port) || port < 1 || port > 65535 || address === undefined || project === undefined) {
27318
+ return { kind: "rubbish" };
27318
27319
  }
27319
- return {
27320
- pid,
27321
- port,
27322
- address,
27323
- project: { name: readString(project, "name") ?? "", path: readString(project, "path") ?? "" },
27324
- file
27320
+ const named = {
27321
+ name: readString(project, "name") ?? "",
27322
+ path: readString(project, "path") ?? ""
27325
27323
  };
27324
+ if (protocol !== RUNTIME_PROTOCOL) {
27325
+ return { kind: "unspoken", unspoken: { pid, protocol: protocol ?? 0, project: named } };
27326
+ }
27327
+ return { kind: "runtime", endpoint: { pid, port, address, project: named, file } };
27326
27328
  }
27327
- function discoverRuntimes(directories = runtimeDirectories()) {
27328
- const found = new Map;
27329
+ function runtimesAnnounced(directories = runtimeDirectories()) {
27330
+ const running = new Map;
27331
+ const unspoken = new Map;
27329
27332
  for (const directory of directories) {
27330
- for (const endpoint of announcedIn(directory)) {
27331
- if (!found.has(endpoint.pid)) {
27332
- found.set(endpoint.pid, endpoint);
27333
+ for (const found of announcedIn(directory)) {
27334
+ if (found.kind === "runtime" && !running.has(found.endpoint.pid)) {
27335
+ running.set(found.endpoint.pid, found.endpoint);
27336
+ } else if (found.kind === "unspoken" && !unspoken.has(found.unspoken.pid)) {
27337
+ unspoken.set(found.unspoken.pid, found.unspoken);
27333
27338
  }
27334
27339
  }
27335
27340
  }
27336
- return [...found.values()].sort((a, b) => b.pid - a.pid);
27341
+ const newest = (a, b) => b.pid - a.pid;
27342
+ return { running: [...running.values()].sort(newest), unspoken: [...unspoken.values()].sort(newest) };
27343
+ }
27344
+ function discoverRuntimes(directories = runtimeDirectories()) {
27345
+ return runtimesAnnounced(directories).running;
27337
27346
  }
27338
27347
  function announcedIn(directory) {
27339
27348
  if (!existsSync5(directory)) {
@@ -27347,22 +27356,30 @@ function announcedIn(directory) {
27347
27356
  }
27348
27357
  const file = join6(directory, entry);
27349
27358
  const pid = Number.parseInt(match[1] ?? "", 10);
27350
- const endpoint = processAlive(pid) ? parseAnnouncement(file, pid) : null;
27351
- if (endpoint) {
27352
- found.push(endpoint);
27353
- } else {
27359
+ const announced = processAlive(pid) ? parseAnnouncement(file, pid) : { kind: "rubbish" };
27360
+ if (announced.kind === "rubbish") {
27354
27361
  try {
27355
27362
  unlinkSync(file);
27356
27363
  } catch {}
27364
+ continue;
27357
27365
  }
27366
+ found.push(announced);
27358
27367
  }
27359
27368
  return found;
27360
27369
  }
27361
27370
  function describe2(endpoint) {
27362
27371
  return `pid ${endpoint.pid} on ${endpoint.address}:${endpoint.port} (${endpoint.project.name || "unnamed"} at ${endpoint.project.path})`;
27363
27372
  }
27364
- function chooseRuntime(endpoints, projectPath) {
27373
+ function tooNew(unspoken) {
27374
+ const named = unspoken.map((game) => `pid ${game.pid} speaks ${game.protocol} (${game.project.name || "unnamed"} at ${game.project.path})`).join("; ");
27375
+ const half = unspoken.some((game) => game.protocol > RUNTIME_PROTOCOL) ? "this server is the older half: reconnect it so it spawns the installed version" : "the addon is the older half: reinstall it and restart the game";
27376
+ return `A game is running, in a protocol this server does not speak ${named}. This server speaks ${RUNTIME_PROTOCOL}, so ${half}.`;
27377
+ }
27378
+ function chooseRuntime(endpoints, projectPath, unspoken = []) {
27365
27379
  if (endpoints.length === 0) {
27380
+ if (unspoken.length > 0) {
27381
+ return { problem: tooNew(unspoken) };
27382
+ }
27366
27383
  return {
27367
27384
  problem: "No game with the runtime addon is running. Start one with editor_run, or play the project from the editor with the addon enabled."
27368
27385
  };
@@ -29764,7 +29781,8 @@ class GodotServer {
29764
29781
  }
29765
29782
  async handleRuntimeCommand(command, args, timeoutMs = this.runtimeTimeoutMs()) {
29766
29783
  const { op: _op, projectPath, ...params } = asParams(args);
29767
- const choice = chooseRuntime(discoverRuntimes(), typeof projectPath === "string" ? projectPath : undefined);
29784
+ const announced = runtimesAnnounced();
29785
+ const choice = chooseRuntime(announced.running, typeof projectPath === "string" ? projectPath : undefined, announced.unspoken);
29768
29786
  if ("problem" in choice) {
29769
29787
  return this.createErrorResponse(choice.problem);
29770
29788
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
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",