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 +39 -21
- package/build/index.js +39 -21
- package/package.json +1 -1
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
|
|
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
|
-
|
|
35469
|
-
|
|
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
|
-
|
|
35472
|
-
|
|
35473
|
-
|
|
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
|
|
35480
|
-
const
|
|
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
|
|
35483
|
-
if (
|
|
35484
|
-
|
|
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
|
-
|
|
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
|
|
35503
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
27317
|
-
|
|
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
|
-
|
|
27320
|
-
|
|
27321
|
-
|
|
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
|
|
27328
|
-
const
|
|
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
|
|
27331
|
-
if (
|
|
27332
|
-
|
|
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
|
-
|
|
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
|
|
27351
|
-
if (
|
|
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
|
|
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
|
|
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.
|
|
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",
|