gdharness 1.3.5 → 1.3.7
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 +2409 -706
- package/build/godot/addons/gdharness_editor/reading.gd +1 -1
- package/build/godot/addons/gdharness_editor/tool_executor.gd +1 -0
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +60 -10
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +11 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +1 -1
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +45 -1
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +19 -4
- package/build/godot/addons/gdharness_runtime/runtime_hover.gd +1 -1
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +1 -1
- package/build/godot/operations/reading.gd +1 -1
- package/build/godot/tests/listed.gd +263 -0
- package/build/keeper.js +69 -11
- package/package.json +1 -1
package/build/keeper.js
CHANGED
|
@@ -106,6 +106,9 @@ public static class GdharnessDesktop {
|
|
|
106
106
|
[DllImport("kernel32.dll")] static extern bool GetExitCodeProcess(IntPtr handle, out uint code);
|
|
107
107
|
[DllImport("kernel32.dll")] static extern bool CloseHandle(IntPtr handle);
|
|
108
108
|
[DllImport("kernel32.dll")] static extern bool TerminateProcess(IntPtr handle, uint code);
|
|
109
|
+
[DllImport("kernel32.dll")] static extern uint SetErrorMode(uint mode);
|
|
110
|
+
const uint FailCriticalErrors = 0x1;
|
|
111
|
+
const uint NoOpenFileErrorBox = 0x8000;
|
|
109
112
|
|
|
110
113
|
delegate bool EachWindow(IntPtr window, IntPtr unused);
|
|
111
114
|
[DllImport("user32.dll")] static extern bool EnumWindows(EachWindow each, IntPtr unused);
|
|
@@ -179,6 +182,11 @@ public static class GdharnessDesktop {
|
|
|
179
182
|
startup.output = GetStdHandle(-11);
|
|
180
183
|
startup.error = startup.output;
|
|
181
184
|
Started started;
|
|
185
|
+
// Inherited by what it starts: without SEM_NOGPFAULTERRORBOX, Windows Error Reporting runs when
|
|
186
|
+
// the target crashes and writes its dump, and with the other two no hard-error box can open.
|
|
187
|
+
// Set outright rather than cleared from this process's own: Windows PowerShell changes the mode
|
|
188
|
+
// it inherited as it starts, and was seen reading 0x1 here, without the open-file bit.
|
|
189
|
+
SetErrorMode(FailCriticalErrors | NoOpenFileErrorBox);
|
|
182
190
|
if (noActivate) {
|
|
183
191
|
LockSetForegroundWindow(LockForeground);
|
|
184
192
|
}
|
|
@@ -360,6 +368,33 @@ function throughHelper(how, command, args, files, assembly = helperAssembly()) {
|
|
|
360
368
|
};
|
|
361
369
|
}
|
|
362
370
|
|
|
371
|
+
// src/error-mode.ts
|
|
372
|
+
import process3 from "node:process";
|
|
373
|
+
var NO_FAULT_ERROR_BOX = 2;
|
|
374
|
+
var cleared = process3.platform !== "win32";
|
|
375
|
+
async function letWindowsReportCrashes() {
|
|
376
|
+
if (cleared || !("bun" in process3.versions)) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
const { dlopen, FFIType } = await import("bun:ffi");
|
|
381
|
+
const kernel32 = dlopen("kernel32.dll", {
|
|
382
|
+
GetErrorMode: { args: [], returns: FFIType.u32 },
|
|
383
|
+
SetErrorMode: { args: [FFIType.u32], returns: FFIType.u32 }
|
|
384
|
+
});
|
|
385
|
+
try {
|
|
386
|
+
const mode = kernel32.symbols.GetErrorMode();
|
|
387
|
+
kernel32.symbols.SetErrorMode(mode & ~NO_FAULT_ERROR_BOX);
|
|
388
|
+
cleared = true;
|
|
389
|
+
} finally {
|
|
390
|
+
kernel32.close();
|
|
391
|
+
}
|
|
392
|
+
} catch {}
|
|
393
|
+
}
|
|
394
|
+
function crashesReported() {
|
|
395
|
+
return cleared;
|
|
396
|
+
}
|
|
397
|
+
|
|
363
398
|
// src/game-debugger.ts
|
|
364
399
|
import { createServer } from "node:net";
|
|
365
400
|
|
|
@@ -668,6 +703,8 @@ function readMessage(body) {
|
|
|
668
703
|
// src/game-debugger.ts
|
|
669
704
|
var PAUSE_MS = 30000;
|
|
670
705
|
var STEP_MS = 1e4;
|
|
706
|
+
var ADDON_STEP_MS = 5000;
|
|
707
|
+
var RUNTIME_ADDON = "res://addons/gdharness_runtime/";
|
|
671
708
|
var READ_MS = 5000;
|
|
672
709
|
var REPLIES = new Set([
|
|
673
710
|
"debug_enter",
|
|
@@ -1050,9 +1087,29 @@ class GameDebugger {
|
|
|
1050
1087
|
};
|
|
1051
1088
|
}
|
|
1052
1089
|
}
|
|
1090
|
+
let frames = await this.stack();
|
|
1091
|
+
const stepping = Date.now();
|
|
1092
|
+
while (frames !== null && this.held !== null && (frames[0]?.script.startsWith(RUNTIME_ADDON) ?? false) && Date.now() - stepping < ADDON_STEP_MS) {
|
|
1093
|
+
this.forget("debug_enter");
|
|
1094
|
+
this.send("step", [], this.held.thread);
|
|
1095
|
+
if (await this.next(GameDebugger.named("debug_enter"), STEP_MS) === null) {
|
|
1096
|
+
return {
|
|
1097
|
+
paused: false,
|
|
1098
|
+
note: `The game stopped in gdharness's runtime addon and was stepped on to reach its own script, and did not stop again within ${STEP_MS / 1000}s. It stops at the next line of script it runs; debug_state stack reads it then.`
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
frames = await this.stack();
|
|
1102
|
+
}
|
|
1053
1103
|
this.stoppedBy("Paused");
|
|
1054
|
-
const
|
|
1055
|
-
return {
|
|
1104
|
+
const stack = frames ?? [];
|
|
1105
|
+
return {
|
|
1106
|
+
paused: true,
|
|
1107
|
+
held: this.heldState(),
|
|
1108
|
+
stack,
|
|
1109
|
+
...stack[0]?.script.startsWith(RUNTIME_ADDON) === true ? {
|
|
1110
|
+
note: `The game is held in gdharness's runtime addon, whose _process runs first in every frame: ${ADDON_STEP_MS / 1000}s of stepping reached no line of the game's own script, so it runs none from frame to frame. A breakpoint in the game's script stops it there.`
|
|
1111
|
+
} : {}
|
|
1112
|
+
};
|
|
1056
1113
|
}
|
|
1057
1114
|
stoppedBy(cause) {
|
|
1058
1115
|
if (this.held !== null) {
|
|
@@ -1073,7 +1130,7 @@ class GameDebugger {
|
|
|
1073
1130
|
return await exited === null ? { refused: `The game did not say it had gone on within ${READ_MS / 1000}s.` } : { continued: true };
|
|
1074
1131
|
}
|
|
1075
1132
|
this.forget("debug_enter");
|
|
1076
|
-
this.send(how, [], held.thread);
|
|
1133
|
+
this.send(held.hasStack ? how : "step", [], held.thread);
|
|
1077
1134
|
const entered = await this.next(GameDebugger.named("debug_enter"), STEP_MS);
|
|
1078
1135
|
if (entered === null) {
|
|
1079
1136
|
return {
|
|
@@ -1142,7 +1199,7 @@ import { mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "node:fs";
|
|
|
1142
1199
|
import { connect, createServer as createServer2 } from "node:net";
|
|
1143
1200
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
1144
1201
|
import { join as join2 } from "node:path";
|
|
1145
|
-
import
|
|
1202
|
+
import process4 from "node:process";
|
|
1146
1203
|
var DEBUG_PREFIX = "debug ";
|
|
1147
1204
|
function endedOrWhyNot(end) {
|
|
1148
1205
|
try {
|
|
@@ -1169,7 +1226,7 @@ function errorText(error) {
|
|
|
1169
1226
|
async function listenForTheServer(end, debug) {
|
|
1170
1227
|
let directory = null;
|
|
1171
1228
|
try {
|
|
1172
|
-
directory =
|
|
1229
|
+
directory = process4.platform === "win32" ? null : mkdtempSync2(join2(tmpdir2(), "gdharness-keeper-"));
|
|
1173
1230
|
const address = directory === null ? `\\\\.\\pipe\\gdharness-keeper-${randomUUID()}` : join2(directory, "keeper.sock");
|
|
1174
1231
|
const server = createServer2((socket) => {
|
|
1175
1232
|
socket.setEncoding("utf8");
|
|
@@ -1469,13 +1526,13 @@ function recordAt(path) {
|
|
|
1469
1526
|
import { existsSync as existsSync2, mkdtempSync as mkdtempSync3, readdirSync as readdirSync2, rmSync as rmSync5 } from "node:fs";
|
|
1470
1527
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
1471
1528
|
import { join as join6 } from "node:path";
|
|
1472
|
-
import
|
|
1529
|
+
import process5 from "node:process";
|
|
1473
1530
|
var SWEPT = { recursive: true, force: true };
|
|
1474
1531
|
var AGAIN_AFTER_MS = [250, 500, 1000, 2000, 4000, 8000, 16000];
|
|
1475
1532
|
var waiting = new Set;
|
|
1476
1533
|
var atExit = new Set;
|
|
1477
1534
|
function scratchDirectory(kind) {
|
|
1478
|
-
return mkdtempSync3(join6(tmpdir4(), `gdharness-${kind}-${
|
|
1535
|
+
return mkdtempSync3(join6(tmpdir4(), `gdharness-${kind}-${process5.pid}-`));
|
|
1479
1536
|
}
|
|
1480
1537
|
function discardWith(remove, paths) {
|
|
1481
1538
|
const left = [];
|
|
@@ -1533,7 +1590,7 @@ function lastTryOnExit() {
|
|
|
1533
1590
|
return;
|
|
1534
1591
|
}
|
|
1535
1592
|
lastTryWatched = true;
|
|
1536
|
-
|
|
1593
|
+
process5.once("exit", () => {
|
|
1537
1594
|
for (const path of [...waiting, ...atExit]) {
|
|
1538
1595
|
try {
|
|
1539
1596
|
remove(path);
|
|
@@ -1937,7 +1994,7 @@ async function startThroughHelper(spec, output, stopFile) {
|
|
|
1937
1994
|
const scratch = scratchDirectory("desktop");
|
|
1938
1995
|
const pidFile = join7(scratch, "pid");
|
|
1939
1996
|
const errorFile = join7(scratch, "error");
|
|
1940
|
-
const where = spec.desktop
|
|
1997
|
+
const where = spec.desktop !== undefined ? `on the ${spec.desktop} desktop` : spec.noActivate === true ? "without activation" : "through the desktop helper";
|
|
1941
1998
|
try {
|
|
1942
1999
|
const compiled = await ensureHelperCompiled();
|
|
1943
2000
|
if (compiled !== null) {
|
|
@@ -2020,10 +2077,10 @@ async function startDetached(spec, output, hidden, stopFile = "") {
|
|
|
2020
2077
|
return throughTheHelper(spec) ? await startThroughHelper(spec, output, stopFile) : await startDirectly(spec, output, hidden);
|
|
2021
2078
|
}
|
|
2022
2079
|
function throughTheHelper(spec) {
|
|
2023
|
-
return process.platform === "win32" && (
|
|
2080
|
+
return needsItsHelperKept(spec) || process.platform === "win32" && !crashesReported();
|
|
2024
2081
|
}
|
|
2025
2082
|
function needsItsHelperKept(spec) {
|
|
2026
|
-
return
|
|
2083
|
+
return process.platform === "win32" && (spec.desktop !== undefined || spec.noActivate === true);
|
|
2027
2084
|
}
|
|
2028
2085
|
async function launch() {
|
|
2029
2086
|
const spec = await receivedSpec();
|
|
@@ -2158,6 +2215,7 @@ async function keep() {
|
|
|
2158
2215
|
}
|
|
2159
2216
|
process.exit(0);
|
|
2160
2217
|
}
|
|
2218
|
+
await letWindowsReportCrashes();
|
|
2161
2219
|
var mode = process.argv[2];
|
|
2162
2220
|
if (mode === "launch") {
|
|
2163
2221
|
await launch();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.7",
|
|
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",
|