gdharness 0.13.34 → 0.13.35
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 +59 -11
- package/build/index.js +58 -11
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -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. 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.',
|
|
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 moves the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with. Those games are real and running, but no tool here can reach them until the two halves match, so runtimes stays empty and runtimeConnected stays false.',
|
|
16026
16026
|
parameters: {},
|
|
16027
16027
|
requires: []
|
|
16028
16028
|
},
|
|
@@ -36752,6 +36752,9 @@ function judgeRun(record, running, needed) {
|
|
|
36752
36752
|
if (wanted !== null && !said.includes(wanted)) {
|
|
36753
36753
|
return false;
|
|
36754
36754
|
}
|
|
36755
|
+
if (running.kind === "commandLine" && AN_EDITOR.test(said)) {
|
|
36756
|
+
return false;
|
|
36757
|
+
}
|
|
36755
36758
|
if (record.projectPath === "") {
|
|
36756
36759
|
return wanted !== null;
|
|
36757
36760
|
}
|
|
@@ -36801,8 +36804,10 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
36801
36804
|
} catch {}
|
|
36802
36805
|
}
|
|
36803
36806
|
}
|
|
36807
|
+
var AN_EDITOR;
|
|
36804
36808
|
var init_run_record = __esm(() => {
|
|
36805
36809
|
init_runtime_client();
|
|
36810
|
+
AN_EDITOR = /(?:^|\s)(?:-e|--editor)(?:\s|$)/;
|
|
36806
36811
|
});
|
|
36807
36812
|
|
|
36808
36813
|
// src/server.ts
|
|
@@ -38229,7 +38234,10 @@ class GodotServer {
|
|
|
38229
38234
|
return `The debug adapter on port ${port} belongs to process ${holder}, and the editor on this server's bridge is process ${ours}. Godot gives every editor the same debug adapter port by default, so the one this editor names in its settings is not necessarily the one it holds. Playing the game now would put another editor's console and another project's debugger under this project's name.`;
|
|
38230
38235
|
}
|
|
38231
38236
|
anEditorIsStillComing(listeningSince) {
|
|
38232
|
-
return anEditorIsStillComing(listeningSince, this.
|
|
38237
|
+
return anEditorIsStillComing(listeningSince, this.launchedEditorIsUp());
|
|
38238
|
+
}
|
|
38239
|
+
launchedEditorIsUp() {
|
|
38240
|
+
return this.launchedEditor?.exitCode === null && this.launchedEditor.signalCode === null;
|
|
38233
38241
|
}
|
|
38234
38242
|
dap() {
|
|
38235
38243
|
const port = this.editorServes("dapPort", "GDHARNESS_DAP_PORT", DEFAULT_DAP_PORT);
|
|
@@ -38275,7 +38283,7 @@ class GodotServer {
|
|
|
38275
38283
|
addonIsStale: status.connected ? stale : undefined,
|
|
38276
38284
|
bridgeAvailable: this.bridgeStartupError === null,
|
|
38277
38285
|
mayYetConnect: status.connected ? undefined : this.anEditorIsStillComing(status.listeningSince),
|
|
38278
|
-
awaitingLaunchedEditor: status.connected || this.
|
|
38286
|
+
awaitingLaunchedEditor: status.connected || !this.launchedEditorIsUp() ? undefined : this.launchedEditor?.pid ?? undefined,
|
|
38279
38287
|
...status.connected ? this.whatTheLaunchedEditorDropped() : {},
|
|
38280
38288
|
startupError: this.bridgeStartupError,
|
|
38281
38289
|
staleNote: stale ? addonMismatch(status.addonVersion, SERVER_VERSION) : undefined,
|
|
@@ -38311,7 +38319,8 @@ class GodotServer {
|
|
|
38311
38319
|
}
|
|
38312
38320
|
async handleEditorStatus() {
|
|
38313
38321
|
const godotPath = await this.locator.find();
|
|
38314
|
-
const
|
|
38322
|
+
const announced = runtimesAnnounced();
|
|
38323
|
+
const games = await Promise.all(announced.running.map(async (endpoint) => {
|
|
38315
38324
|
const reply = await runtimeRequest(endpoint, "ping", {}, this.runtimeTimeoutMs());
|
|
38316
38325
|
return {
|
|
38317
38326
|
pid: endpoint.pid,
|
|
@@ -38333,10 +38342,17 @@ class GodotServer {
|
|
|
38333
38342
|
version: godotPath === null ? null : await this.godotVersion(godotPath)
|
|
38334
38343
|
},
|
|
38335
38344
|
game: {
|
|
38336
|
-
processActive:
|
|
38345
|
+
processActive: runIsUp(this.currentRun(), playing === null ? null : playing.playing),
|
|
38337
38346
|
playingInEditor: playing,
|
|
38338
38347
|
runtimeConnected: games.some((game) => game.reachable),
|
|
38339
|
-
runtimes: games
|
|
38348
|
+
runtimes: games,
|
|
38349
|
+
unreadable: announced.unspoken.map((game) => ({
|
|
38350
|
+
pid: game.pid,
|
|
38351
|
+
project: game.project,
|
|
38352
|
+
protocol: game.protocol,
|
|
38353
|
+
serverSpeaks: RUNTIME_PROTOCOL,
|
|
38354
|
+
behind: game.protocol > RUNTIME_PROTOCOL ? "server" : "addon"
|
|
38355
|
+
}))
|
|
38340
38356
|
}
|
|
38341
38357
|
});
|
|
38342
38358
|
}
|
|
@@ -38486,7 +38502,7 @@ class GodotServer {
|
|
|
38486
38502
|
return { pid: null, error: started };
|
|
38487
38503
|
}
|
|
38488
38504
|
editor.unref();
|
|
38489
|
-
this.launchedEditor = editor.pid
|
|
38505
|
+
this.launchedEditor = editor.pid === undefined ? null : editor;
|
|
38490
38506
|
return { pid: editor.pid ?? null, error: null };
|
|
38491
38507
|
}
|
|
38492
38508
|
async portsForAnEditor() {
|
|
@@ -38533,6 +38549,7 @@ class GodotServer {
|
|
|
38533
38549
|
await this.endActiveGame("editor_run start, which ends the run that was going before it starts another");
|
|
38534
38550
|
}
|
|
38535
38551
|
const alreadyPlaying = new Set(discoverRuntimes().map((endpoint) => endpoint.pid));
|
|
38552
|
+
const wereHereFirst = this.everyRuntimeOfOurs();
|
|
38536
38553
|
const headless = resolveHeadless(args["headless"], {
|
|
38537
38554
|
platform: process.platform,
|
|
38538
38555
|
variables: process.env
|
|
@@ -38553,16 +38570,20 @@ class GodotServer {
|
|
|
38553
38570
|
this.activeProcess = started;
|
|
38554
38571
|
const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && given.value.length > 0 ? " The editor cannot be handed arguments for a game it plays, so this one was started " + "here: the debug_* tools answer only for a game the editor is playing." : "";
|
|
38555
38572
|
const endedForThis = ended === null ? "" : ` The run that was going, pid ${ended}, was ended to start this one; its output is no longer what editor_output answers about.`;
|
|
38573
|
+
const stranded = wereHereFirst.filter((one) => one.pid !== ended && alive(one.pid));
|
|
38574
|
+
const named = stranded.map((one) => one.protocol === null ? `pid ${one.pid}` : `pid ${one.pid} on protocol ${one.protocol}`).join(", ");
|
|
38575
|
+
const alsoRunning = stranded.length === 0 ? "" : ` A game of this project was already running and still is: ${named}. Starting this one did not end it and nothing here can, because this server did not start it. End it yourself, or bring the halves to one version where a protocol is named.`;
|
|
38556
38576
|
return this.jsonTextResponse({
|
|
38557
38577
|
started: true,
|
|
38558
38578
|
through: "gdharness",
|
|
38579
|
+
alsoRunning: stranded.length === 0 ? undefined : stranded.map((one) => one.pid),
|
|
38559
38580
|
pid: started.process.pid ?? null,
|
|
38560
38581
|
arguments: cmdArgs,
|
|
38561
38582
|
transcript: started.transcript,
|
|
38562
38583
|
refreshedClasses: refreshed.value,
|
|
38563
38584
|
endedPreviousRun: ended ?? undefined,
|
|
38564
38585
|
runtime: await this.runtimeUp(project.value.path, alreadyPlaying, runtimeWaitMs, given.value.length > 0),
|
|
38565
|
-
message: `Use editor_output for what it prints and editor_run stop to end it.${spawnedInstead}${endedForThis}`
|
|
38586
|
+
message: `Use editor_output for what it prints and editor_run stop to end it.${spawnedInstead}${endedForThis}${alsoRunning}`
|
|
38566
38587
|
});
|
|
38567
38588
|
}
|
|
38568
38589
|
async gameIsUp() {
|
|
@@ -38878,7 +38899,12 @@ class GodotServer {
|
|
|
38878
38899
|
const noEditor = !status.connected ? " No editor is connected, so a game the editor is playing cannot be seen from here at all:" + " editor_status says whether one is on its way, and a run of yours that is still up is" + " reachable again once it is." : "";
|
|
38879
38900
|
const live = this.aRuntimeOfOursIsAnswering();
|
|
38880
38901
|
if (live !== null) {
|
|
38881
|
-
return `A game is running for ${live.project.path} and has announced its runtime on port ${live.port}, pid ${live.pid},` + " but this server did not start it and cannot reach it through the editor, so editor_run and" + " editor_output have nothing to answer about. The runtime_* tools reach it now." + "
|
|
38902
|
+
return `A game is running for ${live.project.path} and has announced its runtime on port ${live.port}, pid ${live.pid},` + " but this server did not start it and cannot reach it through the editor, so editor_run and" + " editor_output have nothing to answer about. The runtime_* tools reach it now." + " A run started here is a separate process rather than a replacement, so starting one leaves" + " this game running as well." + (this.godotBridge.getStatus().connected ? "" : " editor_status says whether an editor is on its way, and editor_run can end it once one is.");
|
|
38903
|
+
}
|
|
38904
|
+
const unreadable = this.aRuntimeOfOursIsTooFarOff();
|
|
38905
|
+
if (unreadable !== null) {
|
|
38906
|
+
const behind = unreadable.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";
|
|
38907
|
+
return `A game is running for ${unreadable.project.path}, pid ${unreadable.pid}, announced in protocol` + ` ${unreadable.protocol}, which this server does not speak: it speaks ${RUNTIME_PROTOCOL}, so ${behind}.` + " Until then neither editor_run nor the runtime_* tools can reach it." + " A run started here is a separate process rather than a replacement, so starting one leaves" + " this game running as well.";
|
|
38882
38908
|
}
|
|
38883
38909
|
const record = readRunRecord();
|
|
38884
38910
|
if (record === null) {
|
|
@@ -38890,12 +38916,34 @@ class GodotServer {
|
|
|
38890
38916
|
return `No game of this server's is running.${noEditor} A run started from ${whose} is recorded on this machine, and ${ours}, so it is not this server's to answer for or to end. Start one with editor_run.`;
|
|
38891
38917
|
}
|
|
38892
38918
|
aRuntimeOfOursIsAnswering() {
|
|
38919
|
+
return this.announcedForOurProject(discoverRuntimes());
|
|
38920
|
+
}
|
|
38921
|
+
aRuntimeOfOursIsTooFarOff() {
|
|
38922
|
+
return this.announcedForOurProject(runtimesAnnounced().unspoken);
|
|
38923
|
+
}
|
|
38924
|
+
announcedForOurProject(announced) {
|
|
38925
|
+
return this.allAnnouncedForOurProject(announced)[0] ?? null;
|
|
38926
|
+
}
|
|
38927
|
+
allAnnouncedForOurProject(announced) {
|
|
38893
38928
|
const status = this.godotBridge.getStatus();
|
|
38894
38929
|
const mine = this.ownProject ?? (status.connected ? status.projectPath ?? null : null);
|
|
38895
38930
|
if (mine === null) {
|
|
38896
|
-
return
|
|
38931
|
+
return [];
|
|
38897
38932
|
}
|
|
38898
|
-
return
|
|
38933
|
+
return announced.filter((one) => one.project.path !== "" && isSameDirectory(mine, one.project.path));
|
|
38934
|
+
}
|
|
38935
|
+
everyRuntimeOfOurs() {
|
|
38936
|
+
const announced = runtimesAnnounced();
|
|
38937
|
+
return [
|
|
38938
|
+
...this.allAnnouncedForOurProject(announced.running).map((one) => ({
|
|
38939
|
+
pid: one.pid,
|
|
38940
|
+
protocol: null
|
|
38941
|
+
})),
|
|
38942
|
+
...this.allAnnouncedForOurProject(announced.unspoken).map((one) => ({
|
|
38943
|
+
pid: one.pid,
|
|
38944
|
+
protocol: one.protocol
|
|
38945
|
+
}))
|
|
38946
|
+
];
|
|
38899
38947
|
}
|
|
38900
38948
|
couldBeOurs(project) {
|
|
38901
38949
|
const status = this.godotBridge.getStatus();
|
package/build/index.js
CHANGED
|
@@ -28336,6 +28336,7 @@ function stillTheRecordedRun(record) {
|
|
|
28336
28336
|
function couldStillBeTheRecordedRun(record) {
|
|
28337
28337
|
return judgeRun(record, runningAs(record.pid), "possible");
|
|
28338
28338
|
}
|
|
28339
|
+
var AN_EDITOR = /(?:^|\s)(?:-e|--editor)(?:\s|$)/;
|
|
28339
28340
|
function judgeRun(record, running, needed) {
|
|
28340
28341
|
if (running === null) {
|
|
28341
28342
|
return false;
|
|
@@ -28346,6 +28347,9 @@ function judgeRun(record, running, needed) {
|
|
|
28346
28347
|
if (wanted !== null && !said.includes(wanted)) {
|
|
28347
28348
|
return false;
|
|
28348
28349
|
}
|
|
28350
|
+
if (running.kind === "commandLine" && AN_EDITOR.test(said)) {
|
|
28351
|
+
return false;
|
|
28352
|
+
}
|
|
28349
28353
|
if (record.projectPath === "") {
|
|
28350
28354
|
return wanted !== null;
|
|
28351
28355
|
}
|
|
@@ -29104,7 +29108,7 @@ var TOOL_SPECS = [
|
|
|
29104
29108
|
},
|
|
29105
29109
|
{
|
|
29106
29110
|
name: "editor_status",
|
|
29107
|
-
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. 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.',
|
|
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 moves the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with. Those games are real and running, but no tool here can reach them until the two halves match, so runtimes stays empty and runtimeConnected stays false.',
|
|
29108
29112
|
parameters: {},
|
|
29109
29113
|
requires: []
|
|
29110
29114
|
},
|
|
@@ -30970,7 +30974,10 @@ class GodotServer {
|
|
|
30970
30974
|
return `The debug adapter on port ${port} belongs to process ${holder}, and the editor on this server's bridge is process ${ours}. Godot gives every editor the same debug adapter port by default, so the one this editor names in its settings is not necessarily the one it holds. Playing the game now would put another editor's console and another project's debugger under this project's name.`;
|
|
30971
30975
|
}
|
|
30972
30976
|
anEditorIsStillComing(listeningSince) {
|
|
30973
|
-
return anEditorIsStillComing(listeningSince, this.
|
|
30977
|
+
return anEditorIsStillComing(listeningSince, this.launchedEditorIsUp());
|
|
30978
|
+
}
|
|
30979
|
+
launchedEditorIsUp() {
|
|
30980
|
+
return this.launchedEditor?.exitCode === null && this.launchedEditor.signalCode === null;
|
|
30974
30981
|
}
|
|
30975
30982
|
dap() {
|
|
30976
30983
|
const port = this.editorServes("dapPort", "GDHARNESS_DAP_PORT", DEFAULT_DAP_PORT);
|
|
@@ -31016,7 +31023,7 @@ class GodotServer {
|
|
|
31016
31023
|
addonIsStale: status.connected ? stale : undefined,
|
|
31017
31024
|
bridgeAvailable: this.bridgeStartupError === null,
|
|
31018
31025
|
mayYetConnect: status.connected ? undefined : this.anEditorIsStillComing(status.listeningSince),
|
|
31019
|
-
awaitingLaunchedEditor: status.connected || this.
|
|
31026
|
+
awaitingLaunchedEditor: status.connected || !this.launchedEditorIsUp() ? undefined : this.launchedEditor?.pid ?? undefined,
|
|
31020
31027
|
...status.connected ? this.whatTheLaunchedEditorDropped() : {},
|
|
31021
31028
|
startupError: this.bridgeStartupError,
|
|
31022
31029
|
staleNote: stale ? addonMismatch(status.addonVersion, SERVER_VERSION) : undefined,
|
|
@@ -31052,7 +31059,8 @@ class GodotServer {
|
|
|
31052
31059
|
}
|
|
31053
31060
|
async handleEditorStatus() {
|
|
31054
31061
|
const godotPath = await this.locator.find();
|
|
31055
|
-
const
|
|
31062
|
+
const announced = runtimesAnnounced();
|
|
31063
|
+
const games = await Promise.all(announced.running.map(async (endpoint) => {
|
|
31056
31064
|
const reply = await runtimeRequest(endpoint, "ping", {}, this.runtimeTimeoutMs());
|
|
31057
31065
|
return {
|
|
31058
31066
|
pid: endpoint.pid,
|
|
@@ -31074,10 +31082,17 @@ class GodotServer {
|
|
|
31074
31082
|
version: godotPath === null ? null : await this.godotVersion(godotPath)
|
|
31075
31083
|
},
|
|
31076
31084
|
game: {
|
|
31077
|
-
processActive:
|
|
31085
|
+
processActive: runIsUp(this.currentRun(), playing === null ? null : playing.playing),
|
|
31078
31086
|
playingInEditor: playing,
|
|
31079
31087
|
runtimeConnected: games.some((game) => game.reachable),
|
|
31080
|
-
runtimes: games
|
|
31088
|
+
runtimes: games,
|
|
31089
|
+
unreadable: announced.unspoken.map((game) => ({
|
|
31090
|
+
pid: game.pid,
|
|
31091
|
+
project: game.project,
|
|
31092
|
+
protocol: game.protocol,
|
|
31093
|
+
serverSpeaks: RUNTIME_PROTOCOL,
|
|
31094
|
+
behind: game.protocol > RUNTIME_PROTOCOL ? "server" : "addon"
|
|
31095
|
+
}))
|
|
31081
31096
|
}
|
|
31082
31097
|
});
|
|
31083
31098
|
}
|
|
@@ -31227,7 +31242,7 @@ class GodotServer {
|
|
|
31227
31242
|
return { pid: null, error: started };
|
|
31228
31243
|
}
|
|
31229
31244
|
editor.unref();
|
|
31230
|
-
this.launchedEditor = editor.pid
|
|
31245
|
+
this.launchedEditor = editor.pid === undefined ? null : editor;
|
|
31231
31246
|
return { pid: editor.pid ?? null, error: null };
|
|
31232
31247
|
}
|
|
31233
31248
|
async portsForAnEditor() {
|
|
@@ -31274,6 +31289,7 @@ class GodotServer {
|
|
|
31274
31289
|
await this.endActiveGame("editor_run start, which ends the run that was going before it starts another");
|
|
31275
31290
|
}
|
|
31276
31291
|
const alreadyPlaying = new Set(discoverRuntimes().map((endpoint) => endpoint.pid));
|
|
31292
|
+
const wereHereFirst = this.everyRuntimeOfOurs();
|
|
31277
31293
|
const headless = resolveHeadless(args["headless"], {
|
|
31278
31294
|
platform: process.platform,
|
|
31279
31295
|
variables: process.env
|
|
@@ -31294,16 +31310,20 @@ class GodotServer {
|
|
|
31294
31310
|
this.activeProcess = started;
|
|
31295
31311
|
const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && given.value.length > 0 ? " The editor cannot be handed arguments for a game it plays, so this one was started " + "here: the debug_* tools answer only for a game the editor is playing." : "";
|
|
31296
31312
|
const endedForThis = ended === null ? "" : ` The run that was going, pid ${ended}, was ended to start this one; its output is no longer what editor_output answers about.`;
|
|
31313
|
+
const stranded = wereHereFirst.filter((one) => one.pid !== ended && alive(one.pid));
|
|
31314
|
+
const named = stranded.map((one) => one.protocol === null ? `pid ${one.pid}` : `pid ${one.pid} on protocol ${one.protocol}`).join(", ");
|
|
31315
|
+
const alsoRunning = stranded.length === 0 ? "" : ` A game of this project was already running and still is: ${named}. Starting this one did not end it and nothing here can, because this server did not start it. End it yourself, or bring the halves to one version where a protocol is named.`;
|
|
31297
31316
|
return this.jsonTextResponse({
|
|
31298
31317
|
started: true,
|
|
31299
31318
|
through: "gdharness",
|
|
31319
|
+
alsoRunning: stranded.length === 0 ? undefined : stranded.map((one) => one.pid),
|
|
31300
31320
|
pid: started.process.pid ?? null,
|
|
31301
31321
|
arguments: cmdArgs,
|
|
31302
31322
|
transcript: started.transcript,
|
|
31303
31323
|
refreshedClasses: refreshed.value,
|
|
31304
31324
|
endedPreviousRun: ended ?? undefined,
|
|
31305
31325
|
runtime: await this.runtimeUp(project.value.path, alreadyPlaying, runtimeWaitMs, given.value.length > 0),
|
|
31306
|
-
message: `Use editor_output for what it prints and editor_run stop to end it.${spawnedInstead}${endedForThis}`
|
|
31326
|
+
message: `Use editor_output for what it prints and editor_run stop to end it.${spawnedInstead}${endedForThis}${alsoRunning}`
|
|
31307
31327
|
});
|
|
31308
31328
|
}
|
|
31309
31329
|
async gameIsUp() {
|
|
@@ -31619,7 +31639,12 @@ class GodotServer {
|
|
|
31619
31639
|
const noEditor = !status.connected ? " No editor is connected, so a game the editor is playing cannot be seen from here at all:" + " editor_status says whether one is on its way, and a run of yours that is still up is" + " reachable again once it is." : "";
|
|
31620
31640
|
const live = this.aRuntimeOfOursIsAnswering();
|
|
31621
31641
|
if (live !== null) {
|
|
31622
|
-
return `A game is running for ${live.project.path} and has announced its runtime on port ${live.port}, pid ${live.pid},` + " but this server did not start it and cannot reach it through the editor, so editor_run and" + " editor_output have nothing to answer about. The runtime_* tools reach it now." + "
|
|
31642
|
+
return `A game is running for ${live.project.path} and has announced its runtime on port ${live.port}, pid ${live.pid},` + " but this server did not start it and cannot reach it through the editor, so editor_run and" + " editor_output have nothing to answer about. The runtime_* tools reach it now." + " A run started here is a separate process rather than a replacement, so starting one leaves" + " this game running as well." + (this.godotBridge.getStatus().connected ? "" : " editor_status says whether an editor is on its way, and editor_run can end it once one is.");
|
|
31643
|
+
}
|
|
31644
|
+
const unreadable = this.aRuntimeOfOursIsTooFarOff();
|
|
31645
|
+
if (unreadable !== null) {
|
|
31646
|
+
const behind = unreadable.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";
|
|
31647
|
+
return `A game is running for ${unreadable.project.path}, pid ${unreadable.pid}, announced in protocol` + ` ${unreadable.protocol}, which this server does not speak: it speaks ${RUNTIME_PROTOCOL}, so ${behind}.` + " Until then neither editor_run nor the runtime_* tools can reach it." + " A run started here is a separate process rather than a replacement, so starting one leaves" + " this game running as well.";
|
|
31623
31648
|
}
|
|
31624
31649
|
const record = readRunRecord();
|
|
31625
31650
|
if (record === null) {
|
|
@@ -31631,12 +31656,34 @@ class GodotServer {
|
|
|
31631
31656
|
return `No game of this server's is running.${noEditor} A run started from ${whose} is recorded on this machine, and ${ours}, so it is not this server's to answer for or to end. Start one with editor_run.`;
|
|
31632
31657
|
}
|
|
31633
31658
|
aRuntimeOfOursIsAnswering() {
|
|
31659
|
+
return this.announcedForOurProject(discoverRuntimes());
|
|
31660
|
+
}
|
|
31661
|
+
aRuntimeOfOursIsTooFarOff() {
|
|
31662
|
+
return this.announcedForOurProject(runtimesAnnounced().unspoken);
|
|
31663
|
+
}
|
|
31664
|
+
announcedForOurProject(announced) {
|
|
31665
|
+
return this.allAnnouncedForOurProject(announced)[0] ?? null;
|
|
31666
|
+
}
|
|
31667
|
+
allAnnouncedForOurProject(announced) {
|
|
31634
31668
|
const status = this.godotBridge.getStatus();
|
|
31635
31669
|
const mine = this.ownProject ?? (status.connected ? status.projectPath ?? null : null);
|
|
31636
31670
|
if (mine === null) {
|
|
31637
|
-
return
|
|
31671
|
+
return [];
|
|
31638
31672
|
}
|
|
31639
|
-
return
|
|
31673
|
+
return announced.filter((one) => one.project.path !== "" && isSameDirectory(mine, one.project.path));
|
|
31674
|
+
}
|
|
31675
|
+
everyRuntimeOfOurs() {
|
|
31676
|
+
const announced = runtimesAnnounced();
|
|
31677
|
+
return [
|
|
31678
|
+
...this.allAnnouncedForOurProject(announced.running).map((one) => ({
|
|
31679
|
+
pid: one.pid,
|
|
31680
|
+
protocol: null
|
|
31681
|
+
})),
|
|
31682
|
+
...this.allAnnouncedForOurProject(announced.unspoken).map((one) => ({
|
|
31683
|
+
pid: one.pid,
|
|
31684
|
+
protocol: one.protocol
|
|
31685
|
+
}))
|
|
31686
|
+
];
|
|
31640
31687
|
}
|
|
31641
31688
|
couldBeOurs(project) {
|
|
31642
31689
|
const status = this.godotBridge.getStatus();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.35",
|
|
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",
|