gdharness 1.0.23 → 1.0.24

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
@@ -16369,7 +16369,7 @@ var init_tool_definitions = __esm(() => {
16369
16369
  },
16370
16370
  {
16371
16371
  name: "editor_output",
16372
- description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone. Answers with the counts and the verdict as well as the entries. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, which is where the output worth reading usually is, since the class cache, the plugins and the language server all say what they have to say before any harness has connected. The answer carries the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing.",
16372
+ description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone. Answers with the counts and the verdict as well as the entries. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts; a run ended by a signal has no exit code, and exitSignal names the signal instead. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, which is where the output worth reading usually is, since the class cache, the plugins and the language server all say what they have to say before any harness has connected. The answer carries the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing.",
16373
16373
  parameters: {
16374
16374
  severity: {
16375
16375
  type: "string",
@@ -16489,7 +16489,7 @@ var init_tool_definitions = __esm(() => {
16489
16489
  property: {
16490
16490
  type: "string",
16491
16491
  ops: ["property", "find"],
16492
- description: 'property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A negative number counts from the end. A step written as a call, "get_viewport():gui_get_focus_owner()", calls a method that takes no arguments and walks into what it returned, which is how a question only a method answers is read in one call: which control holds the focus in the viewport this one is in. A step that is not there is named, and says what was there instead.'
16492
+ description: 'property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A negative number counts from the end. A step written as a call, "get_viewport():gui_get_focus_owner()", calls a method that takes no arguments and walks into what it returned, which is how a question only a method answers is read in one call: which control holds the focus in the viewport this one is in. A list or a map answers the calls that read it and take no arguments, "_game:run:board:size()" or "_game:inbox:keys()", and not those that change it, such as clear() or sort(). A step that is not there is named, and says what was there instead.'
16493
16493
  },
16494
16494
  depth: { type: "number", ops: ["tree"], description: "tree: levels to descend. Default 3." },
16495
16495
  includeProperties: {
@@ -16535,6 +16535,10 @@ var init_tool_definitions = __esm(() => {
16535
16535
  items: { type: "string" },
16536
16536
  ops: ["metrics"],
16537
16537
  description: "metrics: which to read. Default all."
16538
+ },
16539
+ timeoutMs: {
16540
+ type: "number",
16541
+ description: "How long to wait for the answer before answering pending with a requestId, which runtime_invoke op result collects the reply by. Default 10000, or GDHARNESS_RUNTIME_TIMEOUT_MS."
16538
16542
  }
16539
16543
  },
16540
16544
  requires: [],
@@ -16562,11 +16566,15 @@ var init_tool_definitions = __esm(() => {
16562
16566
  },
16563
16567
  {
16564
16568
  name: "runtime_invoke",
16565
- description: "Sets a property or calls a method on a node in the running game. Needs the game running with the runtime addon.",
16569
+ description: "Sets a property or calls a method on a node in the running game. Needs the game running with the runtime addon. A call that takes longer than timeoutMs is not cancelled: the answer is pending: true with a requestId, the call goes on and does everything it was asked, and op result with that requestId collects its reply once it comes.",
16566
16570
  parameters: {
16567
16571
  projectPath: RUNNING_PROJECT_PATH,
16568
16572
  pid: RUNNING_PID,
16569
- nodePath: { type: "string", description: 'Absolute node path, such as "/root/Main/Player".' },
16573
+ nodePath: {
16574
+ type: "string",
16575
+ ops: ["set", "call"],
16576
+ description: 'Absolute node path, such as "/root/Main/Player".'
16577
+ },
16570
16578
  property: {
16571
16579
  type: "string",
16572
16580
  ops: ["set"],
@@ -16586,17 +16594,31 @@ var init_tool_definitions = __esm(() => {
16586
16594
  type: "array",
16587
16595
  ops: ["call"],
16588
16596
  description: `call: the arguments, fitted to the method's parameter types. A parameter typed as an object takes a path naming one the game holds and is handed that instance: a colon path read from nodePath, "_game:run:wares:3", or one starting at a node, "/root/Main/Hud" or "/root/Main:_game:run". A path that reaches no object, or an object of another class than the parameter declares, is refused. A parameter typed as a list or map, such as Array[int] or Array[Gear], is built from a JSON list or object element by element, objects named by their paths.`
16597
+ },
16598
+ timeoutMs: {
16599
+ type: "number",
16600
+ ops: ["set", "call"],
16601
+ description: "set, call: how long to wait for the answer before answering pending with a requestId, which does not stop the call. Default 10000, or GDHARNESS_RUNTIME_TIMEOUT_MS. Give a longer one for a call known to take a while, such as one that plays many turns in one go."
16602
+ },
16603
+ requestId: {
16604
+ type: "number",
16605
+ ops: ["result"],
16606
+ description: "result: the requestId a pending answer gave."
16589
16607
  }
16590
16608
  },
16591
- requires: ["nodePath"],
16609
+ requires: [],
16592
16610
  operations: {
16593
16611
  set: {
16594
16612
  summary: "set a property, on a node or on an object it holds",
16595
- requires: ["property", "value"]
16613
+ requires: ["nodePath", "property", "value"]
16596
16614
  },
16597
16615
  call: {
16598
16616
  summary: "call a method, on a node or on an object it holds, and return its result",
16599
- requires: ["method"]
16617
+ requires: ["nodePath", "method"]
16618
+ },
16619
+ result: {
16620
+ summary: "collect the reply to a set or call whose wait ran out, by the requestId it answered with",
16621
+ requires: ["requestId"]
16600
16622
  }
16601
16623
  }
16602
16624
  },
@@ -31916,14 +31938,40 @@ function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
31916
31938
  problem: `Several games are running: ${endpoints.map(describe2).join("; ")}. Pass pid to choose one, or projectPath when the project you mean is running only one.`
31917
31939
  };
31918
31940
  }
31919
- function runtimeRequest(endpoint, command, params, timeoutMs) {
31941
+ function keepLate(id, answer) {
31942
+ lateAnswers.delete(id);
31943
+ lateAnswers.set(id, answer);
31944
+ while (lateAnswers.size > LATE_KEPT) {
31945
+ const oldest = lateAnswers.keys().next().value;
31946
+ if (oldest === undefined) {
31947
+ break;
31948
+ }
31949
+ lateAnswers.delete(oldest);
31950
+ }
31951
+ }
31952
+ function lateAnswer(id) {
31953
+ return lateAnswers.get(id);
31954
+ }
31955
+ function runtimeRequest(endpoint, command, params, timeoutMs, keepListening = false) {
31920
31956
  const id = nextRequestId++;
31957
+ const sentAt = Date.now();
31921
31958
  return new Promise((settle) => {
31922
31959
  let done = false;
31960
+ let late = false;
31923
31961
  let buffered = "";
31924
31962
  let welcomed = false;
31925
31963
  const socket = createConnection({ port: endpoint.port, host: endpoint.address });
31964
+ let lateTimer = null;
31926
31965
  const finish = (reply) => {
31966
+ if (late) {
31967
+ if (lateTimer !== null) {
31968
+ clearTimeout(lateTimer);
31969
+ }
31970
+ socket.destroy();
31971
+ keepLate(id, reply.ok || reply.reason === "error" ? { state: "answered", command, sentAt, answeredAt: Date.now(), reply } : { state: "never", command, sentAt, why: reply.message });
31972
+ late = false;
31973
+ return;
31974
+ }
31927
31975
  if (done) {
31928
31976
  return;
31929
31977
  }
@@ -31933,10 +31981,32 @@ function runtimeRequest(endpoint, command, params, timeoutMs) {
31933
31981
  settle(reply);
31934
31982
  };
31935
31983
  const timer = setTimeout(() => {
31936
- finish({
31984
+ const still = welcomed ? `The game (${describe2(endpoint)}) has the request and did not answer '${command}' within ${timeoutMs}ms.` : `The game (${describe2(endpoint)}) did not answer '${command}' within ${timeoutMs}ms and had not taken the request: its frame has not ended since it was sent, which a long frame or a breakpoint does, and the request runs when it does.`;
31985
+ if (!keepListening) {
31986
+ finish({
31987
+ ok: false,
31988
+ reason: "busy",
31989
+ message: `${still} It is either working through something long or held at a breakpoint.`
31990
+ });
31991
+ return;
31992
+ }
31993
+ done = true;
31994
+ late = true;
31995
+ keepLate(id, { state: "waiting", command, sentAt });
31996
+ lateTimer = setTimeout(() => {
31997
+ finish({
31998
+ ok: false,
31999
+ reason: "busy",
32000
+ message: `No answer came within ${LATE_ANSWER_MS / 60000} minutes.`
32001
+ });
32002
+ }, LATE_ANSWER_MS);
32003
+ lateTimer.unref();
32004
+ socket.unref();
32005
+ settle({
31937
32006
  ok: false,
31938
32007
  reason: "busy",
31939
- message: `The game (${describe2(endpoint)}) accepted the connection but did not answer '${command}' within ${timeoutMs}ms. It may be paused at a breakpoint or stuck in a long frame.`
32008
+ requestId: id,
32009
+ message: `${still} Nothing was cancelled: a call that takes longer than the wait goes on and does everything it was asked, so read the state back rather than repeating it. runtime_invoke op result with requestId ${id} answers with its reply once it comes, and timeoutMs waits longer next time. A game held at a breakpoint instead is one debug_state stack answers about.`
31940
32010
  });
31941
32011
  }, timeoutMs);
31942
32012
  socket.setEncoding("utf8");
@@ -31948,7 +32018,7 @@ function runtimeRequest(endpoint, command, params, timeoutMs) {
31948
32018
  buffered += chunk;
31949
32019
  let newline = buffered.indexOf(`
31950
32020
  `);
31951
- while (newline !== -1 && !done) {
32021
+ while (newline !== -1 && (!done || late)) {
31952
32022
  const line = buffered.slice(0, newline).trim();
31953
32023
  buffered = buffered.slice(newline + 1);
31954
32024
  newline = buffered.indexOf(`
@@ -32003,7 +32073,7 @@ function runtimeRequest(endpoint, command, params, timeoutMs) {
32003
32073
  });
32004
32074
  });
32005
32075
  }
32006
- var RUNTIME_PROTOCOL = 2, ANNOUNCEMENT_PATTERN, ERROR_REPORT_PATTERN, ERROR_REPORT_KEEP_MS, GONE_WINDOW_MS = 120000, GONE_KEPT = 32, gone, STARTED_AFTER_ANNOUNCING_MS = 2000, CONFIRMED_FOR_MS = 60000, JUDGED_AFTER_MS = 60000, verdicts, ENDED_WITHIN_MS = 3000, LOOK_EVERY_MS = 50, ANNOUNCE_BUDGET_MS = 5000, nextRequestId = 1;
32076
+ var RUNTIME_PROTOCOL = 2, ANNOUNCEMENT_PATTERN, ERROR_REPORT_PATTERN, ERROR_REPORT_KEEP_MS, GONE_WINDOW_MS = 120000, GONE_KEPT = 32, gone, STARTED_AFTER_ANNOUNCING_MS = 2000, CONFIRMED_FOR_MS = 60000, JUDGED_AFTER_MS = 60000, verdicts, ENDED_WITHIN_MS = 3000, LOOK_EVERY_MS = 50, ANNOUNCE_BUDGET_MS = 5000, nextRequestId = 1, LATE_ANSWER_MS, LATE_KEPT = 50, lateAnswers;
32007
32077
  var init_runtime_client = __esm(() => {
32008
32078
  init_paths();
32009
32079
  init_process_children();
@@ -32012,6 +32082,8 @@ var init_runtime_client = __esm(() => {
32012
32082
  ERROR_REPORT_KEEP_MS = 60 * 60 * 1000;
32013
32083
  gone = [];
32014
32084
  verdicts = new Map;
32085
+ LATE_ANSWER_MS = 30 * 60000;
32086
+ lateAnswers = new Map;
32015
32087
  });
32016
32088
 
32017
32089
  // src/boot-note.ts
@@ -36959,6 +37031,65 @@ function whyNoReport(printed, asked) {
36959
37031
  }
36960
37032
  return printed.some((line) => NOTHING_RAN.test(line)) ? `no test cases found at ${asked}` : null;
36961
37033
  }
37034
+ function unspelled(text) {
37035
+ return text.replace(/<(?:LF|CR|TAB|BS|FF|VT|BEL|ESC|DEL|0x[0-9A-F]{2})>/g, (spelling) => spelling.startsWith("<0x") ? String.fromCharCode(Number.parseInt(spelling.slice(3, 5), 16)) : SPELLED[spelling] ?? spelling);
37036
+ }
37037
+ function readDiff(printed) {
37038
+ let merged = "";
37039
+ let actual = "";
37040
+ let background = null;
37041
+ let last = 0;
37042
+ const take = (text) => {
37043
+ merged += text;
37044
+ if (background === null) {
37045
+ actual += text;
37046
+ } else if (background === "added") {
37047
+ actual += unspelled(text);
37048
+ }
37049
+ };
37050
+ for (const match of printed.matchAll(COLOUR2)) {
37051
+ take(printed.slice(last, match.index));
37052
+ last = match.index + match[0].length;
37053
+ const codes = (match[1] ?? "").split(";");
37054
+ if (codes[0] === "48" && codes[1] === "2") {
37055
+ background = Number(codes[2]) > Number(codes[3]) ? "missing" : "added";
37056
+ } else if (codes[0] === "0" || codes[0] === "") {
37057
+ background = null;
37058
+ }
37059
+ }
37060
+ take(printed.slice(last));
37061
+ return { merged, actual };
37062
+ }
37063
+ function withActualsPrinted(failed, printed) {
37064
+ const diffs = [...printed.replaceAll(`\r
37065
+ `, `
37066
+ `).matchAll(PRINTED_ACTUAL)].map((match) => ({
37067
+ at: match[3] ?? "",
37068
+ ...readDiff(match[1] ?? "")
37069
+ }));
37070
+ return failed.map((entry) => {
37071
+ if (entry.detail === null) {
37072
+ return entry;
37073
+ }
37074
+ let detail = entry.detail;
37075
+ for (const diff of diffs) {
37076
+ const wrong = ` but was
37077
+ '${diff.merged.replaceAll("\r", "")}'
37078
+ at ${diff.at}`;
37079
+ if (detail.includes(wrong)) {
37080
+ detail = detail.replace(wrong, () => ` but was
37081
+ '${diff.actual}'
37082
+ at ${diff.at}`);
37083
+ }
37084
+ }
37085
+ const alike = /Expecting:\n '([\s\S]*)'\n but was\n '\1'\n\tat /.test(detail);
37086
+ if (alike) {
37087
+ detail = `${detail}
37088
+ ${BOTH_SIDES_ALIKE_NOTE}`;
37089
+ }
37090
+ return detail === entry.detail ? entry : { ...entry, detail };
37091
+ });
37092
+ }
36962
37093
  function orphansPrinted(printed) {
36963
37094
  const suites = [];
36964
37095
  let total = null;
@@ -36989,7 +37120,7 @@ function orphansPrinted(printed) {
36989
37120
  suites
36990
37121
  };
36991
37122
  }
36992
- var MalformedReportError, ENTITIES, ATTRIBUTE, NOTHING_RAN, NO_SUCH_PATH, RUNNING_SUITE, ORPHANS, OVERALL = "Overall Summary";
37123
+ var MalformedReportError, ENTITIES, ATTRIBUTE, NOTHING_RAN, NO_SUCH_PATH, ESCAPE, PRINTED_ACTUAL, COLOUR2, SPELLED, BOTH_SIDES_ALIKE_NOTE = "[gdharness: the expected and the actual printed alike because gdUnit4 drops the marks that tell them apart when it writes its report, and the runner printed no copy of this one, so neither side above is the value the assertion found.]", RUNNING_SUITE, ORPHANS, OVERALL = "Overall Summary";
36993
37124
  var init_junit = __esm(() => {
36994
37125
  MalformedReportError = class MalformedReportError extends Error {
36995
37126
  constructor(message) {
@@ -37001,6 +37132,21 @@ var init_junit = __esm(() => {
37001
37132
  ATTRIBUTE = /\s*([^\s=/>]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/y;
37002
37133
  NOTHING_RAN = /no test cases found/i;
37003
37134
  NO_SUCH_PATH = /given directory or file does not exists:\s*(\S+)/i;
37135
+ ESCAPE = String.fromCharCode(27);
37136
+ PRINTED_ACTUAL = new RegExp(` but was\\n '([\\s\\S]*?)'((?:${ESCAPE}\\[[0-9;]*m)*)\\tat ('[^'\\n]*' in \\S+:\\d+)`, "g");
37137
+ COLOUR2 = new RegExp(`${ESCAPE}\\[([0-9;]*)m`, "g");
37138
+ SPELLED = {
37139
+ "<LF>": `
37140
+ `,
37141
+ "<CR>": "\r",
37142
+ "<TAB>": "\t",
37143
+ "<BS>": "\b",
37144
+ "<FF>": "\f",
37145
+ "<VT>": "\v",
37146
+ "<BEL>": "\x07",
37147
+ "<ESC>": ESCAPE,
37148
+ "<DEL>": ""
37149
+ };
37004
37150
  RUNNING_SUITE = /Run Test Suite:\s*(\S+)/;
37005
37151
  ORPHANS = /(\d+)\s+orphans/;
37006
37152
  });
@@ -37811,14 +37957,19 @@ function editorNoteAt(path) {
37811
37957
  }
37812
37958
  return { projectPath, transcript, startedAt };
37813
37959
  }
37814
- function recordRunEnded(pid, exitCode) {
37960
+ function recordRunEnded(pid, ending) {
37815
37961
  const path = recordPath();
37816
37962
  const record = recordAt(path);
37817
37963
  if (record === null || record.pid !== pid) {
37818
37964
  return;
37819
37965
  }
37966
+ const written = {
37967
+ ...record,
37968
+ ...ending.exitCode === null ? {} : { exitCode: ending.exitCode },
37969
+ ...ending.exitSignal === null ? {} : { exitSignal: ending.exitSignal }
37970
+ };
37820
37971
  try {
37821
- writeFileSync15(path, JSON.stringify({ ...record, exitCode }, null, 2), "utf8");
37972
+ writeFileSync15(path, JSON.stringify(written, null, 2), "utf8");
37822
37973
  } catch {}
37823
37974
  }
37824
37975
  function readRunRecord() {
@@ -37855,7 +38006,8 @@ function recordAt(path) {
37855
38006
  projectPath: typeof fields["projectPath"] === "string" ? fields["projectPath"] : "",
37856
38007
  arguments: Array.isArray(args) ? args.filter((value) => typeof value === "string") : [],
37857
38008
  ...typeof fields["command"] === "string" ? { command: fields["command"] } : {},
37858
- ...typeof fields["exitCode"] === "number" ? { exitCode: fields["exitCode"] } : {}
38009
+ ...typeof fields["exitCode"] === "number" ? { exitCode: fields["exitCode"] } : {},
38010
+ ...typeof fields["exitSignal"] === "string" ? { exitSignal: fields["exitSignal"] } : {}
37859
38011
  };
37860
38012
  }
37861
38013
  function runningAs(pid) {
@@ -38116,8 +38268,21 @@ function describeHalt(halt) {
38116
38268
  return `stopped with reason "${halt.reason}"`;
38117
38269
  }
38118
38270
  }
38271
+ function exited(run) {
38272
+ return run.exitCode !== null || run.exitSignal !== null;
38273
+ }
38274
+ function endingOf(failed) {
38275
+ return {
38276
+ exitCode: typeof failed.code === "number" ? failed.code : null,
38277
+ exitSignal: typeof failed.signal === "string" ? failed.signal : null,
38278
+ failure: typeof failed.code === "string" ? failed.code : null
38279
+ };
38280
+ }
38281
+ function howItExited(run) {
38282
+ return run.exitSignal === null ? `exit code ${run.exitCode ?? "unknown"}` : `ended by ${run.exitSignal}, with no exit code`;
38283
+ }
38119
38284
  function stillRunning(run) {
38120
- if (run?.exitCode !== null || run.endedUnwatched === true) {
38285
+ if (run === null || exited(run) || run.endedUnwatched === true) {
38121
38286
  return false;
38122
38287
  }
38123
38288
  if (typeof run.endedHere === "string") {
@@ -38147,7 +38312,7 @@ function runIsUp(run, editorSays, now = Date.now()) {
38147
38312
  return editorSays ?? stillRunning(run);
38148
38313
  }
38149
38314
  function noCodeWillCome(run) {
38150
- return run.exitCode === null && run.process === null;
38315
+ return !exited(run) && run.process === null;
38151
38316
  }
38152
38317
  async function untilGone(handle, pids, withinMs = STOP_WAIT_MS) {
38153
38318
  const gone = () => (handle?.exitCode !== null || handle.signalCode !== null) && pids.every((pid) => !alive(pid));
@@ -38980,7 +39145,8 @@ class GodotServer {
38980
39145
  return await this.handleEditorStatus();
38981
39146
  case "editor_rescan":
38982
39147
  return await this.handleRescanFilesystem(args);
38983
- case "runtime_inspect":
39148
+ case "runtime_inspect": {
39149
+ const patience = readPositiveNumber(args, "timeoutMs") ?? this.runtimeTimeoutMs();
38984
39150
  switch (op) {
38985
39151
  case "tree":
38986
39152
  return await this.handleRuntimeCommand("get_tree", {
@@ -38989,45 +39155,45 @@ class GodotServer {
38989
39155
  depth: readPositiveNumber(args, "depth") ?? 3,
38990
39156
  include_properties: readBoolean(args, "includeProperties") ?? false,
38991
39157
  properties: readArray(args, "properties") ?? []
38992
- });
39158
+ }, patience);
38993
39159
  case "find":
38994
- return await this.handleFindRuntimeNodes(args);
39160
+ return await this.handleFindRuntimeNodes(args, patience);
38995
39161
  case "text":
38996
39162
  return await this.handleRuntimeCommand("read_text", {
38997
39163
  ...whichGame(args),
38998
39164
  root: readNonEmptyString(args, "nodePath") ?? "/root",
38999
39165
  include_hidden: readBoolean(args, "includeHidden") ?? false,
39000
39166
  limit: readPositiveNumber(args, "limit") ?? 500
39001
- });
39167
+ }, patience);
39002
39168
  case "rect":
39003
- return await this.handleRuntimeCommand("get_rect", {
39004
- ...whichGame(args),
39005
- path: readNonEmptyString(args, "nodePath") ?? ""
39006
- });
39169
+ return await this.handleRuntimeCommand("get_rect", { ...whichGame(args), path: readNonEmptyString(args, "nodePath") ?? "" }, patience);
39007
39170
  case "property":
39008
39171
  return await this.handleRuntimeCommand("get_property", {
39009
39172
  ...whichGame(args),
39010
39173
  path: readNonEmptyString(args, "nodePath") ?? "",
39011
39174
  property: readNonEmptyString(args, "property") ?? ""
39012
- });
39175
+ }, patience);
39013
39176
  default:
39014
- return await this.handleRuntimeCommand("get_metrics", {
39015
- ...whichGame(args),
39016
- metrics: readArray(args, "metrics") ?? []
39017
- });
39177
+ return await this.handleRuntimeCommand("get_metrics", { ...whichGame(args), metrics: readArray(args, "metrics") ?? [] }, patience);
39178
+ }
39179
+ }
39180
+ case "runtime_invoke": {
39181
+ if (op === "result") {
39182
+ return this.handleLateAnswer(args);
39018
39183
  }
39019
- case "runtime_invoke":
39184
+ const patience = readPositiveNumber(args, "timeoutMs") ?? this.runtimeTimeoutMs();
39020
39185
  return op === "set" ? await this.handleRuntimeCommand("set_property", {
39021
39186
  ...whichGame(args),
39022
39187
  path: readNonEmptyString(args, "nodePath") ?? "",
39023
39188
  property: readString(args, "property") ?? "",
39024
39189
  value: args["value"]
39025
- }) : await this.handleRuntimeCommand("call_method", {
39190
+ }, patience) : await this.handleRuntimeCommand("call_method", {
39026
39191
  ...whichGame(args),
39027
39192
  path: readNonEmptyString(args, "nodePath") ?? "",
39028
39193
  method: readString(args, "method") ?? "",
39029
39194
  args: readArray(args, "args") ?? []
39030
- });
39195
+ }, patience);
39196
+ }
39031
39197
  case "runtime_capture":
39032
39198
  return await this.handleRuntimeCommand(op === "screenshot" ? "capture_screenshot" : "capture_viewport", args);
39033
39199
  case "runtime_input":
@@ -39178,10 +39344,10 @@ class GodotServer {
39178
39344
  }
39179
39345
  async debuggedGame(intent) {
39180
39346
  const game = this.activeProcess;
39181
- if (game !== null && game.exitCode !== null) {
39347
+ if (game !== null && exited(game)) {
39182
39348
  return {
39183
39349
  ok: false,
39184
- response: this.createErrorResponse(`The last run has already finished (exit code ${game.exitCode}), so there is no debug session to answer for.`, [
39350
+ response: this.createErrorResponse(`The last run has already finished (${howItExited(game)}), so there is no debug session to answer for.`, [
39185
39351
  "editor_output still reads what it printed, until the next run starts",
39186
39352
  "editor_run start plays another, which the debugger can hold"
39187
39353
  ])
@@ -39455,10 +39621,11 @@ class GodotServer {
39455
39621
  ];
39456
39622
  this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
39457
39623
  const log = new GameLog;
39458
- let exitCode = 0;
39624
+ let ending = { exitCode: 0, exitSignal: null, failure: null };
39459
39625
  try {
39460
39626
  const { stdout, stderr } = await run7(engine.value, exportArgs, {
39461
39627
  timeout: 300000,
39628
+ maxBuffer: 64 * 1024 * 1024,
39462
39629
  signal: callSignal()
39463
39630
  });
39464
39631
  log.append("stdout", stdout);
@@ -39470,26 +39637,32 @@ class GodotServer {
39470
39637
  const failed = error;
39471
39638
  log.append("stdout", failed.stdout);
39472
39639
  log.append("stderr", failed.stderr);
39473
- exitCode = typeof failed.code === "number" ? failed.code : -1;
39640
+ ending = endingOf(failed);
39474
39641
  } finally {
39475
39642
  discard(exportLogs);
39476
39643
  }
39477
39644
  log.finish();
39478
39645
  const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
39479
39646
  const verdict = {
39480
- exported: exitCode === 0 && log.count("error") === 0 && existsSync15(output.absolutePath),
39647
+ exported: ending.exitCode === 0 && log.count("error") === 0 && existsSync15(output.absolutePath),
39481
39648
  preset,
39482
39649
  outputPath: output.relativePath,
39483
39650
  debug,
39484
- exitCode,
39651
+ exitCode: ending.exitCode,
39652
+ exitSignal: ending.exitSignal ?? undefined,
39653
+ failure: ending.failure ?? undefined,
39485
39654
  errors: log.count("error"),
39486
39655
  warnings: log.count("warning"),
39487
39656
  entries: forAnswer(problems.entries)
39488
39657
  };
39489
39658
  if (!verdict.exported) {
39659
+ const why = ending.exitSignal === null ? ending.failure === null ? "" : ` The engine was ended by Node (${ending.failure}), so it has no exit code.` : ` The engine was ended by ${ending.exitSignal}, so it has no exit code: the export ran past its five minutes or the call was cancelled.`;
39490
39660
  return {
39491
39661
  content: [
39492
- { type: "text", text: `Export with preset '${preset}' did not produce ${output.relativePath}.` },
39662
+ {
39663
+ type: "text",
39664
+ text: `Export with preset '${preset}' did not produce ${output.relativePath}.${why}`
39665
+ },
39493
39666
  { type: "text", text: JSON.stringify(verdict, null, 2) }
39494
39667
  ],
39495
39668
  isError: true
@@ -39544,6 +39717,10 @@ class GodotServer {
39544
39717
  this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
39545
39718
  const userData = mkdtempSync3(join23(tmpdir5(), "gdharness-tests-"));
39546
39719
  const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
39720
+ const console2 = [];
39721
+ run.process.stdout?.on("data", (data) => {
39722
+ console2.push(data);
39723
+ });
39547
39724
  const hung = await new Promise((resolve) => {
39548
39725
  const timer = setTimeout(() => {
39549
39726
  run.process.kill();
@@ -39588,7 +39765,7 @@ class GodotServer {
39588
39765
  const printed = run.log.select({ severity: "info", sinceLastCall: false, limit: 200 }).entries;
39589
39766
  const said = printed.map((entry) => entry.text);
39590
39767
  const nothingRan = hung ? null : whyNoReport(said, asked);
39591
- const verdict = hung ? `hung: killed after ${timeoutMs} ms` : nothingRan ?? verdicts[exitCode ?? -1] ?? `exit ${exitCode ?? "unknown"}`;
39768
+ const verdict = hung ? `hung: killed after ${timeoutMs} ms` : nothingRan ?? (exitCode === null ? undefined : verdicts[exitCode]) ?? (run.exitSignal === null ? `exit ${exitCode ?? "unknown"}` : howItExited(run));
39592
39769
  const savesNote = savesStayPut() && nothingRan === null ? SAVES_NOT_MOVED_NOTE : undefined;
39593
39770
  if (report === null) {
39594
39771
  const note = nothingRan === null ? `The test run wrote no report (${verdict}${reportProblem ? `; ${reportProblem}` : ""}).` : `No tests ran: ${verdict}.${elsewhereIn(project.value.path, asked)}`;
@@ -39602,6 +39779,7 @@ class GodotServer {
39602
39779
  verdict,
39603
39780
  tests: 0,
39604
39781
  exitCode,
39782
+ exitSignal: run.exitSignal ?? undefined,
39605
39783
  hung,
39606
39784
  arguments: cmdArgs,
39607
39785
  entries: forAnswer(printed.slice(0, 60)),
@@ -39612,7 +39790,7 @@ class GodotServer {
39612
39790
  isError: true
39613
39791
  };
39614
39792
  }
39615
- const failed = report.suites.flatMap((suite) => suite.cases.filter((entry) => entry.status === "failed" || entry.status === "error").map((entry) => ({ ...entry, path: suite.path })));
39793
+ const failed = withActualsPrinted(report.suites.flatMap((suite) => suite.cases.filter((entry) => entry.status === "failed" || entry.status === "error").map((entry) => ({ ...entry, path: suite.path }))), Buffer.concat(console2).toString("utf8"));
39616
39794
  const orphans = orphansPrinted(said);
39617
39795
  const leftBehind = new Map(orphans.suites.map((suite) => [suite.path, suite.orphans]));
39618
39796
  const orphansIn = (path) => path === null ? 0 : leftBehind.get(path) ?? 0;
@@ -39629,6 +39807,7 @@ class GodotServer {
39629
39807
  passed: !hung && exitCode === 0 && report.failures === 0 && report.errors === 0,
39630
39808
  verdict,
39631
39809
  exitCode,
39810
+ exitSignal: run.exitSignal ?? undefined,
39632
39811
  tests: report.tests,
39633
39812
  failures: report.failures,
39634
39813
  errors: report.errors,
@@ -40360,6 +40539,7 @@ class GodotServer {
40360
40539
  projectPath,
40361
40540
  startedAt,
40362
40541
  exitCode: null,
40542
+ exitSignal: null,
40363
40543
  throughEditor: true,
40364
40544
  brokeOn: null,
40365
40545
  announcedBefore: alreadyPlaying,
@@ -40513,6 +40693,7 @@ class GodotServer {
40513
40693
  projectPath: null,
40514
40694
  startedAt: Date.now(),
40515
40695
  exitCode: null,
40696
+ exitSignal: null,
40516
40697
  throughEditor: false,
40517
40698
  brokeOn: null
40518
40699
  };
@@ -40522,13 +40703,11 @@ class GodotServer {
40522
40703
  child.stderr.on("data", (data) => {
40523
40704
  log.append("stderr", data);
40524
40705
  });
40525
- child.on("exit", (code) => {
40526
- this.logDebug(`Godot process exited with code ${code ?? "none"}`);
40706
+ child.on("exit", (code, signal) => {
40707
+ this.logDebug(`Godot process exited with code ${code ?? "none"}, signal ${signal ?? "none"}`);
40527
40708
  log.finish();
40528
- started.exitCode = code ?? -1;
40529
- if (started.pid !== null) {
40530
- recordRunEnded(started.pid, started.exitCode);
40531
- }
40709
+ started.exitCode = code;
40710
+ started.exitSignal = code === null ? signal : null;
40532
40711
  });
40533
40712
  child.on("error", (err) => {
40534
40713
  console.error("Failed to start Godot process:", err);
@@ -40562,14 +40741,19 @@ class GodotServer {
40562
40741
  projectPath,
40563
40742
  startedAt,
40564
40743
  exitCode: null,
40744
+ exitSignal: null,
40565
40745
  throughEditor: false,
40566
40746
  brokeOn: null
40567
40747
  };
40568
- child.on("exit", (code) => {
40569
- this.logDebug(`Godot process exited with code ${code ?? "none"}`);
40748
+ child.on("exit", (code, signal) => {
40749
+ this.logDebug(`Godot process exited with code ${code ?? "none"}, signal ${signal ?? "none"}`);
40570
40750
  this.drainTranscript(started);
40571
40751
  log.finish();
40572
- started.exitCode = code ?? -1;
40752
+ started.exitCode = code;
40753
+ started.exitSignal = code === null ? signal : null;
40754
+ if (started.pid !== null) {
40755
+ recordRunEnded(started.pid, { exitCode: started.exitCode, exitSignal: started.exitSignal });
40756
+ }
40573
40757
  });
40574
40758
  child.on("error", (err) => {
40575
40759
  console.error("Failed to start Godot process:", err);
@@ -40652,6 +40836,7 @@ class GodotServer {
40652
40836
  projectPath: project,
40653
40837
  startedAt: note?.startedAt ?? Date.now(),
40654
40838
  exitCode: null,
40839
+ exitSignal: null,
40655
40840
  throughEditor: true,
40656
40841
  brokeOn: null,
40657
40842
  pickedUpPlaying: true
@@ -40788,6 +40973,7 @@ class GodotServer {
40788
40973
  projectPath: record.projectPath === "" ? null : record.projectPath,
40789
40974
  startedAt: record.startedAt,
40790
40975
  exitCode: null,
40976
+ exitSignal: null,
40791
40977
  throughEditor: false,
40792
40978
  brokeOn: null
40793
40979
  };
@@ -40795,7 +40981,8 @@ class GodotServer {
40795
40981
  if (!alive(record.pid) || !couldStillBeTheRecordedRun(record)) {
40796
40982
  adopted.log.finish();
40797
40983
  adopted.exitCode = record.exitCode ?? null;
40798
- adopted.endedUnwatched = record.exitCode === undefined;
40984
+ adopted.exitSignal = record.exitSignal ?? null;
40985
+ adopted.endedUnwatched = !exited(adopted);
40799
40986
  }
40800
40987
  this.activeProcess = adopted;
40801
40988
  return adopted;
@@ -40894,6 +41081,7 @@ class GodotServer {
40894
41081
  booted: !hung && boot.exitCode === 0 && errors === 0,
40895
41082
  hung,
40896
41083
  exitCode: boot.exitCode,
41084
+ exitSignal: boot.exitSignal ?? undefined,
40897
41085
  durationMs: Date.now() - boot.startedAt,
40898
41086
  frames,
40899
41087
  errors,
@@ -40920,7 +41108,11 @@ class GodotServer {
40920
41108
  }
40921
41109
  async runStillGoing(run, editorSays) {
40922
41110
  if (!run.throughEditor) {
40923
- return stillRunning(run);
41111
+ const going = stillRunning(run);
41112
+ if (!going && run.process !== null && !exited(run)) {
41113
+ await untilGone(run.process, [], EXIT_REPORTED_WITHIN_MS);
41114
+ }
41115
+ return going;
40924
41116
  }
40925
41117
  const announced = this.announcedPidOf(run);
40926
41118
  if (announced !== undefined && !alive(announced)) {
@@ -41028,8 +41220,8 @@ class GodotServer {
41028
41220
  }
41029
41221
  if (typeof run.endedHere === "string") {
41030
41222
  notes.push(`This run was ended here, by ${run.endedHere}.`);
41031
- } else if (!stillRunning(run) && !run.throughEditor && run.exitCode !== null) {
41032
- notes.push(run.exitCode === 0 ? "This run quit on its own, cleanly: exit code 0." : "Nothing here ended this run: it stopped on its own or something outside this server stopped it.");
41223
+ } else if (!stillRunning(run) && !run.throughEditor && exited(run)) {
41224
+ notes.push(run.exitCode === 0 ? "This run quit on its own, cleanly: exit code 0." : run.exitSignal === null ? "Nothing here ended this run: it stopped on its own or something outside this server stopped it." : `Nothing here ended this run: something outside this server sent it ${run.exitSignal}, so it has no exit code.`);
41033
41225
  }
41034
41226
  if (run.endedUnwatched === true) {
41035
41227
  notes.push(endedWithoutACode({
@@ -41052,6 +41244,7 @@ class GodotServer {
41052
41244
  return this.jsonTextResponse({
41053
41245
  running: going,
41054
41246
  exitCode: run.exitCode,
41247
+ exitSignal: run.exitSignal ?? undefined,
41055
41248
  through: run.throughEditor ? "editor" : "gdharness",
41056
41249
  pid: run.pid ?? announced ?? run.announcedPid ?? null,
41057
41250
  errors: run.log.count("error"),
@@ -41105,10 +41298,11 @@ class GodotServer {
41105
41298
  },
41106
41299
  exitedBeforeStop: !wasRunning,
41107
41300
  exitCode: stopped.exitCode,
41301
+ exitSignal: stopped.exitSignal ?? undefined,
41108
41302
  errors: stopped.log.count("error"),
41109
41303
  warnings: stopped.log.count("warning"),
41110
41304
  clean: stopped.log.count("error") === 0,
41111
- note: !wasRunning ? "This run was over before the stop, so nothing was ended here: editor_output has what it printed and how it ended. Anything that game started for itself, with OS.create_process or otherwise, is a separate process and may still be running." : `${stopped.throughEditor ? endedPid === null ? "The editor was asked to stop the scene it is playing. Its game had not announced a runtime, so no process was named under endedPid, and its exit code stays with the editor." : "The editor was asked to stop the scene it is playing." : "The process named under endedPid was ended."}${gone ? "" : ` It had not exited ${STOP_WAIT_MS / 1000} seconds after being told to, so it may still be going: editor_status says whether it is.`} ${aboutTheChildren(ended, stopped.throughEditor)}`,
41305
+ note: !wasRunning ? "This run was over before the stop, so nothing was ended here: editor_output has what it printed and how it ended. Anything that game started for itself, with OS.create_process or otherwise, is a separate process and may still be running." : `${stopped.throughEditor ? endedPid === null ? "The editor was asked to stop the scene it is playing. Its game had not announced a runtime, so no process was named under endedPid, and its exit code stays with the editor." : "The editor was asked to stop the scene it is playing." : "The process named under endedPid was ended."}${stopped.exitSignal === null ? "" : ` It was ended by ${stopped.exitSignal} and so has no exit code.`}${gone ? "" : ` It had not exited ${STOP_WAIT_MS / 1000} seconds after being told to, so it may still be going: editor_status says whether it is.`} ${aboutTheChildren(ended, stopped.throughEditor)}`,
41112
41306
  entries: forAnswer(stopped.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries)
41113
41307
  });
41114
41308
  }
@@ -41524,7 +41718,17 @@ class GodotServer {
41524
41718
  const screenshotDir = expectsScreenshot ? mkdtempSync3(join23(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
41525
41719
  const screenshotPath = screenshotDir ? join23(screenshotDir, "capture.png") : null;
41526
41720
  try {
41527
- const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
41721
+ const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, !expectsScreenshot);
41722
+ if (!reply.ok && reply.requestId !== undefined) {
41723
+ return this.jsonTextResponse({
41724
+ pending: true,
41725
+ requestId: reply.requestId,
41726
+ command,
41727
+ waitedMs: timeoutMs,
41728
+ note: reply.message,
41729
+ ...answeredBy
41730
+ });
41731
+ }
41528
41732
  if (!reply.ok) {
41529
41733
  return this.createErrorResponse(reply.message);
41530
41734
  }
@@ -41560,7 +41764,36 @@ class GodotServer {
41560
41764
  discard(screenshotDir);
41561
41765
  }
41562
41766
  }
41563
- async handleFindRuntimeNodes(args) {
41767
+ handleLateAnswer(args) {
41768
+ const requestId = readPositiveNumber(args, "requestId") ?? 0;
41769
+ const late = lateAnswer(requestId);
41770
+ if (late === undefined) {
41771
+ return this.createErrorResponse(`No request ${requestId} is waiting to be collected here: an id is kept only from a pending answer this server gave, and only for its fifty most recent.`);
41772
+ }
41773
+ if (late.state === "waiting") {
41774
+ return this.jsonTextResponse({
41775
+ pending: true,
41776
+ requestId,
41777
+ command: late.command,
41778
+ sentAgoMs: Date.now() - late.sentAt,
41779
+ note: "The game has not answered it yet. It is still being listened for, and this answers with the reply once it comes."
41780
+ });
41781
+ }
41782
+ if (late.state === "never") {
41783
+ return this.createErrorResponse(`Request ${requestId} ('${late.command}') got no answer: ${late.why} Read the state back to see whether it ran.`);
41784
+ }
41785
+ const reply = late.reply;
41786
+ if (!reply.ok) {
41787
+ return this.createErrorResponse(`Request ${requestId} ('${late.command}') was answered with a refusal: ${reply.message}`);
41788
+ }
41789
+ const { id: _id, ...payload } = reply.payload;
41790
+ return this.jsonTextResponse({
41791
+ ...payload,
41792
+ requestId,
41793
+ answeredAfterMs: late.answeredAt - late.sentAt
41794
+ });
41795
+ }
41796
+ async handleFindRuntimeNodes(args, patience) {
41564
41797
  const filters = {};
41565
41798
  const className = readNonEmptyString(args, "className");
41566
41799
  const script = readNonEmptyString(args, "script");
@@ -41589,7 +41822,7 @@ class GodotServer {
41589
41822
  limit: readPositiveNumber(args, "limit") ?? 100,
41590
41823
  ...property === undefined ? {} : { property },
41591
41824
  ...includeHidden === undefined ? {} : { include_hidden: includeHidden }
41592
- });
41825
+ }, patience);
41593
41826
  }
41594
41827
  async handleRuntimeWait(op, args) {
41595
41828
  if (op === "frames") {
@@ -41624,7 +41857,7 @@ async function runGodotServer() {
41624
41857
  const server = new GodotServer;
41625
41858
  await server.run();
41626
41859
  }
41627
- var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run7, __dirname2, EDITOR_RESTART_TIMEOUT_MS = 90000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, STOP_WAIT_MS = 1e4, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, MOST_GROUPS = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/";
41860
+ var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run7, __dirname2, EDITOR_RESTART_TIMEOUT_MS = 90000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, STOP_WAIT_MS = 1e4, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, MOST_GROUPS = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/";
41628
41861
  var init_server3 = __esm(() => {
41629
41862
  init_mcp();
41630
41863
  init_stdio2();