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 +58 -15
- package/build/index.js +25 -7
- package/package.json +1 -1
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:
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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", "
|
|
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) || !
|
|
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
|
-
|
|
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 (
|
|
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:
|
|
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
|
|
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", "
|
|
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) || !
|
|
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.
|
|
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",
|