gdharness 0.13.35 → 0.13.36
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 +63 -13
- package/build/index.js +56 -10
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -15994,7 +15994,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15994
15994
|
},
|
|
15995
15995
|
{
|
|
15996
15996
|
name: "editor_output",
|
|
15997
|
-
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. Answers with the counts and the verdict as well as the entries. A run that has quit still answers here, with running false and its exit code, until the next one starts. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state:
|
|
15997
|
+
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. Answers with the counts and the verdict as well as the entries. A run that has quit still answers here, with running false and its exit code, until the next one starts. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering.",
|
|
15998
15998
|
parameters: {
|
|
15999
15999
|
severity: {
|
|
16000
16000
|
type: "string",
|
|
@@ -16022,7 +16022,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16022
16022
|
},
|
|
16023
16023
|
{
|
|
16024
16024
|
name: "editor_status",
|
|
16025
|
-
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. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. 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. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. settingsDropped appears here once when the editor this server opened saved project.godot on its way in, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade: installing
|
|
16025
|
+
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. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. 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. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. settingsDropped appears here once when the editor this server opened saved project.godot on its way in, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it.',
|
|
16026
16026
|
parameters: {},
|
|
16027
16027
|
requires: []
|
|
16028
16028
|
},
|
|
@@ -36662,23 +36662,32 @@ function recordAt(path) {
|
|
|
36662
36662
|
}
|
|
36663
36663
|
function runningAs(pid) {
|
|
36664
36664
|
if (process.platform === "win32") {
|
|
36665
|
-
const
|
|
36666
|
-
if (
|
|
36667
|
-
|
|
36665
|
+
const answer = windowsCommandLine(pid);
|
|
36666
|
+
if (answer !== null) {
|
|
36667
|
+
const [, ...rest] = answer.split(`
|
|
36668
|
+
`);
|
|
36669
|
+
const line = rest.join(`
|
|
36670
|
+
`).trim();
|
|
36671
|
+
const began = startedAt(pid, answer);
|
|
36672
|
+
if (line !== "") {
|
|
36673
|
+
return { kind: "commandLine", text: line, ...began === null ? {} : { startedAt: began } };
|
|
36674
|
+
}
|
|
36668
36675
|
}
|
|
36669
36676
|
const image = windowsImage(pid);
|
|
36670
36677
|
return image === null ? null : { kind: "image", text: image };
|
|
36671
36678
|
}
|
|
36672
36679
|
try {
|
|
36680
|
+
const began = startedAt(pid, null);
|
|
36681
|
+
const when = began === null ? {} : { startedAt: began };
|
|
36673
36682
|
if (process.platform === "linux") {
|
|
36674
36683
|
const raw = readFileSync14(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
36675
|
-
return raw === "" ? null : { kind: "commandLine", text: raw };
|
|
36684
|
+
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
36676
36685
|
}
|
|
36677
36686
|
const args = execFileSync("ps", ["-p", String(pid), "-o", "args="], {
|
|
36678
36687
|
encoding: "utf8",
|
|
36679
36688
|
timeout: 15000
|
|
36680
36689
|
}).trim();
|
|
36681
|
-
return args === "" ? null : { kind: "commandLine", text: args };
|
|
36690
|
+
return args === "" ? null : { kind: "commandLine", text: args, ...when };
|
|
36682
36691
|
} catch {
|
|
36683
36692
|
return null;
|
|
36684
36693
|
}
|
|
@@ -36717,13 +36726,30 @@ function windowsCommandLine(pid) {
|
|
|
36717
36726
|
"-NoProfile",
|
|
36718
36727
|
"-NonInteractive",
|
|
36719
36728
|
"-Command",
|
|
36720
|
-
|
|
36729
|
+
`$p = Get-CimInstance Win32_Process -Filter "ProcessId=${pid}"; if ($p) { $p.CreationDate.ToUniversalTime().ToString("o"); $p.CommandLine }`
|
|
36721
36730
|
], { encoding: "utf8", timeout: 15000, windowsHide: true }).trim();
|
|
36722
36731
|
return answer === "" ? null : answer;
|
|
36723
36732
|
} catch {
|
|
36724
36733
|
return null;
|
|
36725
36734
|
}
|
|
36726
36735
|
}
|
|
36736
|
+
function startedAt(pid, windowsAnswer) {
|
|
36737
|
+
if (process.platform === "win32") {
|
|
36738
|
+
const when = Date.parse(windowsAnswer?.split(`
|
|
36739
|
+
`)[0]?.trim() ?? "");
|
|
36740
|
+
return Number.isNaN(when) ? null : when;
|
|
36741
|
+
}
|
|
36742
|
+
try {
|
|
36743
|
+
const said = execFileSync("ps", ["-p", String(pid), "-o", "lstart="], {
|
|
36744
|
+
encoding: "utf8",
|
|
36745
|
+
timeout: 15000
|
|
36746
|
+
}).trim();
|
|
36747
|
+
const when = Date.parse(said);
|
|
36748
|
+
return said === "" || Number.isNaN(when) ? null : when;
|
|
36749
|
+
} catch {
|
|
36750
|
+
return null;
|
|
36751
|
+
}
|
|
36752
|
+
}
|
|
36727
36753
|
function windowsImage(pid) {
|
|
36728
36754
|
try {
|
|
36729
36755
|
const csv = execFileSync("tasklist", ["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], {
|
|
@@ -36746,6 +36772,9 @@ function judgeRun(record, running, needed) {
|
|
|
36746
36772
|
if (running === null) {
|
|
36747
36773
|
return false;
|
|
36748
36774
|
}
|
|
36775
|
+
if (running.startedAt !== undefined && running.startedAt - record.startedAt > SAME_RUN_WINDOW_MS) {
|
|
36776
|
+
return false;
|
|
36777
|
+
}
|
|
36749
36778
|
const engine = record.command === undefined ? null : basename2(record.command);
|
|
36750
36779
|
const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
|
|
36751
36780
|
const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
|
|
@@ -36804,7 +36833,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
36804
36833
|
} catch {}
|
|
36805
36834
|
}
|
|
36806
36835
|
}
|
|
36807
|
-
var AN_EDITOR;
|
|
36836
|
+
var AN_EDITOR, SAME_RUN_WINDOW_MS = 1e4;
|
|
36808
36837
|
var init_run_record = __esm(() => {
|
|
36809
36838
|
init_runtime_client();
|
|
36810
36839
|
AN_EDITOR = /(?:^|\s)(?:-e|--editor)(?:\s|$)/;
|
|
@@ -36840,12 +36869,18 @@ function stillRunning(run) {
|
|
|
36840
36869
|
if (!run.throughEditor && run.process === null && run.pid !== null) {
|
|
36841
36870
|
return alive(run.pid);
|
|
36842
36871
|
}
|
|
36872
|
+
if (run.announcedPid !== undefined && !alive(run.announcedPid)) {
|
|
36873
|
+
return false;
|
|
36874
|
+
}
|
|
36843
36875
|
return true;
|
|
36844
36876
|
}
|
|
36845
36877
|
function runIsUp(run, editorSays) {
|
|
36846
36878
|
if (run?.throughEditor !== true) {
|
|
36847
36879
|
return stillRunning(run);
|
|
36848
36880
|
}
|
|
36881
|
+
if (run.announcedPid !== undefined && !alive(run.announcedPid)) {
|
|
36882
|
+
return false;
|
|
36883
|
+
}
|
|
36849
36884
|
return editorSays ?? stillRunning(run);
|
|
36850
36885
|
}
|
|
36851
36886
|
function runtimeVerdict(endpoint, after) {
|
|
@@ -38599,6 +38634,10 @@ class GodotServer {
|
|
|
38599
38634
|
budgetMs,
|
|
38600
38635
|
giveUp: () => this.dapClient?.isStopped() === true || !stillRunning(this.currentRun())
|
|
38601
38636
|
});
|
|
38637
|
+
const going = this.currentRun();
|
|
38638
|
+
if (endpoint !== null && going?.throughEditor === true && going.announcedPid === undefined) {
|
|
38639
|
+
going.announcedPid = endpoint.pid;
|
|
38640
|
+
}
|
|
38602
38641
|
return runtimeVerdict(endpoint, {
|
|
38603
38642
|
addon: true,
|
|
38604
38643
|
budgetMs,
|
|
@@ -38708,9 +38747,15 @@ class GodotServer {
|
|
|
38708
38747
|
running.log.record("warning", `This run was not ended here: pid ${running.pid} no longer answers as the run that was recorded, so nothing was signalled. If that process is still the game, end it yourself; if it is not, it belongs to something else.`);
|
|
38709
38748
|
return;
|
|
38710
38749
|
}
|
|
38750
|
+
const signalled = runningAs(running.pid);
|
|
38751
|
+
const described = signalled === null ? "nothing it would name" : `${signalled.kind}: ${signalled.text}`;
|
|
38752
|
+
let landed = true;
|
|
38711
38753
|
try {
|
|
38712
38754
|
process.kill(running.pid);
|
|
38713
|
-
} catch {
|
|
38755
|
+
} catch {
|
|
38756
|
+
landed = false;
|
|
38757
|
+
}
|
|
38758
|
+
running.log.record("info", landed ? `gdharness ended pid ${running.pid}, which the operating system described as ${described}.` : `gdharness signalled pid ${running.pid} and it was already gone; the operating system had described it as ${described}.`);
|
|
38714
38759
|
}
|
|
38715
38760
|
spawnGame(godotPath, cmdArgs, env) {
|
|
38716
38761
|
const child = spawn(godotPath, cmdArgs, { stdio: ["ignore", "pipe", "pipe"], ...env ? { env } : {} });
|
|
@@ -39048,7 +39093,7 @@ class GodotServer {
|
|
|
39048
39093
|
notes.push(run.exitCode === 0 ? "This run quit on its own, cleanly: exit code 0." : "Nothing here ended this run: it stopped on its own or something outside this server stopped it.");
|
|
39049
39094
|
}
|
|
39050
39095
|
if (run.transcript !== null && selected.omitted > 0) {
|
|
39051
|
-
notes.push(`Everything this run has printed is in ${run.transcript}, uncapped and still being written. Read it for output, not for whether the run is alive: running above is asked of the operating system, and
|
|
39096
|
+
notes.push(`Everything this run has printed is in ${run.transcript}, uncapped and still being written. Read it for output, not for whether the run is alive: a transcript that stops growing is a run between prints. running above is asked of the operating system while there is a process to ask about, and for an editor-played run that announced no runtime it is the editor's answer instead, which can lag after the game has gone.`);
|
|
39052
39097
|
}
|
|
39053
39098
|
return this.jsonTextResponse({
|
|
39054
39099
|
running: stillRunning(run),
|
|
@@ -40844,8 +40889,13 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
40844
40889
|
with their backtraces, and a \`clean\` verdict, so a run that printed an error is one call away
|
|
40845
40890
|
from being known. For a long run it also names a transcript file: read that for output and this
|
|
40846
40891
|
answer for state. Never decide a run has ended by watching the file stop growing, or a process
|
|
40847
|
-
list, or a timeout
|
|
40848
|
-
gdharness ended it
|
|
40892
|
+
list, or a timeout; a bench between prints looks exactly like a bench that died to everything else.
|
|
40893
|
+
\`endedBy\` says whether gdharness ended it, and \`running\` is asked of the operating system
|
|
40894
|
+
whenever there is a process to ask about: every run started through \`editor_run\`, and every
|
|
40895
|
+
editor-played run whose game announced its runtime. An editor-played run in a project with no
|
|
40896
|
+
runtime addon announces nothing, so there is no process id and \`running\` is the editor's answer,
|
|
40897
|
+
which can lag by seconds after the game has gone. \`editor_status\` names the game under
|
|
40898
|
+
\`runtimes\`, which does not depend on the editor answering.
|
|
40849
40899
|
- One server, one editor. A second project is a second harness session with its own server, and
|
|
40850
40900
|
that works: each editor is opened on its own language server and debug adapter ports, and says
|
|
40851
40901
|
where it serves. Two editors on one project is the thing to refuse.
|
package/build/index.js
CHANGED
|
@@ -28256,23 +28256,32 @@ function recordAt(path) {
|
|
|
28256
28256
|
}
|
|
28257
28257
|
function runningAs(pid) {
|
|
28258
28258
|
if (process.platform === "win32") {
|
|
28259
|
-
const
|
|
28260
|
-
if (
|
|
28261
|
-
|
|
28259
|
+
const answer = windowsCommandLine(pid);
|
|
28260
|
+
if (answer !== null) {
|
|
28261
|
+
const [, ...rest] = answer.split(`
|
|
28262
|
+
`);
|
|
28263
|
+
const line = rest.join(`
|
|
28264
|
+
`).trim();
|
|
28265
|
+
const began = startedAt(pid, answer);
|
|
28266
|
+
if (line !== "") {
|
|
28267
|
+
return { kind: "commandLine", text: line, ...began === null ? {} : { startedAt: began } };
|
|
28268
|
+
}
|
|
28262
28269
|
}
|
|
28263
28270
|
const image = windowsImage(pid);
|
|
28264
28271
|
return image === null ? null : { kind: "image", text: image };
|
|
28265
28272
|
}
|
|
28266
28273
|
try {
|
|
28274
|
+
const began = startedAt(pid, null);
|
|
28275
|
+
const when = began === null ? {} : { startedAt: began };
|
|
28267
28276
|
if (process.platform === "linux") {
|
|
28268
28277
|
const raw = readFileSync9(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
28269
|
-
return raw === "" ? null : { kind: "commandLine", text: raw };
|
|
28278
|
+
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
28270
28279
|
}
|
|
28271
28280
|
const args = execFileSync("ps", ["-p", String(pid), "-o", "args="], {
|
|
28272
28281
|
encoding: "utf8",
|
|
28273
28282
|
timeout: 15000
|
|
28274
28283
|
}).trim();
|
|
28275
|
-
return args === "" ? null : { kind: "commandLine", text: args };
|
|
28284
|
+
return args === "" ? null : { kind: "commandLine", text: args, ...when };
|
|
28276
28285
|
} catch {
|
|
28277
28286
|
return null;
|
|
28278
28287
|
}
|
|
@@ -28311,13 +28320,30 @@ function windowsCommandLine(pid) {
|
|
|
28311
28320
|
"-NoProfile",
|
|
28312
28321
|
"-NonInteractive",
|
|
28313
28322
|
"-Command",
|
|
28314
|
-
|
|
28323
|
+
`$p = Get-CimInstance Win32_Process -Filter "ProcessId=${pid}"; if ($p) { $p.CreationDate.ToUniversalTime().ToString("o"); $p.CommandLine }`
|
|
28315
28324
|
], { encoding: "utf8", timeout: 15000, windowsHide: true }).trim();
|
|
28316
28325
|
return answer === "" ? null : answer;
|
|
28317
28326
|
} catch {
|
|
28318
28327
|
return null;
|
|
28319
28328
|
}
|
|
28320
28329
|
}
|
|
28330
|
+
function startedAt(pid, windowsAnswer) {
|
|
28331
|
+
if (process.platform === "win32") {
|
|
28332
|
+
const when = Date.parse(windowsAnswer?.split(`
|
|
28333
|
+
`)[0]?.trim() ?? "");
|
|
28334
|
+
return Number.isNaN(when) ? null : when;
|
|
28335
|
+
}
|
|
28336
|
+
try {
|
|
28337
|
+
const said = execFileSync("ps", ["-p", String(pid), "-o", "lstart="], {
|
|
28338
|
+
encoding: "utf8",
|
|
28339
|
+
timeout: 15000
|
|
28340
|
+
}).trim();
|
|
28341
|
+
const when = Date.parse(said);
|
|
28342
|
+
return said === "" || Number.isNaN(when) ? null : when;
|
|
28343
|
+
} catch {
|
|
28344
|
+
return null;
|
|
28345
|
+
}
|
|
28346
|
+
}
|
|
28321
28347
|
function windowsImage(pid) {
|
|
28322
28348
|
try {
|
|
28323
28349
|
const csv = execFileSync("tasklist", ["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], {
|
|
@@ -28337,10 +28363,14 @@ function couldStillBeTheRecordedRun(record) {
|
|
|
28337
28363
|
return judgeRun(record, runningAs(record.pid), "possible");
|
|
28338
28364
|
}
|
|
28339
28365
|
var AN_EDITOR = /(?:^|\s)(?:-e|--editor)(?:\s|$)/;
|
|
28366
|
+
var SAME_RUN_WINDOW_MS = 1e4;
|
|
28340
28367
|
function judgeRun(record, running, needed) {
|
|
28341
28368
|
if (running === null) {
|
|
28342
28369
|
return false;
|
|
28343
28370
|
}
|
|
28371
|
+
if (running.startedAt !== undefined && running.startedAt - record.startedAt > SAME_RUN_WINDOW_MS) {
|
|
28372
|
+
return false;
|
|
28373
|
+
}
|
|
28344
28374
|
const engine = record.command === undefined ? null : basename(record.command);
|
|
28345
28375
|
const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
|
|
28346
28376
|
const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
|
|
@@ -29080,7 +29110,7 @@ var TOOL_SPECS = [
|
|
|
29080
29110
|
},
|
|
29081
29111
|
{
|
|
29082
29112
|
name: "editor_output",
|
|
29083
|
-
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. Answers with the counts and the verdict as well as the entries. A run that has quit still answers here, with running false and its exit code, until the next one starts. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state:
|
|
29113
|
+
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. Answers with the counts and the verdict as well as the entries. A run that has quit still answers here, with running false and its exit code, until the next one starts. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering.",
|
|
29084
29114
|
parameters: {
|
|
29085
29115
|
severity: {
|
|
29086
29116
|
type: "string",
|
|
@@ -29108,7 +29138,7 @@ var TOOL_SPECS = [
|
|
|
29108
29138
|
},
|
|
29109
29139
|
{
|
|
29110
29140
|
name: "editor_status",
|
|
29111
|
-
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. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. 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. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. settingsDropped appears here once when the editor this server opened saved project.godot on its way in, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade: installing
|
|
29141
|
+
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. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. 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. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. settingsDropped appears here once when the editor this server opened saved project.godot on its way in, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it.',
|
|
29112
29142
|
parameters: {},
|
|
29113
29143
|
requires: []
|
|
29114
29144
|
},
|
|
@@ -29538,12 +29568,18 @@ function stillRunning(run) {
|
|
|
29538
29568
|
if (!run.throughEditor && run.process === null && run.pid !== null) {
|
|
29539
29569
|
return alive(run.pid);
|
|
29540
29570
|
}
|
|
29571
|
+
if (run.announcedPid !== undefined && !alive(run.announcedPid)) {
|
|
29572
|
+
return false;
|
|
29573
|
+
}
|
|
29541
29574
|
return true;
|
|
29542
29575
|
}
|
|
29543
29576
|
function runIsUp(run, editorSays) {
|
|
29544
29577
|
if (run?.throughEditor !== true) {
|
|
29545
29578
|
return stillRunning(run);
|
|
29546
29579
|
}
|
|
29580
|
+
if (run.announcedPid !== undefined && !alive(run.announcedPid)) {
|
|
29581
|
+
return false;
|
|
29582
|
+
}
|
|
29547
29583
|
return editorSays ?? stillRunning(run);
|
|
29548
29584
|
}
|
|
29549
29585
|
function runtimeVerdict(endpoint, after) {
|
|
@@ -31339,6 +31375,10 @@ class GodotServer {
|
|
|
31339
31375
|
budgetMs,
|
|
31340
31376
|
giveUp: () => this.dapClient?.isStopped() === true || !stillRunning(this.currentRun())
|
|
31341
31377
|
});
|
|
31378
|
+
const going = this.currentRun();
|
|
31379
|
+
if (endpoint !== null && going?.throughEditor === true && going.announcedPid === undefined) {
|
|
31380
|
+
going.announcedPid = endpoint.pid;
|
|
31381
|
+
}
|
|
31342
31382
|
return runtimeVerdict(endpoint, {
|
|
31343
31383
|
addon: true,
|
|
31344
31384
|
budgetMs,
|
|
@@ -31448,9 +31488,15 @@ class GodotServer {
|
|
|
31448
31488
|
running.log.record("warning", `This run was not ended here: pid ${running.pid} no longer answers as the run that was recorded, so nothing was signalled. If that process is still the game, end it yourself; if it is not, it belongs to something else.`);
|
|
31449
31489
|
return;
|
|
31450
31490
|
}
|
|
31491
|
+
const signalled = runningAs(running.pid);
|
|
31492
|
+
const described = signalled === null ? "nothing it would name" : `${signalled.kind}: ${signalled.text}`;
|
|
31493
|
+
let landed = true;
|
|
31451
31494
|
try {
|
|
31452
31495
|
process.kill(running.pid);
|
|
31453
|
-
} catch {
|
|
31496
|
+
} catch {
|
|
31497
|
+
landed = false;
|
|
31498
|
+
}
|
|
31499
|
+
running.log.record("info", landed ? `gdharness ended pid ${running.pid}, which the operating system described as ${described}.` : `gdharness signalled pid ${running.pid} and it was already gone; the operating system had described it as ${described}.`);
|
|
31454
31500
|
}
|
|
31455
31501
|
spawnGame(godotPath, cmdArgs, env) {
|
|
31456
31502
|
const child = spawn(godotPath, cmdArgs, { stdio: ["ignore", "pipe", "pipe"], ...env ? { env } : {} });
|
|
@@ -31788,7 +31834,7 @@ class GodotServer {
|
|
|
31788
31834
|
notes.push(run.exitCode === 0 ? "This run quit on its own, cleanly: exit code 0." : "Nothing here ended this run: it stopped on its own or something outside this server stopped it.");
|
|
31789
31835
|
}
|
|
31790
31836
|
if (run.transcript !== null && selected.omitted > 0) {
|
|
31791
|
-
notes.push(`Everything this run has printed is in ${run.transcript}, uncapped and still being written. Read it for output, not for whether the run is alive: running above is asked of the operating system, and
|
|
31837
|
+
notes.push(`Everything this run has printed is in ${run.transcript}, uncapped and still being written. Read it for output, not for whether the run is alive: a transcript that stops growing is a run between prints. running above is asked of the operating system while there is a process to ask about, and for an editor-played run that announced no runtime it is the editor's answer instead, which can lag after the game has gone.`);
|
|
31792
31838
|
}
|
|
31793
31839
|
return this.jsonTextResponse({
|
|
31794
31840
|
running: stillRunning(run),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.36",
|
|
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",
|