gdharness 1.2.17 → 1.2.18
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
|
@@ -10793,6 +10793,10 @@ function describeOperations(spec) {
|
|
|
10793
10793
|
const fallback = spec.defaultOperation ? ` Default ${spec.defaultOperation}.` : "";
|
|
10794
10794
|
return ` Operations: ${lines.join("; ")}.${fallback}`;
|
|
10795
10795
|
}
|
|
10796
|
+
function servedDescription(spec) {
|
|
10797
|
+
const answer = spec.answer === undefined ? "" : " The tool reference in the gdharness skill, and the site, say what every field of the answer means.";
|
|
10798
|
+
return `${spec.description}${describeOperations(spec)}${answer}`;
|
|
10799
|
+
}
|
|
10796
10800
|
function buildToolDefinitions() {
|
|
10797
10801
|
return TOOL_SPECS.map((spec) => {
|
|
10798
10802
|
const properties = {};
|
|
@@ -10814,7 +10818,7 @@ function buildToolDefinitions() {
|
|
|
10814
10818
|
return {
|
|
10815
10819
|
name: spec.name,
|
|
10816
10820
|
title: spec.title,
|
|
10817
|
-
description:
|
|
10821
|
+
description: servedDescription(spec),
|
|
10818
10822
|
inputSchema: {
|
|
10819
10823
|
type: "object",
|
|
10820
10824
|
properties,
|
|
@@ -11832,12 +11836,12 @@ var init_tool_definitions = __esm(() => {
|
|
|
11832
11836
|
},
|
|
11833
11837
|
stop: {
|
|
11834
11838
|
effect: "destructive",
|
|
11835
|
-
summary: "end the run
|
|
11839
|
+
summary: "end the run, naming the process ended under endedPid, and answer with the errors and warnings it printed under entries (editor_output has the rest); a game that started processes of its own keeps them unless andChildren says to end those too",
|
|
11836
11840
|
requires: []
|
|
11837
11841
|
},
|
|
11838
11842
|
check: {
|
|
11839
11843
|
effect: "additive",
|
|
11840
|
-
summary: "boot headless, quit after a few frames, and report the verdict
|
|
11844
|
+
summary: "boot headless, quit after a few frames, and report the verdict: booted is true only for a run that quit on its own within timeoutMs, exited 0 and printed no error, so a game that reached _ready and then raised an error answers false",
|
|
11841
11845
|
requires: ["projectPath"]
|
|
11842
11846
|
},
|
|
11843
11847
|
wait: {
|
|
@@ -11847,10 +11851,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
11847
11851
|
},
|
|
11848
11852
|
profile: {
|
|
11849
11853
|
effect: "read",
|
|
11850
|
-
summary: "where the run spends its time by GDScript function, while it runs or after it ended
|
|
11854
|
+
summary: "where the run spends its time by GDScript function, while it runs or after it ended, sorted by self time. A run started with profile: true is profiled from its start; a game the editor plays that was not is profiled from the moment this is asked; a run this server started without it is refused, since its debugger is fixed as it starts",
|
|
11851
11855
|
requires: []
|
|
11852
11856
|
}
|
|
11853
11857
|
},
|
|
11858
|
+
answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked.",
|
|
11854
11859
|
defaultOperation: "start"
|
|
11855
11860
|
},
|
|
11856
11861
|
{
|
|
@@ -11908,7 +11913,8 @@ var init_tool_definitions = __esm(() => {
|
|
|
11908
11913
|
name: "editor_status",
|
|
11909
11914
|
title: "Editor status",
|
|
11910
11915
|
effect: "read",
|
|
11911
|
-
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and
|
|
11916
|
+
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and which games with the runtime addon are reachable, of this project and of every other on the machine. Read connected with mayYetConnect: the editor dials this server and backs off between tries, so for the first half-minute after a server starts false means not reached yet as often as no editor; mayYetConnect true is worth waiting out, false is final, and restartInterrupted, restartUnderway, closedHere and editorsNotYetConnected say why. An editor that has connected and not yet greeted reports nothing about itself, and every other call made in that window waits up to ten seconds for it to settle. playingInEditor null means unanswered rather than nothing playing: processActive and runtimes do not depend on the editor answering. game.run names the run editor_output and editor_run stop answer for, and stays named after it ends. addonIsStale, on the editor and on each game, says it runs addon code other than what this server ships, and staleNote says which half moves: gdharness upgrade in the project, a restart, or a reconnect of this server. A false editor.lspListening or editor.dapListening comes with serversNote, and editor_launch restart binds the port again. editor.scan says whether the editor is scanning, which editor_run waits out before starting a game. A game announced in a protocol this server does not speak is listed under unreadable, during an upgrade that moves the protocol, until the halves match.",
|
|
11917
|
+
answer: "runtimeConnected counts the games of the project this server serves, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath. 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. game.run names the run processActive is about, the one editor_output and editor_run stop answer for: its pid, its project, and adopted true when this server read it back from another server rather than starting it. It stays named after the run ends, beside processActive false, so editor_output can still read what that run printed; null when there is no run at all. startedAt says when it began, and endedAt when it ended, wherever something saw the end: a run adopted from a note another server wrote can have ended hours before this server started. editor.lspListening and editor.dapListening say whether anything accepts connections on the ports the editor names, lspPort and dapPort: Godot binds each once, as the editor starts, and reports a failure only in the log panel of the editor itself, so a false comes with serversNote, and editor_launch restart binds it again. editor.scan says whether the editor is scanning or importing and how many milliseconds ago its last scan finished (finishedMsAgo), or is null when it did not answer: the editor rewrites the class cache just after a scan, and editor_run waits that out before starting a game, saying so under waitedForEditorScanMs. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout; for the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. A game the editor played announces the editor under editorPid, and so does whatever that game started for itself; a game some other server started announces none, which is how the two are told apart when both are running from one project. Each game also says which runtime addon it loaded, under addonVersion (null when it cannot say); addonIsStale is true when that code is not what this server ships, and staleNote says whether the project needs gdharness upgrade, the game a restart, or this server a reconnect. A second checkout of a project, such as a git worktree, carries addons of its own and is where this shows. Every runtime_* answer from such a game carries addonIsStale and staleNote too. 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. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for; editor_launch open finishes it. True beside restartUnderway is a restart still in progress, its server alive and within the wait for the old editor to go: the editor is coming, and opening another would put two on one project. 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. Any editor of this project running and not yet connected, whoever opened it, makes it true as well and is named under editorsNotYetConnected with its pid and start time, so a restart a reconnect interrupted after its launch is not read as nothing being there. 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. addonIsStale is decided by the code of the editor addons rather than their version, so an editor started before an upgrade that left that code alone reports the older addonVersion, is not stale, and addonNote says so: it needs no restart. port is the one the editor bridge is on; when that is not the one this server was configured with, portWanted names the configured one and portNote says why it moved and, when it was the server this one replaced, which pid kept it. A server replacing another of its project that holds the configured port waits for it to stand down and takes the port over, which takes a few seconds; bridgeHandover says so while it waits, and it moves only if that server has not let go within the wait. An editor that has connected and not yet greeted, which lasts a frame and longer while it imports, reports nothing about itself: greeting says so, addonIsStale is absent rather than guessed, and a restart asked then waits for the greeting to learn who opened it. startedByAnEditor is an editor another editor started, by Godot's own restart or the project manager, which has none of the arguments a server gave the one before it; startedByAnEditorNote says what that costs. An engine run that loads the editor addon to do one job and quit, such as a gate's godot --headless --import, an export or a script, is not taken as the editor: a process that greets without a server's mark has its command line read first, and while that is read identifying is true and connected false; one started with such an option is turned away, and turnedAway names the last one, its pid and the option. Every other call made while an editor is arriving, before its greeting or while it is looked up, waits up to ten seconds for it to settle rather than acting on connected as it reads in that window.",
|
|
11912
11918
|
parameters: {},
|
|
11913
11919
|
requires: []
|
|
11914
11920
|
},
|
|
@@ -12215,6 +12221,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12215
12221
|
name: "runtime_input",
|
|
12216
12222
|
title: "Send input to the game",
|
|
12217
12223
|
description: "Input to the running game: a whole click on a Control or a 3D node named by path or on a control named by the words on it, an item chosen out of a menu, typing into whatever has the focus, or a raw action, key, mouse button or mouse motion. All of it works headless, where the window is 64 by 64 and the GUI only takes what is inside it. On gdharness's own desktop, where a windowed run goes, Windows refuses to say where the pointer is: every pointer answer there carries pointer_on_hidden_desktop, and the first of the run pointer_note saying why it matters: get_mouse_position() on the root window, and the mouse positions derived from it, read a meaningless position, while what reads the event itself (the control under it, hover, tooltips, input handlers) gets the injected position.",
|
|
12224
|
+
answer: 'click: what was under the pointer, and what became of the control: in_tree, removed or freed. Named by says, found says which match it was, and found.pressedOn says "the words" for words in a RichTextLabel pressed where they are drawn, or "the middle of the label" with a note saying why they could not be placed; on a rich label link_pressed says whether the press reached a link, with link_meta giving the meta the label reports clicked, an empty one included. A control scrolled into view first is scrolled each container out from it a frame after the one inside, and scrolled_into_view says whether a scroll value changed. A button in toggle mode, a CheckBox or CheckButton included, answers pressed with before and after, its button_pressed either side of the click: the same value twice is a click the game undid, and after is null when the click freed it. A refusal for a pointer turned off names where mouse_behavior_recursive was set. On a 3D node landed says the interface did not swallow the press: no control under the pointer stopped it, a control letting the pointer pass included, and every viewport on the way out from the room had the pointer over what shows the room. choose: what was chosen, read before the press, which is heard rather than assumed; what became of the control named (in_tree, removed or freed, since a game can rebuild its screen on the pick); and what the button in front of the menu shows now when it is still there. A menu is opened rather than clicked because its items are drawn rather than built, and the item is looked for as the menu stands once open, since a game can fill a menu as it opens.',
|
|
12218
12225
|
parameters: {
|
|
12219
12226
|
projectPath: RUNNING_PROJECT_PATH,
|
|
12220
12227
|
pid: RUNNING_PID,
|
|
@@ -12304,12 +12311,12 @@ var init_tool_definitions = __esm(() => {
|
|
|
12304
12311
|
operations: {
|
|
12305
12312
|
click: {
|
|
12306
12313
|
effect: "additive",
|
|
12307
|
-
summary:
|
|
12314
|
+
summary: "press and release on a Control, a frame apart, named by nodePath or by says with the words on it (found under nodePath when both are given), or on a 3D node where it is drawn. A control out of sight inside a ScrollContainer is scrolled to first, and a link in a RichTextLabel is pressed where its words are drawn. A disabled button, a control that does not process and one whose pointer mouse_behavior_recursive turned off are refused, since the press would do nothing; by says, one the pointer can reach is picked over it",
|
|
12308
12315
|
requires: []
|
|
12309
12316
|
},
|
|
12310
12317
|
choose: {
|
|
12311
12318
|
effect: "additive",
|
|
12312
|
-
summary: "take an item out of a menu, by what it says or by where it is in the list
|
|
12319
|
+
summary: "take an item out of a menu, by what it says or by where it is in the list: the menu is opened, the item found in it as it stands then, and pressed through the engine's own path, which needs no window and does not depend on what ui_accept is bound to. A disabled or hidden button in front of the menu is refused, and so is a press the menu did not take",
|
|
12313
12320
|
requires: ["nodePath"]
|
|
12314
12321
|
},
|
|
12315
12322
|
action: { effect: "additive", summary: "press an action, or hold it", requires: ["action"] },
|
|
@@ -12585,6 +12592,9 @@ function renderToolsMarkdown() {
|
|
|
12585
12592
|
];
|
|
12586
12593
|
for (const tool of TOOL_SPECS) {
|
|
12587
12594
|
lines.push(`## ${tool.name}`, "", `${tool.title}. ${whatItCanChange(tool)}`, "", tool.description, "");
|
|
12595
|
+
if (tool.answer !== undefined) {
|
|
12596
|
+
lines.push(`The answer: ${tool.answer}`, "");
|
|
12597
|
+
}
|
|
12588
12598
|
const operations = Object.entries(tool.operations ?? {});
|
|
12589
12599
|
if (operations.length > 0) {
|
|
12590
12600
|
for (const [op, spec] of operations) {
|
|
@@ -43608,6 +43618,11 @@ function scanWaitAnswer(scanned, started = "game") {
|
|
|
43608
43618
|
scanNote: !scanned.stillScanning ? undefined : started === "game" ? `${still} this game started during the scan and may have read a class cache being rewritten. If ` + 'editor_output shows "Could not find type" for a class that is declared, editor_rescan ' + "waits for the scan to finish, and a start after it reads the finished cache." : `${still} the engine answering this started during the scan and may have read a class cache being ` + "rewritten, and a class cache it wrote can be written over when the scan finishes. If " + 'engine_messages shows "Could not find type" for a class that is declared, editor_rescan waits ' + "for the scan to finish, and the same call after it reads the finished cache."
|
|
43609
43619
|
};
|
|
43610
43620
|
}
|
|
43621
|
+
function opElsewhere(tool, requested) {
|
|
43622
|
+
const ops = Object.hasOwn(OPS_ELSEWHERE, tool) ? OPS_ELSEWHERE[tool] : undefined;
|
|
43623
|
+
const where = ops !== undefined && typeof requested === "string" && Object.hasOwn(ops, requested) ? ops[requested] : undefined;
|
|
43624
|
+
return where === undefined ? "" : ` A value is read with ${where}.`;
|
|
43625
|
+
}
|
|
43611
43626
|
function changedSettings(before, after) {
|
|
43612
43627
|
if (before === null || after === null) {
|
|
43613
43628
|
return null;
|
|
@@ -44662,7 +44677,7 @@ class GodotServer {
|
|
|
44662
44677
|
} else {
|
|
44663
44678
|
return {
|
|
44664
44679
|
ok: false,
|
|
44665
|
-
response: this.createErrorResponse(`${spec.name} has no op ${JSON.stringify(requested)}. Valid ops: ${valid.join(", ")}
|
|
44680
|
+
response: this.createErrorResponse(`${spec.name} has no op ${JSON.stringify(requested)}. Valid ops: ${valid.join(", ")}.${opElsewhere(spec.name, requested)}`)
|
|
44666
44681
|
};
|
|
44667
44682
|
}
|
|
44668
44683
|
}
|
|
@@ -49218,7 +49233,7 @@ async function runGodotServer() {
|
|
|
49218
49233
|
const server = new GodotServer;
|
|
49219
49234
|
await server.run();
|
|
49220
49235
|
}
|
|
49221
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
|
|
49236
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
|
|
49222
49237
|
var init_server = __esm(() => {
|
|
49223
49238
|
init_dist();
|
|
49224
49239
|
init_stdio();
|
|
@@ -49281,6 +49296,9 @@ var init_server = __esm(() => {
|
|
|
49281
49296
|
object: "an object"
|
|
49282
49297
|
};
|
|
49283
49298
|
HANDOVER_MS = SUCCESSOR_CHECK_MS + 5000;
|
|
49299
|
+
OPS_ELSEWHERE = {
|
|
49300
|
+
runtime_invoke: { get: "runtime_inspect property", property: "runtime_inspect property" }
|
|
49301
|
+
};
|
|
49284
49302
|
AUDIO_BUS_OPS = new Set([
|
|
49285
49303
|
"add_audio_bus",
|
|
49286
49304
|
"remove_audio_bus",
|
|
@@ -135,6 +135,24 @@ static func make_folder_for(file_path: String) -> String:
|
|
|
135
135
|
return "" if made == OK else "Could not make the folder %s: %s" % [folder, error_string(made)]
|
|
136
136
|
|
|
137
137
|
|
|
138
|
+
## Why [param script], loaded from [param script_path], cannot be attached because the editor could not
|
|
139
|
+
## compile it, or "". A script that did not compile has no base type, and the refusal built from that
|
|
140
|
+
## said "start.gd extends , so it cannot be on a Control root" about a script that was fine, whose
|
|
141
|
+
## failure was a class it called being behind in the editor.
|
|
142
|
+
static func uncompiled(script: Script, script_path: String) -> String:
|
|
143
|
+
if not str(script.get_instance_base_type()).is_empty():
|
|
144
|
+
return ""
|
|
145
|
+
return (
|
|
146
|
+
(
|
|
147
|
+
"%s does not compile in the editor, so it cannot be attached. script_diagnostics on it gives "
|
|
148
|
+
+ "the parser's errors. If they deny a member that another class's file declares, the "
|
|
149
|
+
+ "editor's copy of that class is behind: editor_rescan with reloadScript on that class, then "
|
|
150
|
+
+ "ask again."
|
|
151
|
+
)
|
|
152
|
+
% script_path
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
138
156
|
## Why no node can be called [param node_name], wherever it sits, or "".
|
|
139
157
|
static func unfit_name(node_name: String) -> String:
|
|
140
158
|
if node_name.is_empty():
|
|
@@ -174,6 +174,9 @@ func create_resource(args: Dictionary) -> Dictionary:
|
|
|
174
174
|
var script: Script = load(script_file) as Script
|
|
175
175
|
if script == null:
|
|
176
176
|
return {"ok": false, "error": "No script loads from " + script_file}
|
|
177
|
+
var uncompiled: String = SceneFile.uncompiled(script, script_file)
|
|
178
|
+
if not uncompiled.is_empty():
|
|
179
|
+
return {"ok": false, "error": uncompiled}
|
|
177
180
|
var base: String = str(script.get_instance_base_type())
|
|
178
181
|
if not ClassDB.is_parent_class(resource_type, base):
|
|
179
182
|
return {
|
|
@@ -313,6 +313,10 @@ func create_scene(args: Dictionary) -> Dictionary:
|
|
|
313
313
|
if script == null:
|
|
314
314
|
root.free()
|
|
315
315
|
return {"ok": false, "error": "No script loads from " + full_script_path}
|
|
316
|
+
var uncompiled: String = SceneFile.uncompiled(script, full_script_path)
|
|
317
|
+
if not uncompiled.is_empty():
|
|
318
|
+
root.free()
|
|
319
|
+
return {"ok": false, "error": uncompiled}
|
|
316
320
|
var base: String = str(script.get_instance_base_type())
|
|
317
321
|
if not ClassDB.is_parent_class(root_node_type, base):
|
|
318
322
|
root.free()
|
package/build/index.js
CHANGED
|
@@ -34845,12 +34845,12 @@ var TOOL_SPECS = [
|
|
|
34845
34845
|
},
|
|
34846
34846
|
stop: {
|
|
34847
34847
|
effect: "destructive",
|
|
34848
|
-
summary: "end the run
|
|
34848
|
+
summary: "end the run, naming the process ended under endedPid, and answer with the errors and warnings it printed under entries (editor_output has the rest); a game that started processes of its own keeps them unless andChildren says to end those too",
|
|
34849
34849
|
requires: []
|
|
34850
34850
|
},
|
|
34851
34851
|
check: {
|
|
34852
34852
|
effect: "additive",
|
|
34853
|
-
summary: "boot headless, quit after a few frames, and report the verdict
|
|
34853
|
+
summary: "boot headless, quit after a few frames, and report the verdict: booted is true only for a run that quit on its own within timeoutMs, exited 0 and printed no error, so a game that reached _ready and then raised an error answers false",
|
|
34854
34854
|
requires: ["projectPath"]
|
|
34855
34855
|
},
|
|
34856
34856
|
wait: {
|
|
@@ -34860,10 +34860,11 @@ var TOOL_SPECS = [
|
|
|
34860
34860
|
},
|
|
34861
34861
|
profile: {
|
|
34862
34862
|
effect: "read",
|
|
34863
|
-
summary: "where the run spends its time by GDScript function, while it runs or after it ended
|
|
34863
|
+
summary: "where the run spends its time by GDScript function, while it runs or after it ended, sorted by self time. A run started with profile: true is profiled from its start; a game the editor plays that was not is profiled from the moment this is asked; a run this server started without it is refused, since its debugger is fixed as it starts",
|
|
34864
34864
|
requires: []
|
|
34865
34865
|
}
|
|
34866
34866
|
},
|
|
34867
|
+
answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked.",
|
|
34867
34868
|
defaultOperation: "start"
|
|
34868
34869
|
},
|
|
34869
34870
|
{
|
|
@@ -34921,7 +34922,8 @@ var TOOL_SPECS = [
|
|
|
34921
34922
|
name: "editor_status",
|
|
34922
34923
|
title: "Editor status",
|
|
34923
34924
|
effect: "read",
|
|
34924
|
-
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and
|
|
34925
|
+
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and which games with the runtime addon are reachable, of this project and of every other on the machine. Read connected with mayYetConnect: the editor dials this server and backs off between tries, so for the first half-minute after a server starts false means not reached yet as often as no editor; mayYetConnect true is worth waiting out, false is final, and restartInterrupted, restartUnderway, closedHere and editorsNotYetConnected say why. An editor that has connected and not yet greeted reports nothing about itself, and every other call made in that window waits up to ten seconds for it to settle. playingInEditor null means unanswered rather than nothing playing: processActive and runtimes do not depend on the editor answering. game.run names the run editor_output and editor_run stop answer for, and stays named after it ends. addonIsStale, on the editor and on each game, says it runs addon code other than what this server ships, and staleNote says which half moves: gdharness upgrade in the project, a restart, or a reconnect of this server. A false editor.lspListening or editor.dapListening comes with serversNote, and editor_launch restart binds the port again. editor.scan says whether the editor is scanning, which editor_run waits out before starting a game. A game announced in a protocol this server does not speak is listed under unreadable, during an upgrade that moves the protocol, until the halves match.",
|
|
34926
|
+
answer: "runtimeConnected counts the games of the project this server serves, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath. 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. game.run names the run processActive is about, the one editor_output and editor_run stop answer for: its pid, its project, and adopted true when this server read it back from another server rather than starting it. It stays named after the run ends, beside processActive false, so editor_output can still read what that run printed; null when there is no run at all. startedAt says when it began, and endedAt when it ended, wherever something saw the end: a run adopted from a note another server wrote can have ended hours before this server started. editor.lspListening and editor.dapListening say whether anything accepts connections on the ports the editor names, lspPort and dapPort: Godot binds each once, as the editor starts, and reports a failure only in the log panel of the editor itself, so a false comes with serversNote, and editor_launch restart binds it again. editor.scan says whether the editor is scanning or importing and how many milliseconds ago its last scan finished (finishedMsAgo), or is null when it did not answer: the editor rewrites the class cache just after a scan, and editor_run waits that out before starting a game, saying so under waitedForEditorScanMs. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout; for the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. A game the editor played announces the editor under editorPid, and so does whatever that game started for itself; a game some other server started announces none, which is how the two are told apart when both are running from one project. Each game also says which runtime addon it loaded, under addonVersion (null when it cannot say); addonIsStale is true when that code is not what this server ships, and staleNote says whether the project needs gdharness upgrade, the game a restart, or this server a reconnect. A second checkout of a project, such as a git worktree, carries addons of its own and is where this shows. Every runtime_* answer from such a game carries addonIsStale and staleNote too. 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. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for; editor_launch open finishes it. True beside restartUnderway is a restart still in progress, its server alive and within the wait for the old editor to go: the editor is coming, and opening another would put two on one project. 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. Any editor of this project running and not yet connected, whoever opened it, makes it true as well and is named under editorsNotYetConnected with its pid and start time, so a restart a reconnect interrupted after its launch is not read as nothing being there. 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. addonIsStale is decided by the code of the editor addons rather than their version, so an editor started before an upgrade that left that code alone reports the older addonVersion, is not stale, and addonNote says so: it needs no restart. port is the one the editor bridge is on; when that is not the one this server was configured with, portWanted names the configured one and portNote says why it moved and, when it was the server this one replaced, which pid kept it. A server replacing another of its project that holds the configured port waits for it to stand down and takes the port over, which takes a few seconds; bridgeHandover says so while it waits, and it moves only if that server has not let go within the wait. An editor that has connected and not yet greeted, which lasts a frame and longer while it imports, reports nothing about itself: greeting says so, addonIsStale is absent rather than guessed, and a restart asked then waits for the greeting to learn who opened it. startedByAnEditor is an editor another editor started, by Godot's own restart or the project manager, which has none of the arguments a server gave the one before it; startedByAnEditorNote says what that costs. An engine run that loads the editor addon to do one job and quit, such as a gate's godot --headless --import, an export or a script, is not taken as the editor: a process that greets without a server's mark has its command line read first, and while that is read identifying is true and connected false; one started with such an option is turned away, and turnedAway names the last one, its pid and the option. Every other call made while an editor is arriving, before its greeting or while it is looked up, waits up to ten seconds for it to settle rather than acting on connected as it reads in that window.",
|
|
34925
34927
|
parameters: {},
|
|
34926
34928
|
requires: []
|
|
34927
34929
|
},
|
|
@@ -35228,6 +35230,7 @@ var TOOL_SPECS = [
|
|
|
35228
35230
|
name: "runtime_input",
|
|
35229
35231
|
title: "Send input to the game",
|
|
35230
35232
|
description: "Input to the running game: a whole click on a Control or a 3D node named by path or on a control named by the words on it, an item chosen out of a menu, typing into whatever has the focus, or a raw action, key, mouse button or mouse motion. All of it works headless, where the window is 64 by 64 and the GUI only takes what is inside it. On gdharness's own desktop, where a windowed run goes, Windows refuses to say where the pointer is: every pointer answer there carries pointer_on_hidden_desktop, and the first of the run pointer_note saying why it matters: get_mouse_position() on the root window, and the mouse positions derived from it, read a meaningless position, while what reads the event itself (the control under it, hover, tooltips, input handlers) gets the injected position.",
|
|
35233
|
+
answer: 'click: what was under the pointer, and what became of the control: in_tree, removed or freed. Named by says, found says which match it was, and found.pressedOn says "the words" for words in a RichTextLabel pressed where they are drawn, or "the middle of the label" with a note saying why they could not be placed; on a rich label link_pressed says whether the press reached a link, with link_meta giving the meta the label reports clicked, an empty one included. A control scrolled into view first is scrolled each container out from it a frame after the one inside, and scrolled_into_view says whether a scroll value changed. A button in toggle mode, a CheckBox or CheckButton included, answers pressed with before and after, its button_pressed either side of the click: the same value twice is a click the game undid, and after is null when the click freed it. A refusal for a pointer turned off names where mouse_behavior_recursive was set. On a 3D node landed says the interface did not swallow the press: no control under the pointer stopped it, a control letting the pointer pass included, and every viewport on the way out from the room had the pointer over what shows the room. choose: what was chosen, read before the press, which is heard rather than assumed; what became of the control named (in_tree, removed or freed, since a game can rebuild its screen on the pick); and what the button in front of the menu shows now when it is still there. A menu is opened rather than clicked because its items are drawn rather than built, and the item is looked for as the menu stands once open, since a game can fill a menu as it opens.',
|
|
35231
35234
|
parameters: {
|
|
35232
35235
|
projectPath: RUNNING_PROJECT_PATH,
|
|
35233
35236
|
pid: RUNNING_PID,
|
|
@@ -35317,12 +35320,12 @@ var TOOL_SPECS = [
|
|
|
35317
35320
|
operations: {
|
|
35318
35321
|
click: {
|
|
35319
35322
|
effect: "additive",
|
|
35320
|
-
summary:
|
|
35323
|
+
summary: "press and release on a Control, a frame apart, named by nodePath or by says with the words on it (found under nodePath when both are given), or on a 3D node where it is drawn. A control out of sight inside a ScrollContainer is scrolled to first, and a link in a RichTextLabel is pressed where its words are drawn. A disabled button, a control that does not process and one whose pointer mouse_behavior_recursive turned off are refused, since the press would do nothing; by says, one the pointer can reach is picked over it",
|
|
35321
35324
|
requires: []
|
|
35322
35325
|
},
|
|
35323
35326
|
choose: {
|
|
35324
35327
|
effect: "additive",
|
|
35325
|
-
summary: "take an item out of a menu, by what it says or by where it is in the list
|
|
35328
|
+
summary: "take an item out of a menu, by what it says or by where it is in the list: the menu is opened, the item found in it as it stands then, and pressed through the engine's own path, which needs no window and does not depend on what ui_accept is bound to. A disabled or hidden button in front of the menu is refused, and so is a press the menu did not take",
|
|
35326
35329
|
requires: ["nodePath"]
|
|
35327
35330
|
},
|
|
35328
35331
|
action: { effect: "additive", summary: "press an action, or hold it", requires: ["action"] },
|
|
@@ -35486,6 +35489,10 @@ function describeOperations(spec) {
|
|
|
35486
35489
|
const fallback = spec.defaultOperation ? ` Default ${spec.defaultOperation}.` : "";
|
|
35487
35490
|
return ` Operations: ${lines.join("; ")}.${fallback}`;
|
|
35488
35491
|
}
|
|
35492
|
+
function servedDescription(spec) {
|
|
35493
|
+
const answer = spec.answer === undefined ? "" : " The tool reference in the gdharness skill, and the site, say what every field of the answer means.";
|
|
35494
|
+
return `${spec.description}${describeOperations(spec)}${answer}`;
|
|
35495
|
+
}
|
|
35489
35496
|
function buildToolDefinitions() {
|
|
35490
35497
|
return TOOL_SPECS.map((spec) => {
|
|
35491
35498
|
const properties = {};
|
|
@@ -35507,7 +35514,7 @@ function buildToolDefinitions() {
|
|
|
35507
35514
|
return {
|
|
35508
35515
|
name: spec.name,
|
|
35509
35516
|
title: spec.title,
|
|
35510
|
-
description:
|
|
35517
|
+
description: servedDescription(spec),
|
|
35511
35518
|
inputSchema: {
|
|
35512
35519
|
type: "object",
|
|
35513
35520
|
properties,
|
|
@@ -36310,6 +36317,14 @@ var PLAYING_STATUS_MS = 1000;
|
|
|
36310
36317
|
var SCAN_START_MS = 2000;
|
|
36311
36318
|
var SCAN_SETTLED_MS = 500;
|
|
36312
36319
|
var REIMPORT_TIMEOUT_MS = 300000;
|
|
36320
|
+
var OPS_ELSEWHERE = {
|
|
36321
|
+
runtime_invoke: { get: "runtime_inspect property", property: "runtime_inspect property" }
|
|
36322
|
+
};
|
|
36323
|
+
function opElsewhere(tool, requested) {
|
|
36324
|
+
const ops = Object.hasOwn(OPS_ELSEWHERE, tool) ? OPS_ELSEWHERE[tool] : undefined;
|
|
36325
|
+
const where = ops !== undefined && typeof requested === "string" && Object.hasOwn(ops, requested) ? ops[requested] : undefined;
|
|
36326
|
+
return where === undefined ? "" : ` A value is read with ${where}.`;
|
|
36327
|
+
}
|
|
36313
36328
|
var AUDIO_BUS_OPS = new Set([
|
|
36314
36329
|
"add_audio_bus",
|
|
36315
36330
|
"remove_audio_bus",
|
|
@@ -37413,7 +37428,7 @@ class GodotServer {
|
|
|
37413
37428
|
} else {
|
|
37414
37429
|
return {
|
|
37415
37430
|
ok: false,
|
|
37416
|
-
response: this.createErrorResponse(`${spec.name} has no op ${JSON.stringify(requested)}. Valid ops: ${valid.join(", ")}
|
|
37431
|
+
response: this.createErrorResponse(`${spec.name} has no op ${JSON.stringify(requested)}. Valid ops: ${valid.join(", ")}.${opElsewhere(spec.name, requested)}`)
|
|
37417
37432
|
};
|
|
37418
37433
|
}
|
|
37419
37434
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.18",
|
|
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",
|