gdharness 1.2.2 → 1.2.4
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
|
@@ -42117,6 +42117,30 @@ function projectKey(projectPath) {
|
|
|
42117
42117
|
function runRecordPath(projectPath, directory = runtimeDirectory()) {
|
|
42118
42118
|
return join24(directory, "runs", `note-${projectKey(projectPath)}.json`);
|
|
42119
42119
|
}
|
|
42120
|
+
function latestRunPath(servedBy, directory = runtimeDirectory()) {
|
|
42121
|
+
return join24(directory, "runs", `latest-${projectKey(servedBy)}.json`);
|
|
42122
|
+
}
|
|
42123
|
+
function writeLatestRun(servedBy, latest) {
|
|
42124
|
+
try {
|
|
42125
|
+
mkdirSync14(runsDirectory(), { recursive: true });
|
|
42126
|
+
writeFileSync16(latestRunPath(servedBy), JSON.stringify(latest, null, 2), "utf8");
|
|
42127
|
+
} catch {}
|
|
42128
|
+
}
|
|
42129
|
+
function readLatestRun(servedBy, directory = runtimeDirectory()) {
|
|
42130
|
+
try {
|
|
42131
|
+
const fields = JSON.parse(readFileSync21(latestRunPath(servedBy, directory), "utf8"));
|
|
42132
|
+
if (typeof fields !== "object" || fields === null) {
|
|
42133
|
+
return null;
|
|
42134
|
+
}
|
|
42135
|
+
const { projectPath, pid, startedAt } = fields;
|
|
42136
|
+
if (typeof projectPath !== "string" || typeof startedAt !== "number") {
|
|
42137
|
+
return null;
|
|
42138
|
+
}
|
|
42139
|
+
return { projectPath, pid: typeof pid === "number" ? pid : null, startedAt };
|
|
42140
|
+
} catch {
|
|
42141
|
+
return null;
|
|
42142
|
+
}
|
|
42143
|
+
}
|
|
42120
42144
|
function openTranscript(startedAt) {
|
|
42121
42145
|
mkdirSync14(runsDirectory(), { recursive: true });
|
|
42122
42146
|
const path = join24(runsDirectory(), `run-${startedAt}.log`);
|
|
@@ -42866,6 +42890,9 @@ function runIsUp(run, editorSays, editorPlay = null, now = Date.now()) {
|
|
|
42866
42890
|
function noCodeWillCome(run) {
|
|
42867
42891
|
return !exited(run);
|
|
42868
42892
|
}
|
|
42893
|
+
function transcriptNote(transcript, going) {
|
|
42894
|
+
return going ? `Everything this run has printed is in ${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.` : `Everything this run printed is in ${transcript}, uncapped and complete, since the run is over.`;
|
|
42895
|
+
}
|
|
42869
42896
|
function stopWaitMs() {
|
|
42870
42897
|
return waitFromEnv("GDHARNESS_STOP_WAIT_MS", 1e4);
|
|
42871
42898
|
}
|
|
@@ -46560,6 +46587,7 @@ class GodotServer {
|
|
|
46560
46587
|
...which === null ? {} : { play: which }
|
|
46561
46588
|
};
|
|
46562
46589
|
this.activeProcess = played;
|
|
46590
|
+
this.noteTheLatestRun({ projectPath, pid: null, startedAt });
|
|
46563
46591
|
this.watchThePlayedConsole();
|
|
46564
46592
|
writeEditorRunNote({
|
|
46565
46593
|
projectPath,
|
|
@@ -46912,6 +46940,7 @@ class GodotServer {
|
|
|
46912
46940
|
return launched;
|
|
46913
46941
|
}
|
|
46914
46942
|
const startedBy = Date.now();
|
|
46943
|
+
this.noteTheLatestRun({ projectPath, pid: launched.pid, startedAt });
|
|
46915
46944
|
sweepTranscripts();
|
|
46916
46945
|
const noted = readRunRecord(projectPath);
|
|
46917
46946
|
const keeper = noted?.pid === launched.pid ? noted.keeper : undefined;
|
|
@@ -47150,7 +47179,8 @@ class GodotServer {
|
|
|
47150
47179
|
async adoptRecordedRun() {
|
|
47151
47180
|
const project = this.ourProject();
|
|
47152
47181
|
const found = project === null ? [] : [readRunRecord(project), this.startedForUs(project)];
|
|
47153
|
-
const
|
|
47182
|
+
const latest = project === null ? null : readLatestRun(project);
|
|
47183
|
+
const record = latest === null ? found.reduce((newest, one) => one !== null && (newest === null || one.startedAt > newest.startedAt) ? one : newest, null) : found.find((one) => one !== null && one.pid === latest.pid && one.startedAt === latest.startedAt) ?? null;
|
|
47154
47184
|
if (record === null || !(this.couldBeOurs(record.projectPath) || this.couldBeOurs(record.servedBy ?? ""))) {
|
|
47155
47185
|
return null;
|
|
47156
47186
|
}
|
|
@@ -47190,6 +47220,12 @@ class GodotServer {
|
|
|
47190
47220
|
const mine = this.ourProject();
|
|
47191
47221
|
return mine === null ? {} : { servedBy: mine };
|
|
47192
47222
|
}
|
|
47223
|
+
noteTheLatestRun(latest) {
|
|
47224
|
+
const mine = this.ourProject();
|
|
47225
|
+
if (mine !== null) {
|
|
47226
|
+
writeLatestRun(mine, latest);
|
|
47227
|
+
}
|
|
47228
|
+
}
|
|
47193
47229
|
startedForUs(project) {
|
|
47194
47230
|
return everyRunRecord().find((one) => one.servedBy !== undefined && isSameDirectory(one.servedBy, project) && !(one.projectPath !== "" && isSameDirectory(one.projectPath, project))) ?? null;
|
|
47195
47231
|
}
|
|
@@ -47601,7 +47637,7 @@ class GodotServer {
|
|
|
47601
47637
|
notes.push(`The debug adapter this run's console arrives over went away while the run was going, so what is below has a hole in it and the counts are of what was heard rather than of what was printed. ${back ? "It is connected again, and the line marking where is in the log." : "It is still gone, so nothing further will arrive here."} The project's own user://logs/godot.log has the rest, and "no entries" here means "not heard" rather than a quiet run.`);
|
|
47602
47638
|
}
|
|
47603
47639
|
if (run.transcript !== null && selected.omitted > 0) {
|
|
47604
|
-
notes.push(
|
|
47640
|
+
notes.push(transcriptNote(run.transcript, going));
|
|
47605
47641
|
}
|
|
47606
47642
|
return this.jsonTextResponse({
|
|
47607
47643
|
running: going,
|
|
@@ -377,10 +377,15 @@ static func drawn_over(target: Control) -> Node:
|
|
|
377
377
|
|
|
378
378
|
|
|
379
379
|
## Why [param node], which is not visible in the tree, is not, as a parenthesis to follow its path:
|
|
380
|
-
## which nodes hide it, nearest first, and what
|
|
381
|
-
##
|
|
382
|
-
##
|
|
383
|
-
##
|
|
380
|
+
## which nodes hide it, nearest first, and what has taken its place. Reading visible off the node
|
|
381
|
+
## itself says true when only an ancestor is hidden, so without this a caller had nothing to unhide;
|
|
382
|
+
## every hidden one is named because unhiding only the nearest leaves the rest hiding it.
|
|
383
|
+
##
|
|
384
|
+
## What has taken its place is what a click at its centre reaches now, which is the screen a game
|
|
385
|
+
## shows instead. Without a control there, as for a 3D node, it is what is shown beside the
|
|
386
|
+
## outermost hidden node: a game hides a screen by hiding the screen's root, and the nearest hidden
|
|
387
|
+
## node can be a panel deep inside it whose siblings are hidden with it. "" when nothing on the way
|
|
388
|
+
## up says the node is hidden.
|
|
384
389
|
static func why_hidden(node: Node) -> String:
|
|
385
390
|
var hiding: Array[Node] = _hiding(node)
|
|
386
391
|
if hiding.is_empty():
|
|
@@ -391,27 +396,71 @@ static func why_hidden(node: Node) -> String:
|
|
|
391
396
|
named.append("its own visible is false")
|
|
392
397
|
else:
|
|
393
398
|
named.append("%s is hidden%s" % [one.get_path(), "" if named.is_empty() else " too"])
|
|
399
|
+
var control: Control = node as Control
|
|
400
|
+
var instead: Node = null if control == null else drawn_at(control)
|
|
401
|
+
if instead != null:
|
|
402
|
+
return " (%s; a click where it would be reaches %s)" % [", and ".join(named), instead.get_path()]
|
|
403
|
+
var outermost: Node = hiding.back()
|
|
394
404
|
var beside: Array[String] = []
|
|
395
|
-
var parent: Node =
|
|
405
|
+
var parent: Node = outermost.get_parent()
|
|
396
406
|
if parent != null:
|
|
397
407
|
for sibling: Node in parent.get_children():
|
|
398
|
-
if sibling !=
|
|
408
|
+
if sibling != outermost and _shown(sibling):
|
|
399
409
|
beside.append(str(sibling.get_path()))
|
|
400
|
-
return " (%s%s)" % [", and ".join(named), _shown_beside(beside)]
|
|
410
|
+
return " (%s%s)" % [", and ".join(named), _shown_beside(beside, outermost)]
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
## What a click at the centre of [param target], which is hidden, would reach instead, or null.
|
|
414
|
+
##
|
|
415
|
+
## The topmost thing there that takes the pointer, whatever order it is drawn in relative to the
|
|
416
|
+
## target, since the target is not drawn at all; and never a node holding the target, which covers
|
|
417
|
+
## its place by containing it and is not what took it.
|
|
418
|
+
static func drawn_at(target: Control) -> Node:
|
|
419
|
+
var drawn: Array[Node] = _pointer_takers(target.get_tree().root)
|
|
420
|
+
var point: Vector2 = _centre_of(target)
|
|
421
|
+
var none: Dictionary[ScrollContainer, Vector2] = {}
|
|
422
|
+
var viewport: Viewport = target.get_viewport()
|
|
423
|
+
var best: Control = null
|
|
424
|
+
for each: Node in drawn:
|
|
425
|
+
var window: Window = each as Window
|
|
426
|
+
if window != null:
|
|
427
|
+
if _window_over(target, point, window):
|
|
428
|
+
return window
|
|
429
|
+
continue
|
|
430
|
+
var control: Control = each
|
|
431
|
+
if control.is_ancestor_of(target) or target.is_ancestor_of(control):
|
|
432
|
+
continue
|
|
433
|
+
if control.get_viewport() != viewport or not _holds(control, point):
|
|
434
|
+
continue
|
|
435
|
+
if _clipped_away(control, point, none):
|
|
436
|
+
continue
|
|
437
|
+
if best == null or _drawn_above(control, best):
|
|
438
|
+
best = control
|
|
439
|
+
return best
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
## Whether [param control] is drawn above [param other]: on a higher canvas layer, or later on the
|
|
443
|
+
## same one.
|
|
444
|
+
static func _drawn_above(control: Control, other: Control) -> bool:
|
|
445
|
+
var layer: int = _layer_of(control)
|
|
446
|
+
var other_layer: int = _layer_of(other)
|
|
447
|
+
return layer > other_layer or (layer == other_layer and control.is_greater_than(other))
|
|
401
448
|
|
|
402
449
|
|
|
403
|
-
## "; A, B and C
|
|
404
|
-
|
|
450
|
+
## "; A, B and C are shown beside N" for [param beside] and the hidden node [param hidden], naming
|
|
451
|
+
## three at most and counting the rest.
|
|
452
|
+
static func _shown_beside(beside: Array[String], hidden: Node) -> String:
|
|
405
453
|
if beside.is_empty():
|
|
406
454
|
return ""
|
|
455
|
+
var at: String = str(hidden.get_path())
|
|
407
456
|
if beside.size() == 1:
|
|
408
|
-
return "; %s
|
|
457
|
+
return "; %s is shown beside %s" % [beside[0], at]
|
|
409
458
|
var listed: Array[String] = beside.slice(0, 3)
|
|
410
459
|
var rest: int = beside.size() - listed.size()
|
|
411
460
|
if rest > 0:
|
|
412
|
-
return "; %s and %d more
|
|
461
|
+
return "; %s and %d more are shown beside %s" % [", ".join(listed), rest, at]
|
|
413
462
|
var last: String = listed.pop_back()
|
|
414
|
-
return "; %s and %s
|
|
463
|
+
return "; %s and %s are shown beside %s" % [", ".join(listed), last, at]
|
|
415
464
|
|
|
416
465
|
|
|
417
466
|
## The nodes whose visible is false and keeps [param node] from being drawn, nearest first. Walked
|
package/build/index.js
CHANGED
|
@@ -32922,6 +32922,30 @@ function projectKey(projectPath) {
|
|
|
32922
32922
|
function runRecordPath(projectPath, directory = runtimeDirectory()) {
|
|
32923
32923
|
return join19(directory, "runs", `note-${projectKey(projectPath)}.json`);
|
|
32924
32924
|
}
|
|
32925
|
+
function latestRunPath(servedBy, directory = runtimeDirectory()) {
|
|
32926
|
+
return join19(directory, "runs", `latest-${projectKey(servedBy)}.json`);
|
|
32927
|
+
}
|
|
32928
|
+
function writeLatestRun(servedBy, latest) {
|
|
32929
|
+
try {
|
|
32930
|
+
mkdirSync11(runsDirectory(), { recursive: true });
|
|
32931
|
+
writeFileSync13(latestRunPath(servedBy), JSON.stringify(latest, null, 2), "utf8");
|
|
32932
|
+
} catch {}
|
|
32933
|
+
}
|
|
32934
|
+
function readLatestRun(servedBy, directory = runtimeDirectory()) {
|
|
32935
|
+
try {
|
|
32936
|
+
const fields = JSON.parse(readFileSync18(latestRunPath(servedBy, directory), "utf8"));
|
|
32937
|
+
if (typeof fields !== "object" || fields === null) {
|
|
32938
|
+
return null;
|
|
32939
|
+
}
|
|
32940
|
+
const { projectPath, pid, startedAt } = fields;
|
|
32941
|
+
if (typeof projectPath !== "string" || typeof startedAt !== "number") {
|
|
32942
|
+
return null;
|
|
32943
|
+
}
|
|
32944
|
+
return { projectPath, pid: typeof pid === "number" ? pid : null, startedAt };
|
|
32945
|
+
} catch {
|
|
32946
|
+
return null;
|
|
32947
|
+
}
|
|
32948
|
+
}
|
|
32925
32949
|
var SHARED_NOTE = "run.json";
|
|
32926
32950
|
function openTranscript(startedAt) {
|
|
32927
32951
|
mkdirSync11(runsDirectory(), { recursive: true });
|
|
@@ -35294,6 +35318,9 @@ function runIsUp(run, editorSays, editorPlay = null, now = Date.now()) {
|
|
|
35294
35318
|
function noCodeWillCome(run) {
|
|
35295
35319
|
return !exited(run);
|
|
35296
35320
|
}
|
|
35321
|
+
function transcriptNote(transcript, going) {
|
|
35322
|
+
return going ? `Everything this run has printed is in ${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.` : `Everything this run printed is in ${transcript}, uncapped and complete, since the run is over.`;
|
|
35323
|
+
}
|
|
35297
35324
|
function stopWaitMs() {
|
|
35298
35325
|
return waitFromEnv("GDHARNESS_STOP_WAIT_MS", 1e4);
|
|
35299
35326
|
}
|
|
@@ -39057,6 +39084,7 @@ class GodotServer {
|
|
|
39057
39084
|
...which === null ? {} : { play: which }
|
|
39058
39085
|
};
|
|
39059
39086
|
this.activeProcess = played;
|
|
39087
|
+
this.noteTheLatestRun({ projectPath, pid: null, startedAt });
|
|
39060
39088
|
this.watchThePlayedConsole();
|
|
39061
39089
|
writeEditorRunNote({
|
|
39062
39090
|
projectPath,
|
|
@@ -39409,6 +39437,7 @@ class GodotServer {
|
|
|
39409
39437
|
return launched;
|
|
39410
39438
|
}
|
|
39411
39439
|
const startedBy = Date.now();
|
|
39440
|
+
this.noteTheLatestRun({ projectPath, pid: launched.pid, startedAt });
|
|
39412
39441
|
sweepTranscripts();
|
|
39413
39442
|
const noted = readRunRecord(projectPath);
|
|
39414
39443
|
const keeper = noted?.pid === launched.pid ? noted.keeper : undefined;
|
|
@@ -39647,7 +39676,8 @@ class GodotServer {
|
|
|
39647
39676
|
async adoptRecordedRun() {
|
|
39648
39677
|
const project = this.ourProject();
|
|
39649
39678
|
const found = project === null ? [] : [readRunRecord(project), this.startedForUs(project)];
|
|
39650
|
-
const
|
|
39679
|
+
const latest = project === null ? null : readLatestRun(project);
|
|
39680
|
+
const record = latest === null ? found.reduce((newest, one) => one !== null && (newest === null || one.startedAt > newest.startedAt) ? one : newest, null) : found.find((one) => one !== null && one.pid === latest.pid && one.startedAt === latest.startedAt) ?? null;
|
|
39651
39681
|
if (record === null || !(this.couldBeOurs(record.projectPath) || this.couldBeOurs(record.servedBy ?? ""))) {
|
|
39652
39682
|
return null;
|
|
39653
39683
|
}
|
|
@@ -39687,6 +39717,12 @@ class GodotServer {
|
|
|
39687
39717
|
const mine = this.ourProject();
|
|
39688
39718
|
return mine === null ? {} : { servedBy: mine };
|
|
39689
39719
|
}
|
|
39720
|
+
noteTheLatestRun(latest) {
|
|
39721
|
+
const mine = this.ourProject();
|
|
39722
|
+
if (mine !== null) {
|
|
39723
|
+
writeLatestRun(mine, latest);
|
|
39724
|
+
}
|
|
39725
|
+
}
|
|
39690
39726
|
startedForUs(project) {
|
|
39691
39727
|
return everyRunRecord().find((one) => one.servedBy !== undefined && isSameDirectory(one.servedBy, project) && !(one.projectPath !== "" && isSameDirectory(one.projectPath, project))) ?? null;
|
|
39692
39728
|
}
|
|
@@ -40098,7 +40134,7 @@ class GodotServer {
|
|
|
40098
40134
|
notes.push(`The debug adapter this run's console arrives over went away while the run was going, so what is below has a hole in it and the counts are of what was heard rather than of what was printed. ${back ? "It is connected again, and the line marking where is in the log." : "It is still gone, so nothing further will arrive here."} The project's own user://logs/godot.log has the rest, and "no entries" here means "not heard" rather than a quiet run.`);
|
|
40099
40135
|
}
|
|
40100
40136
|
if (run.transcript !== null && selected.omitted > 0) {
|
|
40101
|
-
notes.push(
|
|
40137
|
+
notes.push(transcriptNote(run.transcript, going));
|
|
40102
40138
|
}
|
|
40103
40139
|
return this.jsonTextResponse({
|
|
40104
40140
|
running: going,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
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",
|