gdharness 1.3.47 → 1.3.49

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.
Files changed (32) hide show
  1. package/build/cli.js +589 -229
  2. package/build/godot/addons/gdharness_editor/animation_reads.gd +20 -0
  3. package/build/godot/addons/gdharness_editor/declared_values.gd +37 -0
  4. package/build/godot/addons/gdharness_editor/file_reads.gd +6 -0
  5. package/build/godot/addons/gdharness_editor/property_values.gd +8 -11
  6. package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +19 -0
  7. package/build/godot/addons/gdharness_editor/tools/play_tools.gd +42 -0
  8. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +1 -1
  9. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +8 -1
  10. package/build/godot/addons/gdharness_editor/value_shapes.gd +9 -0
  11. package/build/godot/addons/gdharness_runtime/declared_values.gd +37 -0
  12. package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +32 -9
  13. package/build/godot/addons/gdharness_runtime/runtime_changes.gd +3 -2
  14. package/build/godot/addons/gdharness_runtime/runtime_hover.gd +9 -7
  15. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +72 -11
  16. package/build/godot/addons/gdharness_runtime/runtime_queries.gd +21 -1
  17. package/build/godot/addons/gdharness_runtime/runtime_targets.gd +21 -8
  18. package/build/godot/addons/gdharness_runtime/runtime_waits.gd +3 -2
  19. package/build/godot/addons/gdharness_runtime/runtime_words.gd +1 -1
  20. package/build/godot/addons/gdharness_runtime/value_shapes.gd +9 -0
  21. package/build/godot/operations/animation_reads.gd +20 -0
  22. package/build/godot/operations/declared_values.gd +37 -0
  23. package/build/godot/operations/dependencies.gd +65 -9
  24. package/build/godot/operations/file_reads.gd +6 -0
  25. package/build/godot/operations/import_pipeline.gd +10 -2
  26. package/build/godot/operations/project_config.gd +35 -0
  27. package/build/godot/operations/property_values.gd +8 -11
  28. package/build/godot/operations/resource_files.gd +1 -2
  29. package/build/godot/operations/value_shapes.gd +9 -0
  30. package/build/godot/tests/listed.gd +25 -0
  31. package/build/keeper.js +1312 -1026
  32. package/package.json +1 -1
package/build/keeper.js CHANGED
@@ -6946,8 +6946,8 @@ var require_public_api = __commonJS(function(exports) {
6946
6946
  });
6947
6947
 
6948
6948
  // src/keeper.ts
6949
- import { closeSync as closeSync2, existsSync as existsSync3, mkdtempSync as mkdtempSync4, openSync as openSync3, readFileSync as readFileSync7, rmSync as rmSync6, writeFileSync as writeFileSync3 } from "node:fs";
6950
- import { tmpdir as tmpdir5 } from "node:os";
6949
+ import { closeSync as closeSync3, existsSync as existsSync3, mkdtempSync as mkdtempSync4, openSync as openSync4, readFileSync as readFileSync8, rmSync as rmSync7, writeFileSync as writeFileSync3 } from "node:fs";
6950
+ import { tmpdir as tmpdir6 } from "node:os";
6951
6951
  import { join as join10 } from "node:path";
6952
6952
  import { setTimeout as delay3 } from "node:timers/promises";
6953
6953
 
@@ -7407,344 +7407,542 @@ function throughHelper(how, command, args, files, assembly = helperAssembly()) {
7407
7407
  };
7408
7408
  }
7409
7409
 
7410
- // src/error-mode.ts
7411
- import process3 from "node:process";
7412
- var NO_FAULT_ERROR_BOX = 2;
7413
- var cleared = process3.platform !== "win32";
7414
- async function letWindowsReportCrashes() {
7415
- if (cleared || !("bun" in process3.versions)) {
7416
- return;
7417
- }
7418
- try {
7419
- const { dlopen, FFIType } = await import("bun:ffi");
7420
- const kernel32 = dlopen("kernel32.dll", {
7421
- GetErrorMode: { args: [], returns: FFIType.u32 },
7422
- SetErrorMode: { args: [FFIType.u32], returns: FFIType.u32 }
7423
- });
7424
- try {
7425
- const mode = kernel32.symbols.GetErrorMode();
7426
- kernel32.symbols.SetErrorMode(mode & ~NO_FAULT_ERROR_BOX);
7427
- cleared = true;
7428
- } finally {
7429
- kernel32.close();
7430
- }
7431
- } catch {}
7432
- }
7433
- function crashesReported() {
7434
- return cleared;
7435
- }
7410
+ // src/engine-slots.ts
7411
+ import {
7412
+ closeSync as closeSync2,
7413
+ mkdirSync,
7414
+ openSync as openSync2,
7415
+ readdirSync,
7416
+ readFileSync as readFileSync4,
7417
+ rmSync as rmSync3,
7418
+ statSync as statSync3,
7419
+ watch,
7420
+ writeSync as writeSync2
7421
+ } from "node:fs";
7422
+ import process4 from "node:process";
7436
7423
 
7437
- // src/game-debugger.ts
7438
- import { readFileSync as readFileSync5 } from "node:fs";
7439
- import { createServer } from "node:net";
7440
- import { join as join6 } from "node:path";
7424
+ // src/call-signal.ts
7425
+ import { AsyncLocalStorage } from "node:async_hooks";
7426
+ var current = new AsyncLocalStorage;
7441
7427
 
7442
- // src/godot-variant.ts
7443
- var NIL = 0;
7444
- var BOOL = 1;
7445
- var INT = 2;
7446
- var FLOAT = 3;
7447
- var STRING = 4;
7448
- var ARRAY = 28;
7449
- var FLAG_64 = 1 << 16;
7450
- var MATH = {
7451
- 5: { name: "Vector2", count: 2 },
7452
- 6: { name: "Vector2i", count: 2, ints: true },
7453
- 7: { name: "Rect2", count: 4 },
7454
- 8: { name: "Rect2i", count: 4, ints: true },
7455
- 9: { name: "Vector3", count: 3 },
7456
- 10: { name: "Vector3i", count: 3, ints: true },
7457
- 11: { name: "Transform2D", count: 6 },
7458
- 12: { name: "Vector4", count: 4 },
7459
- 13: { name: "Vector4i", count: 4, ints: true },
7460
- 14: { name: "Plane", count: 4 },
7461
- 15: { name: "Quaternion", count: 4 },
7462
- 16: { name: "AABB", count: 6 },
7463
- 17: { name: "Basis", count: 9 },
7464
- 18: { name: "Transform3D", count: 12 },
7465
- 19: { name: "Projection", count: 16 }
7466
- };
7467
- var PACKED_VECTORS = { 35: 2, 36: 3, 38: 4 };
7428
+ // src/harnesses.ts
7429
+ import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute2, join as join3, relative as relative2, resolve as resolve2 } from "node:path";
7468
7430
 
7469
- class Reader {
7470
- offset = 0;
7471
- view;
7472
- constructor(view) {
7473
- this.view = view;
7474
- }
7475
- get remaining() {
7476
- return this.view.byteLength - this.offset;
7477
- }
7478
- need(bytes) {
7479
- if (bytes < 0 || bytes > this.remaining) {
7480
- throw new RangeError(`a value runs past the end of its message (${bytes} bytes wanted, ${this.remaining} left)`);
7431
+ // node_modules/yaml/dist/index.js
7432
+ var composer = require_composer();
7433
+ var Document = require_Document();
7434
+ var Schema = require_Schema();
7435
+ var errors = require_errors();
7436
+ var Alias = require_Alias();
7437
+ var identity = require_identity();
7438
+ var Pair = require_Pair();
7439
+ var Scalar = require_Scalar();
7440
+ var YAMLMap = require_YAMLMap();
7441
+ var YAMLSeq = require_YAMLSeq();
7442
+ var cst = require_cst();
7443
+ var lexer = require_lexer();
7444
+ var lineCounter = require_line_counter();
7445
+ var parser = require_parser();
7446
+ var publicApi = require_public_api();
7447
+ var visit = require_visit();
7448
+ var $Composer = composer.Composer;
7449
+ var $Document = Document.Document;
7450
+ var $Schema = Schema.Schema;
7451
+ var $YAMLError = errors.YAMLError;
7452
+ var $YAMLParseError = errors.YAMLParseError;
7453
+ var $YAMLWarning = errors.YAMLWarning;
7454
+ var $Alias = Alias.Alias;
7455
+ var $isAlias = identity.isAlias;
7456
+ var $isCollection = identity.isCollection;
7457
+ var $isDocument = identity.isDocument;
7458
+ var $isMap = identity.isMap;
7459
+ var $isNode = identity.isNode;
7460
+ var $isPair = identity.isPair;
7461
+ var $isScalar = identity.isScalar;
7462
+ var $isSeq = identity.isSeq;
7463
+ var $Pair = Pair.Pair;
7464
+ var $Scalar = Scalar.Scalar;
7465
+ var $YAMLMap = YAMLMap.YAMLMap;
7466
+ var $YAMLSeq = YAMLSeq.YAMLSeq;
7467
+ var $Lexer = lexer.Lexer;
7468
+ var $LineCounter = lineCounter.LineCounter;
7469
+ var $Parser = parser.Parser;
7470
+ var $parse = publicApi.parse;
7471
+ var $parseAllDocuments = publicApi.parseAllDocuments;
7472
+ var $parseDocument = publicApi.parseDocument;
7473
+ var $stringify = publicApi.stringify;
7474
+ var $visit = visit.visit;
7475
+ var $visitAsync = visit.visitAsync;
7476
+
7477
+ // src/paths.ts
7478
+ import { realpathSync, statSync as statSync2 } from "node:fs";
7479
+ import { basename, dirname, isAbsolute, join as join2, relative, resolve, sep } from "node:path";
7480
+ function realAsFarAsItExists(path) {
7481
+ const missing = [];
7482
+ let at = path;
7483
+ for (;; ) {
7484
+ try {
7485
+ return join2(realpathSync.native(at), ...missing);
7486
+ } catch {
7487
+ const parent = dirname(at);
7488
+ if (parent === at) {
7489
+ return path;
7490
+ }
7491
+ missing.unshift(basename(at));
7492
+ at = parent;
7481
7493
  }
7482
7494
  }
7483
- u32() {
7484
- this.need(4);
7485
- const value = this.view.getUint32(this.offset, true);
7486
- this.offset += 4;
7487
- return value;
7488
- }
7489
- i32() {
7490
- this.need(4);
7491
- const value = this.view.getInt32(this.offset, true);
7492
- this.offset += 4;
7493
- return value;
7494
- }
7495
- i64() {
7496
- this.need(8);
7497
- const value = Number(this.view.getBigInt64(this.offset, true));
7498
- this.offset += 8;
7499
- return value;
7500
- }
7501
- u64() {
7502
- this.need(8);
7503
- const value = Number(this.view.getBigUint64(this.offset, true));
7504
- this.offset += 8;
7505
- return value;
7506
- }
7507
- f32() {
7508
- this.need(4);
7509
- const value = this.view.getFloat32(this.offset, true);
7510
- this.offset += 4;
7511
- return value;
7512
- }
7513
- f64() {
7514
- this.need(8);
7515
- const value = this.view.getFloat64(this.offset, true);
7516
- this.offset += 8;
7517
- return value;
7518
- }
7519
- real(wide) {
7520
- return wide ? this.f64() : this.f32();
7521
- }
7522
- bytes(count) {
7523
- this.need(count);
7524
- const out = new Uint8Array(this.view.buffer, this.view.byteOffset + this.offset, count);
7525
- this.offset += count;
7526
- return out;
7527
- }
7528
- string() {
7529
- const length = this.u32();
7530
- const text = new TextDecoder().decode(this.bytes(length));
7531
- this.bytes((4 - length % 4) % 4);
7532
- return text;
7533
- }
7534
7495
  }
7535
- var MAX_DEPTH = 1024;
7536
- function containerType(reader, kind) {
7537
- if (kind === 1) {
7538
- reader.u32();
7539
- } else if (kind === 2 || kind === 3) {
7540
- reader.string();
7541
- }
7496
+
7497
+ // src/harnesses.ts
7498
+ var SERVER_KEY = "gdharness";
7499
+ function tomlArray(values) {
7500
+ return `[${values.map((value) => `"${value}"`).join(", ")}]`;
7542
7501
  }
7543
- function decodeValue(reader, depth) {
7544
- if (depth > MAX_DEPTH) {
7545
- throw new RangeError("a value nests deeper than the engine allows");
7546
- }
7547
- const header = reader.u32();
7548
- const type = header & 255;
7549
- const wide = (header & FLAG_64) !== 0;
7550
- const math = MATH[type];
7551
- if (math !== undefined) {
7552
- return {
7553
- kind: math.name,
7554
- values: Array.from({ length: math.count }, () => math.ints === true ? reader.i32() : reader.real(wide))
7555
- };
7556
- }
7557
- const vectorWidth = PACKED_VECTORS[type];
7558
- if (vectorWidth !== undefined) {
7559
- const count = reader.u32();
7560
- return Array.from({ length: count }, () => ({
7561
- kind: "Vector",
7562
- values: Array.from({ length: vectorWidth }, () => reader.real(wide))
7563
- }));
7564
- }
7565
- switch (type) {
7566
- case NIL:
7567
- return null;
7568
- case BOOL:
7569
- return reader.u32() !== 0;
7570
- case INT:
7571
- return wide ? reader.i64() : reader.i32();
7572
- case FLOAT:
7573
- return wide ? reader.f64() : reader.f32();
7574
- case STRING:
7575
- case 21:
7576
- return reader.string();
7577
- case 20:
7578
- return { kind: "Color", values: [reader.f32(), reader.f32(), reader.f32(), reader.f32()] };
7579
- case 22: {
7580
- const first = reader.u32();
7581
- if ((first & 2147483648) === 0) {
7582
- throw new RangeError("a node path in the format before 3.0");
7583
- }
7584
- const names = first & 2147483647;
7585
- let subnames = reader.u32();
7586
- const flags = reader.u32();
7587
- if ((flags & 2) !== 0) {
7588
- subnames += 1;
7589
- }
7590
- return { kind: "NodePath", values: Array.from({ length: names + subnames }, () => reader.string()) };
7591
- }
7592
- case 23:
7593
- return { kind: "RID", values: [reader.u64()] };
7594
- case 24:
7595
- if ((header & FLAG_64) === 0) {
7596
- throw new RangeError("a full object, which this reader does not take");
7597
- }
7598
- return { kind: "Object", values: [reader.u64()] };
7599
- case 25:
7600
- return { kind: "Callable", values: [] };
7601
- case 26:
7602
- return { kind: "Signal", values: [reader.string(), reader.u64()] };
7603
- case 27: {
7604
- containerType(reader, header >> 16 & 3);
7605
- containerType(reader, header >> 18 & 3);
7606
- const count = reader.u32() & 2147483647;
7607
- const entries = [];
7608
- for (let index = 0;index < count; index += 1) {
7609
- const key = decodeValue(reader, depth + 1);
7610
- entries.push([key, decodeValue(reader, depth + 1)]);
7611
- }
7612
- return { kind: "dictionary", entries };
7502
+ function tomlEnv(launch) {
7503
+ const pairs = Object.entries(launch.env).map(([name, value]) => `${name} = "${value}"`);
7504
+ return `env = { ${pairs.join(", ")} }`;
7505
+ }
7506
+ function jsonSnippet(launch, container, shape) {
7507
+ return JSON.stringify({ [container]: { [SERVER_KEY]: entryFor(shape, launch) } }, null, 2);
7508
+ }
7509
+ var HARNESSES = [
7510
+ {
7511
+ id: "claude-code",
7512
+ name: "Claude Code",
7513
+ scope: "project",
7514
+ file: ".mcp.json",
7515
+ container: "mcpServers",
7516
+ shape: "plain",
7517
+ home: ".claude",
7518
+ marker: ".claude",
7519
+ skills: { dir: join3(".claude", "skills"), shared: false }
7520
+ },
7521
+ {
7522
+ id: "copilot-cli",
7523
+ name: "Copilot CLI",
7524
+ scope: "project",
7525
+ file: ".mcp.json",
7526
+ container: "mcpServers",
7527
+ shape: "plain",
7528
+ home: ".copilot",
7529
+ skills: { dir: join3(".github", "skills"), shared: true },
7530
+ manual: "Trust the folder when Copilot CLI first asks, or it reads no config here."
7531
+ },
7532
+ {
7533
+ id: "qoder",
7534
+ name: "Qoder",
7535
+ scope: "project",
7536
+ file: ".mcp.json",
7537
+ container: "mcpServers",
7538
+ shape: "plain",
7539
+ home: ".qoder",
7540
+ marker: ".qoder"
7541
+ },
7542
+ {
7543
+ id: "command-code",
7544
+ name: "Command Code",
7545
+ scope: "project",
7546
+ file: ".mcp.json",
7547
+ container: "mcpServers",
7548
+ shape: "plain",
7549
+ home: ".commandcode"
7550
+ },
7551
+ {
7552
+ id: "cursor",
7553
+ name: "Cursor",
7554
+ scope: "project",
7555
+ file: join3(".cursor", "mcp.json"),
7556
+ container: "mcpServers",
7557
+ shape: "plain",
7558
+ home: ".cursor",
7559
+ skills: { dir: join3(".cursor", "skills"), shared: true },
7560
+ manual: "Cursor asks before each tool call by default, and the server can be toggled off."
7561
+ },
7562
+ {
7563
+ id: "vscode",
7564
+ name: "VS Code",
7565
+ scope: "project",
7566
+ file: join3(".vscode", "mcp.json"),
7567
+ container: "servers",
7568
+ shape: "typed",
7569
+ home: ".vscode",
7570
+ skills: { dir: join3(".github", "skills"), shared: true },
7571
+ manual: "VS Code asks you to trust a server the first time it starts one."
7572
+ },
7573
+ {
7574
+ id: "opencode",
7575
+ name: "opencode",
7576
+ scope: "project",
7577
+ file: "opencode.json",
7578
+ container: "mcp",
7579
+ shape: "opencode",
7580
+ home: join3(".config", "opencode"),
7581
+ marker: ".opencode",
7582
+ skills: { dir: join3(".opencode", "skills"), shared: true }
7583
+ },
7584
+ {
7585
+ id: "junie",
7586
+ name: "Junie",
7587
+ scope: "project",
7588
+ file: join3(".junie", "mcp", "mcp.json"),
7589
+ container: "mcpServers",
7590
+ shape: "plain",
7591
+ home: ".junie",
7592
+ skills: { dir: join3(".junie", "skills"), shared: true }
7593
+ },
7594
+ {
7595
+ id: "kiro",
7596
+ name: "Kiro",
7597
+ scope: "project",
7598
+ file: join3(".kiro", "settings", "mcp.json"),
7599
+ container: "mcpServers",
7600
+ shape: "plain",
7601
+ home: ".kiro",
7602
+ skills: { dir: join3(".kiro", "skills"), shared: false }
7603
+ },
7604
+ {
7605
+ id: "gemini",
7606
+ name: "Gemini CLI",
7607
+ scope: "project",
7608
+ file: join3(".gemini", "settings.json"),
7609
+ container: "mcpServers",
7610
+ shape: "plain",
7611
+ home: ".gemini",
7612
+ skills: { dir: join3(".gemini", "skills"), shared: true }
7613
+ },
7614
+ {
7615
+ id: "roo",
7616
+ name: "Roo Code",
7617
+ scope: "project",
7618
+ file: join3(".roo", "mcp.json"),
7619
+ container: "mcpServers",
7620
+ shape: "plain"
7621
+ },
7622
+ {
7623
+ id: "kilo",
7624
+ name: "Kilo Code",
7625
+ scope: "project",
7626
+ file: join3(".kilocode", "mcp.json"),
7627
+ container: "mcpServers",
7628
+ shape: "plain"
7629
+ },
7630
+ {
7631
+ id: "amazon-q",
7632
+ name: "Amazon Q Developer CLI",
7633
+ scope: "project",
7634
+ file: join3(".amazonq", "mcp.json"),
7635
+ container: "mcpServers",
7636
+ shape: "plain",
7637
+ home: join3(".aws", "amazonq")
7638
+ },
7639
+ {
7640
+ id: "zed",
7641
+ name: "Zed",
7642
+ scope: "project",
7643
+ file: join3(".zed", "settings.json"),
7644
+ container: "context_servers",
7645
+ shape: "plain"
7646
+ },
7647
+ {
7648
+ id: "amp",
7649
+ name: "Amp",
7650
+ scope: "project",
7651
+ file: join3(".amp", "settings.json"),
7652
+ container: "amp.mcpServers",
7653
+ shape: "plain",
7654
+ home: join3(".config", "amp")
7655
+ },
7656
+ {
7657
+ id: "warp",
7658
+ name: "Warp",
7659
+ scope: "project",
7660
+ file: join3(".warp", ".mcp.json"),
7661
+ container: "mcpServers",
7662
+ shape: "plain",
7663
+ home: ".warp",
7664
+ manual: "Warp starts a project server only once you toggle it on, on its MCP servers page."
7665
+ },
7666
+ {
7667
+ id: "trae",
7668
+ name: "Trae",
7669
+ scope: "project",
7670
+ file: join3(".trae", "mcp.json"),
7671
+ container: "mcpServers",
7672
+ shape: "plain",
7673
+ home: ".trae"
7674
+ },
7675
+ {
7676
+ id: "factory",
7677
+ name: "Factory Droid",
7678
+ scope: "project",
7679
+ file: join3(".factory", "mcp.json"),
7680
+ container: "mcpServers",
7681
+ shape: "typed",
7682
+ home: ".factory"
7683
+ },
7684
+ {
7685
+ id: "tabnine",
7686
+ name: "Tabnine",
7687
+ scope: "project",
7688
+ file: join3(".tabnine", "mcp_servers.json"),
7689
+ container: "mcpServers",
7690
+ shape: "plain",
7691
+ home: ".tabnine"
7692
+ },
7693
+ {
7694
+ id: "firebender",
7695
+ name: "Firebender",
7696
+ scope: "project",
7697
+ file: "firebender.json",
7698
+ container: "mcpServers",
7699
+ shape: "plain"
7700
+ },
7701
+ {
7702
+ id: "pi",
7703
+ name: "pi",
7704
+ scope: "project",
7705
+ file: join3(".pi", "mcp.json"),
7706
+ container: "mcpServers",
7707
+ shape: "plain",
7708
+ home: ".pi"
7709
+ },
7710
+ {
7711
+ id: "mistral-vibe",
7712
+ name: "Mistral Vibe",
7713
+ scope: "project",
7714
+ file: join3(".vibe", "config.toml"),
7715
+ container: "mcp_servers",
7716
+ shape: "plain",
7717
+ toml: {
7718
+ header: "[[mcp_servers]]",
7719
+ identity: `name = "${SERVER_KEY}"`,
7720
+ block: (launch) => [
7721
+ "[[mcp_servers]]",
7722
+ `name = "${SERVER_KEY}"`,
7723
+ 'transport = "stdio"',
7724
+ `command = "${launch.command}"`,
7725
+ `args = ${tomlArray(launch.args)}`,
7726
+ tomlEnv(launch)
7727
+ ].join(`
7728
+ `)
7613
7729
  }
7614
- case ARRAY: {
7615
- containerType(reader, header >> 16 & 3);
7616
- const count = reader.u32() & 2147483647;
7617
- return Array.from({ length: count }, () => decodeValue(reader, depth + 1));
7730
+ },
7731
+ {
7732
+ id: "vtcode",
7733
+ name: "VT Code",
7734
+ scope: "project",
7735
+ file: "vtcode.toml",
7736
+ container: "mcp.providers",
7737
+ shape: "plain",
7738
+ marker: ".vtcode",
7739
+ toml: {
7740
+ header: "[[mcp.providers]]",
7741
+ identity: `name = "${SERVER_KEY}"`,
7742
+ enable: { header: "[mcp]", line: "enabled = true" },
7743
+ block: (launch) => [
7744
+ "[[mcp.providers]]",
7745
+ `name = "${SERVER_KEY}"`,
7746
+ "enabled = true",
7747
+ `command = "${launch.command}"`,
7748
+ `args = ${tomlArray(launch.args)}`,
7749
+ tomlEnv(launch)
7750
+ ].join(`
7751
+ `)
7618
7752
  }
7619
- case 29: {
7620
- const count = reader.u32();
7621
- const bytes = [...reader.bytes(count)];
7622
- reader.bytes((4 - count % 4) % 4);
7623
- return bytes;
7753
+ },
7754
+ {
7755
+ id: "fast-agent",
7756
+ name: "fast-agent",
7757
+ scope: "project",
7758
+ file: "fastagent.config.yaml",
7759
+ container: "mcp",
7760
+ shape: "plain",
7761
+ yaml: {
7762
+ path: ["mcp", "servers", SERVER_KEY],
7763
+ entry: (launch) => ({ command: launch.command, args: [...launch.args], env: { ...launch.env } })
7764
+ }
7765
+ },
7766
+ {
7767
+ id: "codex",
7768
+ name: "Codex CLI",
7769
+ scope: "home",
7770
+ file: join3(".codex", "config.toml"),
7771
+ container: "mcp_servers",
7772
+ shape: "plain",
7773
+ manual: "Codex reads its config at startup, so restart it.",
7774
+ toml: {
7775
+ header: `[mcp_servers.${SERVER_KEY}]`,
7776
+ block: (launch) => [
7777
+ `[mcp_servers.${SERVER_KEY}]`,
7778
+ `command = "${launch.command}"`,
7779
+ `args = ${tomlArray(launch.args)}`,
7780
+ tomlEnv(launch)
7781
+ ].join(`
7782
+ `)
7783
+ }
7784
+ },
7785
+ {
7786
+ id: "cline",
7787
+ name: "Cline",
7788
+ scope: "home",
7789
+ file: join3(".cline", "data", "settings", "cline_mcp_settings.json"),
7790
+ container: "mcpServers",
7791
+ shape: "plain",
7792
+ marker: ".cline",
7793
+ skills: { dir: join3(".cline", "skills"), shared: false }
7794
+ },
7795
+ {
7796
+ id: "goose",
7797
+ name: "Goose",
7798
+ scope: "home",
7799
+ file: {
7800
+ win32: { appData: join3("Block", "goose", "config", "config.yaml") },
7801
+ darwin: join3(".config", "goose", "config.yaml"),
7802
+ linux: join3(".config", "goose", "config.yaml")
7803
+ },
7804
+ container: "extensions",
7805
+ shape: "plain",
7806
+ yaml: {
7807
+ path: ["extensions", SERVER_KEY],
7808
+ entry: (launch) => ({
7809
+ name: SERVER_KEY,
7810
+ type: "stdio",
7811
+ enabled: true,
7812
+ cmd: launch.command,
7813
+ args: [...launch.args],
7814
+ envs: { ...launch.env },
7815
+ timeout: 300
7816
+ })
7624
7817
  }
7625
- case 30:
7626
- return Array.from({ length: reader.u32() }, () => reader.i32());
7627
- case 31:
7628
- return Array.from({ length: reader.u32() }, () => reader.i64());
7629
- case 32:
7630
- return Array.from({ length: reader.u32() }, () => reader.f32());
7631
- case 33:
7632
- return Array.from({ length: reader.u32() }, () => reader.f64());
7633
- case 34:
7634
- return Array.from({ length: reader.u32() }, () => reader.string().replace(/\0$/, ""));
7635
- case 37:
7636
- return Array.from({ length: reader.u32() }, () => ({
7637
- kind: "Color",
7638
- values: [reader.f32(), reader.f32(), reader.f32(), reader.f32()]
7639
- }));
7640
- default:
7641
- throw new RangeError(`a value of type ${type}, which Godot 4.7 does not have`);
7642
- }
7643
- }
7644
- function decodeVariant(bytes) {
7645
- const reader = new Reader(new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength));
7646
- const value = decodeValue(reader, 0);
7647
- if (reader.remaining !== 0) {
7648
- throw new RangeError(`${reader.remaining} bytes left over after the value`);
7649
- }
7650
- return value;
7651
- }
7652
- function pushU32(out, value) {
7653
- out.push(value & 255, value >>> 8 & 255, value >>> 16 & 255, value >>> 24 & 255);
7654
- }
7655
- function encodeInto(out, value) {
7656
- if (value === null) {
7657
- pushU32(out, NIL);
7658
- } else if (typeof value === "boolean") {
7659
- pushU32(out, BOOL);
7660
- pushU32(out, value ? 1 : 0);
7661
- } else if (typeof value === "number") {
7662
- const view = new DataView(new ArrayBuffer(8));
7663
- if (Number.isInteger(value) && value >= -2147483648 && value <= 2147483647) {
7664
- pushU32(out, INT);
7665
- view.setInt32(0, value, true);
7666
- out.push(...new Uint8Array(view.buffer, 0, 4));
7667
- } else if (Number.isInteger(value)) {
7668
- pushU32(out, INT | FLAG_64);
7669
- view.setBigInt64(0, BigInt(value), true);
7670
- out.push(...new Uint8Array(view.buffer));
7671
- } else {
7672
- pushU32(out, FLOAT | FLAG_64);
7673
- view.setFloat64(0, value, true);
7674
- out.push(...new Uint8Array(view.buffer));
7818
+ },
7819
+ {
7820
+ id: "windsurf",
7821
+ name: "Windsurf",
7822
+ scope: "home",
7823
+ file: join3(".codeium", "windsurf", "mcp_config.json"),
7824
+ container: "mcpServers",
7825
+ shape: "plain",
7826
+ skills: { dir: join3(".windsurf", "skills"), shared: true }
7827
+ },
7828
+ {
7829
+ id: "hermes",
7830
+ name: "Hermes",
7831
+ scope: "home",
7832
+ file: join3(".hermes", "config.yaml"),
7833
+ container: "mcp_servers",
7834
+ shape: "plain",
7835
+ skills: { dir: join3(".hermes", "skills"), shared: true },
7836
+ yaml: {
7837
+ path: ["mcp_servers", SERVER_KEY],
7838
+ entry: (launch) => ({
7839
+ command: launch.command,
7840
+ args: [...launch.args],
7841
+ env: { ...launch.env },
7842
+ enabled: true
7843
+ })
7675
7844
  }
7676
- } else if (typeof value === "string") {
7677
- const bytes = new TextEncoder().encode(value);
7678
- pushU32(out, STRING);
7679
- pushU32(out, bytes.length);
7680
- out.push(...bytes, ...Array.from({ length: (4 - bytes.length % 4) % 4 }, () => 0));
7681
- } else {
7682
- pushU32(out, ARRAY);
7683
- pushU32(out, value.length);
7684
- for (const element of value) {
7685
- encodeInto(out, element);
7845
+ },
7846
+ {
7847
+ id: "openclaw",
7848
+ name: "OpenClaw",
7849
+ scope: "home",
7850
+ file: join3(".openclaw", "openclaw.json"),
7851
+ container: "mcpServers",
7852
+ shape: "plain"
7853
+ },
7854
+ {
7855
+ id: "claude-desktop",
7856
+ name: "Claude Desktop",
7857
+ scope: "home",
7858
+ file: {
7859
+ win32: { appData: join3("Claude", "claude_desktop_config.json") },
7860
+ darwin: join3("Library", "Application Support", "Claude", "claude_desktop_config.json"),
7861
+ linux: join3(".config", "Claude", "claude_desktop_config.json")
7862
+ },
7863
+ container: "mcpServers",
7864
+ shape: "plain"
7865
+ },
7866
+ {
7867
+ id: "zeroclaw",
7868
+ name: "ZeroClaw",
7869
+ scope: "home",
7870
+ file: join3(".zeroclaw", "config.toml"),
7871
+ container: "mcp.servers",
7872
+ shape: "plain",
7873
+ toml: {
7874
+ header: "[[mcp.servers]]",
7875
+ identity: `name = "${SERVER_KEY}"`,
7876
+ enable: { header: "[mcp]", line: "enabled = true" },
7877
+ block: (launch) => [
7878
+ "[[mcp.servers]]",
7879
+ `name = "${SERVER_KEY}"`,
7880
+ `command = "${launch.command}"`,
7881
+ `args = ${tomlArray(launch.args)}`,
7882
+ tomlEnv(launch)
7883
+ ].join(`
7884
+ `)
7686
7885
  }
7886
+ },
7887
+ {
7888
+ id: "deepcode",
7889
+ name: "Deep Code",
7890
+ scope: "home",
7891
+ file: join3(".deepcode", "settings.json"),
7892
+ container: "mcpServers",
7893
+ shape: "plain"
7894
+ },
7895
+ {
7896
+ id: "nanobot",
7897
+ name: "nanobot",
7898
+ scope: "home",
7899
+ file: join3(".nanobot", "config.json"),
7900
+ container: "mcpServers",
7901
+ shape: "plain",
7902
+ snippet: (launch) => jsonSnippet(launch, "mcpServers", "plain")
7903
+ },
7904
+ {
7905
+ id: "autohand",
7906
+ name: "Autohand",
7907
+ scope: "home",
7908
+ file: join3(".autohand", "config.json"),
7909
+ container: "mcpServers",
7910
+ shape: "plain",
7911
+ snippet: (launch) => jsonSnippet(launch, "mcpServers", "plain")
7687
7912
  }
7688
- }
7689
- function encodeVariant(value) {
7690
- const out = [];
7691
- encodeInto(out, value);
7692
- return Uint8Array.from(out);
7693
- }
7694
- var MAIN_THREAD = 1;
7695
- function framedCommand(message, data, thread = MAIN_THREAD) {
7696
- const body = encodeVariant([message, thread, data]);
7697
- const framed = new Uint8Array(body.length + 4);
7698
- new DataView(framed.buffer).setUint32(0, body.length, true);
7699
- framed.set(body, 4);
7700
- return framed;
7701
- }
7702
- var MOST_MESSAGE_BYTES = (8 << 20) + 4;
7703
-
7704
- class DebuggerStream {
7705
- pending = new Uint8Array(0);
7706
- push(chunk) {
7707
- const joined = new Uint8Array(this.pending.length + chunk.length);
7708
- joined.set(this.pending, 0);
7709
- joined.set(chunk, this.pending.length);
7710
- const out = [];
7711
- let at = 0;
7712
- while (joined.length - at >= 4) {
7713
- const length = new DataView(joined.buffer, joined.byteOffset + at, 4).getUint32(0, true);
7714
- if (length > MOST_MESSAGE_BYTES) {
7715
- throw new RangeError(`a message of ${length} bytes, more than the engine sends`);
7716
- }
7717
- if (joined.length - at - 4 < length) {
7718
- break;
7913
+ ];
7914
+ var OURS = ["GODOT_PATH", "GDHARNESS_PROJECT"];
7915
+ function environmentFor(launch, held) {
7916
+ const kept = {};
7917
+ if (typeof held === "object" && held !== null && !Array.isArray(held)) {
7918
+ for (const [name, value] of Object.entries(held)) {
7919
+ if (typeof value === "string" && !OURS.includes(name)) {
7920
+ kept[name] = value;
7719
7921
  }
7720
- out.push(readMessage(joined.subarray(at + 4, at + 4 + length)));
7721
- at += 4 + length;
7722
7922
  }
7723
- this.pending = joined.slice(at);
7724
- return out;
7725
7923
  }
7924
+ return { ...kept, ...launch.env };
7726
7925
  }
7727
- function readMessage(body) {
7728
- let value;
7729
- try {
7730
- value = decodeVariant(body);
7731
- } catch (error) {
7732
- return { unreadable: error instanceof Error ? error.message : String(error) };
7733
- }
7734
- if (!Array.isArray(value) || value.length !== 3) {
7735
- return { unreadable: "a message that is not [message, thread, data]" };
7736
- }
7737
- const [message, thread, data] = value;
7738
- if (typeof message !== "string" || typeof thread !== "number" || !Array.isArray(data)) {
7739
- return { unreadable: "a message whose name, thread or data is the wrong type" };
7926
+ function entryFor(shape, launch, held) {
7927
+ const before = typeof held === "object" && held !== null && !Array.isArray(held) ? { ...held } : {};
7928
+ if (shape === "opencode") {
7929
+ return {
7930
+ ...before,
7931
+ type: "local",
7932
+ command: [launch.command, ...launch.args],
7933
+ environment: environmentFor(launch, before["environment"]),
7934
+ enabled: true
7935
+ };
7740
7936
  }
7741
- return { message, thread, data };
7937
+ return {
7938
+ ...before,
7939
+ ...shape === "typed" ? { type: "stdio" } : {},
7940
+ command: launch.command,
7941
+ args: [...launch.args],
7942
+ env: environmentFor(launch, before["env"])
7943
+ };
7742
7944
  }
7743
7945
 
7744
- // src/setup.ts
7745
- import { dirname as dirname3, join as join5 } from "node:path";
7746
- import { fileURLToPath } from "node:url";
7747
-
7748
7946
  // src/gdscript-source.ts
7749
7947
  var DECLARING = new Set(["var", "const", "func", "signal", "enum", "class", "for", "class_name"]);
7750
7948
  var NAME = "[\\p{L}_][\\p{L}\\p{N}_]*";
@@ -7807,6 +8005,23 @@ import { readFileSync as readFileSync2, renameSync, rmSync as rmSync2, writeFile
7807
8005
  var HELD_FILE_MS = 1000;
7808
8006
  var HELD_FILE_RETRY_MS = 10;
7809
8007
  var HELD_BY_A_READER = new Set(["EPERM", "EACCES", "EBUSY"]);
8008
+ function readWhole(path) {
8009
+ const until = Date.now() + HELD_FILE_MS;
8010
+ for (;; ) {
8011
+ try {
8012
+ return readFileSync2(path, "utf8");
8013
+ } catch (error) {
8014
+ const code = error.code ?? "";
8015
+ if (code === "ENOENT") {
8016
+ return null;
8017
+ }
8018
+ if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
8019
+ throw error;
8020
+ }
8021
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, HELD_FILE_RETRY_MS);
8022
+ }
8023
+ }
8024
+ }
7810
8025
  function writeWhole(path, text) {
7811
8026
  const partial = `${path}.${process.pid}.partial`;
7812
8027
  try {
@@ -7831,719 +8046,760 @@ function writeWhole(path, text) {
7831
8046
  }
7832
8047
  }
7833
8048
  }
7834
-
7835
- // src/dictionary.ts
7836
- function emptyRecord() {
7837
- return Object.create(null);
8049
+ function removeWhole(path) {
8050
+ const until = Date.now() + HELD_FILE_MS;
8051
+ for (;; ) {
8052
+ try {
8053
+ rmSync2(path, { force: true });
8054
+ return true;
8055
+ } catch (error) {
8056
+ const code = error.code ?? "";
8057
+ if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
8058
+ return false;
8059
+ }
8060
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, HELD_FILE_RETRY_MS);
8061
+ }
8062
+ }
7838
8063
  }
7839
8064
 
7840
- // src/call-signal.ts
7841
- import { AsyncLocalStorage } from "node:async_hooks";
7842
- var current = new AsyncLocalStorage;
8065
+ // src/engine-leaks.ts
8066
+ var REFCOUNTED_HELD = "A RefCounted alive at exit is held by a cycle of references (two holding each other, or one " + "holding a lambda that captures it) or by an Object never freed, rather than by a node left out " + "of the tree; a WeakRef on one side of the cycle, or clearing the reference once done, frees it.";
8067
+ var WALK_STOPPED_SENTENCE = "The engine's shutdown stopped part way through its scripts (godot#117991, fixed in 4.8 by " + "godot#120976), so it reported as leaked at exit what it never got to free, and the report is the " + "engine's rather than the run's. gdharness holds every script loaded from a file past that walk, " + "which keeps it whole, so this one stopped on a script it could not hold, such as one made in code. " + "A leak of the run's own is not told apart in such a run.";
8068
+ var NAME2 = "[\\p{L}_][\\p{L}\\p{N}_]*";
8069
+ var EXPORTED = new RegExp(`@export(?!_(?:group|subgroup|category)\\b)\\w*(?:\\([^)]*\\))?\\s+(?:@${NAME2}(?:\\([^)]*\\))?\\s+)*` + `(?:static\\s+)?var\\s+(${NAME2})\\s*:\\s*(?:Array|Dictionary)\\s*\\[([^\\]]*)\\]`, "gu");
7843
8070
 
7844
- // src/harnesses.ts
7845
- import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute2, join as join3, relative as relative2, resolve as resolve2 } from "node:path";
8071
+ // src/junit.ts
8072
+ var ESCAPE = String.fromCharCode(27);
8073
+ var LOCATION = `'[^'\\n]*' in \\S+:\\d+`;
8074
+ var IGNORING_CASE = " \\(ignoring case\\)";
8075
+ var PRINTED_ACTUAL = new RegExp(` but was\\n '((?:(?!\\tat ${LOCATION})[\\s\\S])*?)'((?:${ESCAPE}\\[[0-9;]*m)*)(${IGNORING_CASE})?((?:${ESCAPE}\\[[0-9;]*m)*)\\tat (${LOCATION})`, "g");
8076
+ var QUOTED_EQUALITY = new RegExp(`^Expecting(?: error message)?:\\n '([\\s\\S]*)'\\n but was\\n '([\\s\\S]*)'(?:${IGNORING_CASE})?\\n\\tat `);
8077
+ var COLOUR = new RegExp(`${ESCAPE}\\[([0-9;]*)m`, "g");
8078
+ var HOOKS_LEFT = new Map([
8079
+ ["before", "before()"],
8080
+ ["after", "after()"],
8081
+ ["before or after", "before() or after()"]
8082
+ ]);
7846
8083
 
7847
- // node_modules/yaml/dist/index.js
7848
- var composer = require_composer();
7849
- var Document = require_Document();
7850
- var Schema = require_Schema();
7851
- var errors = require_errors();
7852
- var Alias = require_Alias();
7853
- var identity = require_identity();
7854
- var Pair = require_Pair();
7855
- var Scalar = require_Scalar();
7856
- var YAMLMap = require_YAMLMap();
7857
- var YAMLSeq = require_YAMLSeq();
7858
- var cst = require_cst();
7859
- var lexer = require_lexer();
7860
- var lineCounter = require_line_counter();
7861
- var parser = require_parser();
7862
- var publicApi = require_public_api();
7863
- var visit = require_visit();
7864
- var $Composer = composer.Composer;
7865
- var $Document = Document.Document;
7866
- var $Schema = Schema.Schema;
7867
- var $YAMLError = errors.YAMLError;
7868
- var $YAMLParseError = errors.YAMLParseError;
7869
- var $YAMLWarning = errors.YAMLWarning;
7870
- var $Alias = Alias.Alias;
7871
- var $isAlias = identity.isAlias;
7872
- var $isCollection = identity.isCollection;
7873
- var $isDocument = identity.isDocument;
7874
- var $isMap = identity.isMap;
7875
- var $isNode = identity.isNode;
7876
- var $isPair = identity.isPair;
7877
- var $isScalar = identity.isScalar;
7878
- var $isSeq = identity.isSeq;
7879
- var $Pair = Pair.Pair;
7880
- var $Scalar = Scalar.Scalar;
7881
- var $YAMLMap = YAMLMap.YAMLMap;
7882
- var $YAMLSeq = YAMLSeq.YAMLSeq;
7883
- var $Lexer = lexer.Lexer;
7884
- var $LineCounter = lineCounter.LineCounter;
7885
- var $Parser = parser.Parser;
7886
- var $parse = publicApi.parse;
7887
- var $parseAllDocuments = publicApi.parseAllDocuments;
7888
- var $parseDocument = publicApi.parseDocument;
7889
- var $stringify = publicApi.stringify;
7890
- var $visit = visit.visit;
7891
- var $visitAsync = visit.visitAsync;
8084
+ // src/suite-judge.ts
8085
+ var TIME = String.raw`(?:\d+h )?(?:\d+min )?(?:\d+s )?\d+ms`;
8086
+ var CASE_LINE = new RegExp(String.raw`^\s*(res://.+?\.gd) > (.+?) (STARTED|PASSED|FAILED|FLAKY|SKIPPED|WARNING)(?: \((?:retry \d+|\d+ retries)\))?(?: (${TIME}))?\s*$`);
8087
+ var STATISTICS = new RegExp(String.raw`^Statistics:.*\|\s*([A-Z]+)(?:\s+\(.*?\))?\s+(${TIME})\s*$`);
8088
+ var WINDOWS_EXITS = new Map([
8089
+ [3221225477, "STATUS_ACCESS_VIOLATION"],
8090
+ [3221225495, "STATUS_NO_MEMORY"],
8091
+ [3221225773, "STATUS_COMMITMENT_LIMIT"],
8092
+ [3221225794, "STATUS_DLL_INIT_FAILED"],
8093
+ [3221226505, "STATUS_STACK_BUFFER_OVERRUN"],
8094
+ [3228369022, "a module that could not be found (VcppException)"],
8095
+ [3228369023, "a procedure that could not be found (VcppException)"]
8096
+ ]);
8097
+ var MEMORY_EXITS = new Set([3221225495, 3221225773]);
8098
+ var GDUNIT_FAILED = 100;
8099
+ var GDUNIT_ORPHANS = 101;
8100
+ var GDUNIT_EXITS = new Map([
8101
+ [GDUNIT_FAILED, "gdUnit4's code for a case that failed or errored"],
8102
+ [GDUNIT_ORPHANS, "gdUnit4's code for orphan nodes left behind"],
8103
+ [103, "gdUnit4's code for a suite that cannot run headless"],
8104
+ [104, "gdUnit4's code for a Godot version it does not support"],
8105
+ [105, "gdUnit4's code for scripts that did not compile"]
8106
+ ]);
8107
+ var GODOT_EXITS = new Set([1, 255]);
8108
+ var QUEUE_OVERFLOW_FRAMES = new Map([
8109
+ ["ed1daf0bf001b61586d9930840f2f1394092c079", ["main+3e1082a", "main+3e0f4aa"]]
8110
+ ]);
7892
8111
 
7893
- // src/paths.ts
7894
- import { realpathSync, statSync as statSync2 } from "node:fs";
7895
- import { basename, dirname, isAbsolute, join as join2, relative, resolve, sep } from "node:path";
7896
- function realAsFarAsItExists(path) {
7897
- const missing = [];
7898
- let at = path;
7899
- for (;; ) {
8112
+ // src/windows-ask.ts
8113
+ import { tmpdir as tmpdir2 } from "node:os";
8114
+ import process3 from "node:process";
8115
+ var SCRIPT = [
8116
+ "[Console]::OutputEncoding = [Text.Encoding]::UTF8",
8117
+ "$in = [Console]::In",
8118
+ "while ($null -ne ($line = $in.ReadLine())) {",
8119
+ ' $parts = $line.Split(" ", 2)',
8120
+ " try {",
8121
+ " $asked = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($parts[1]))",
8122
+ " $said = (& ([ScriptBlock]::Create($asked)) | Out-String -Width 1000000)",
8123
+ ' $tag = "OK"',
8124
+ " } catch {",
8125
+ " $said = $_.Exception.Message",
8126
+ ' $tag = "ERR"',
8127
+ " }",
8128
+ ' [Console]::Out.Write($parts[0] + " " + $tag + " " + [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($said)) + "`n")',
8129
+ " [Console]::Out.Flush()",
8130
+ "}"
8131
+ ].join(`
8132
+ `);
8133
+
8134
+ class WindowsAskFailure extends Error {
8135
+ constructor(how) {
8136
+ super(`PowerShell ${how}`);
8137
+ }
8138
+ }
8139
+ var helper = null;
8140
+ var buffered = "";
8141
+ var next = 1;
8142
+ var out = null;
8143
+ var queued = [];
8144
+ var held = false;
8145
+ function holding(child, wanted) {
8146
+ if (held === wanted) {
8147
+ return;
8148
+ }
8149
+ held = wanted;
8150
+ const stdout = child.stdout;
8151
+ if (wanted) {
8152
+ stdout.ref?.();
8153
+ } else {
8154
+ stdout.unref?.();
8155
+ }
8156
+ }
8157
+ function running() {
8158
+ if (helper?.exitCode === null && helper.signalCode === null) {
8159
+ return helper;
8160
+ }
8161
+ const child = spawnHidden("powershell.exe", [
8162
+ "-NoProfile",
8163
+ "-NonInteractive",
8164
+ "-ExecutionPolicy",
8165
+ "Bypass",
8166
+ "-EncodedCommand",
8167
+ Buffer.from(SCRIPT, "utf16le").toString("base64")
8168
+ ], { cwd: tmpdir2() });
8169
+ buffered = "";
8170
+ child.stdout.setEncoding("utf8");
8171
+ child.stdout.on("data", (chunk) => {
8172
+ buffered += chunk;
8173
+ for (let end = buffered.indexOf(`
8174
+ `);end !== -1; end = buffered.indexOf(`
8175
+ `)) {
8176
+ answered(buffered.slice(0, end).trim());
8177
+ buffered = buffered.slice(end + 1);
8178
+ }
8179
+ });
8180
+ child.stderr.resume();
8181
+ child.stdin.on("error", () => {});
8182
+ const gone = () => {
8183
+ if (helper !== child) {
8184
+ return;
8185
+ }
8186
+ helper = null;
8187
+ if (out !== null) {
8188
+ clearTimeout(out.timer);
8189
+ out.question.reject(new WindowsAskFailure("exited before it answered"));
8190
+ out = null;
8191
+ }
8192
+ sendNext();
8193
+ };
8194
+ child.once("exit", gone);
8195
+ child.once("error", gone);
8196
+ child.unref();
8197
+ child.stdin.unref?.();
8198
+ child.stderr.unref?.();
8199
+ held = true;
8200
+ holding(child, false);
8201
+ helper = child;
8202
+ return child;
8203
+ }
8204
+ function answered(line) {
8205
+ const [id, tag, body] = line.split(" ");
8206
+ if (out === null || out.id !== Number(id)) {
8207
+ return;
8208
+ }
8209
+ const { question, timer } = out;
8210
+ clearTimeout(timer);
8211
+ out = null;
8212
+ const said = Buffer.from(body ?? "", "base64").toString("utf8");
8213
+ if (tag === "OK") {
8214
+ question.resolve(said);
8215
+ } else {
8216
+ question.reject(new WindowsAskFailure(`refused the question: ${said.trim() || "no reason given"}`));
8217
+ }
8218
+ sendNext();
8219
+ }
8220
+ function sendNext() {
8221
+ const question = queued.shift();
8222
+ if (question === undefined) {
8223
+ if (helper !== null) {
8224
+ holding(helper, false);
8225
+ }
8226
+ return;
8227
+ }
8228
+ const child = running();
8229
+ holding(child, true);
8230
+ const id = next;
8231
+ next += 1;
8232
+ const timer = setTimeout(() => {
8233
+ if (out?.id !== id) {
8234
+ return;
8235
+ }
8236
+ out = null;
8237
+ question.reject(new WindowsAskFailure(`did not answer within ${question.withinMs}ms`));
8238
+ if (helper === child) {
8239
+ helper = null;
8240
+ }
8241
+ child.kill();
8242
+ sendNext();
8243
+ }, question.withinMs);
8244
+ out = { id, question, timer };
8245
+ child.stdin.write(`${id} ${Buffer.from(question.script, "utf8").toString("base64")}
8246
+ `);
8247
+ }
8248
+ function askWindows(script, withinMs) {
8249
+ if (process3.platform !== "win32") {
8250
+ return Promise.reject(new WindowsAskFailure("is asked only on Windows"));
8251
+ }
8252
+ return new Promise((resolve, reject) => {
8253
+ queued.push({ script, withinMs, resolve, reject });
8254
+ if (out === null) {
8255
+ sendNext();
8256
+ }
8257
+ });
8258
+ }
8259
+
8260
+ // src/engine-launch.ts
8261
+ var CONTROL_SEQUENCE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[A-Za-z]`, "g");
8262
+ var MEMORY_KEPT = 4 * 1024 ** 3;
8263
+ var MEMORY_PER_ENGINE = 1.5 * 1024 ** 3;
8264
+
8265
+ // src/process-children.ts
8266
+ import { readFileSync as readFileSync3 } from "node:fs";
8267
+ import { basename as basename3 } from "node:path";
8268
+ async function startTimesOf(pids) {
8269
+ const began = new Map;
8270
+ if (pids.length === 0) {
8271
+ return began;
8272
+ }
8273
+ try {
8274
+ if (process.platform === "win32") {
8275
+ const filter = pids.map((pid) => `ProcessId=${pid}`).join(" OR ");
8276
+ const said = await askWindows(`Get-CimInstance Win32_Process -Filter "${filter}" | ForEach-Object { "$($_.ProcessId) $(([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds())" }`, ASK_TIMEOUT_MS);
8277
+ for (const line of said.split(`
8278
+ `)) {
8279
+ const found = /^\s*(\d+)\s+(\d+)\s*$/.exec(line);
8280
+ if (found?.[1] !== undefined && found[2] !== undefined) {
8281
+ began.set(Number(found[1]), Number(found[2]));
8282
+ }
8283
+ }
8284
+ return began;
8285
+ }
8286
+ if (process.platform === "linux") {
8287
+ return await linuxStartTimes(pids);
8288
+ }
8289
+ const said = await printedBy("ps", ["-p", pids.join(","), "-o", "pid=,lstart="], {
8290
+ ...process.env,
8291
+ LC_ALL: "C"
8292
+ });
8293
+ for (const line of said.split(`
8294
+ `)) {
8295
+ const found = /^\s*(\d+)\s+(.+)$/.exec(line);
8296
+ const at = Date.parse(found?.[2]?.trim() ?? "");
8297
+ if (found?.[1] !== undefined && !Number.isNaN(at)) {
8298
+ began.set(Number(found[1]), at);
8299
+ }
8300
+ }
8301
+ } catch {}
8302
+ return began;
8303
+ }
8304
+ async function linuxStartTimes(pids) {
8305
+ const began = new Map;
8306
+ const hertz = await ticksPerSecond();
8307
+ let booted;
8308
+ try {
8309
+ booted = Number(/^btime\s+(\d+)$/m.exec(readFileSync3("/proc/stat", "utf8"))?.[1] ?? Number.NaN) * 1000;
8310
+ } catch {
8311
+ return began;
8312
+ }
8313
+ if (hertz === null || Number.isNaN(booted)) {
8314
+ return began;
8315
+ }
8316
+ for (const pid of pids) {
8317
+ let stat;
7900
8318
  try {
7901
- return join2(realpathSync.native(at), ...missing);
8319
+ stat = readFileSync3(`/proc/${String(pid)}/stat`, "utf8");
7902
8320
  } catch {
7903
- const parent = dirname(at);
7904
- if (parent === at) {
7905
- return path;
7906
- }
7907
- missing.unshift(basename(at));
7908
- at = parent;
8321
+ continue;
8322
+ }
8323
+ const ticks = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19]);
8324
+ if (Number.isInteger(ticks) && ticks >= 0) {
8325
+ began.set(pid, booted + Math.round(ticks * 1000 / hertz));
7909
8326
  }
7910
8327
  }
8328
+ return began;
7911
8329
  }
7912
-
7913
- // src/harnesses.ts
7914
- var SERVER_KEY = "gdharness";
7915
- function tomlArray(values) {
7916
- return `[${values.map((value) => `"${value}"`).join(", ")}]`;
8330
+ async function linuxStartEarliest() {
8331
+ return 1000 + Math.ceil(1000 / (await ticksPerSecond() ?? 100));
7917
8332
  }
7918
- function tomlEnv(launch) {
7919
- const pairs = Object.entries(launch.env).map(([name, value]) => `${name} = "${value}"`);
7920
- return `env = { ${pairs.join(", ")} }`;
8333
+ var ticksAnswered;
8334
+ async function ticksPerSecond() {
8335
+ if (ticksAnswered !== undefined) {
8336
+ return ticksAnswered;
8337
+ }
8338
+ try {
8339
+ const hertz = Number((await runHidden("getconf", ["CLK_TCK"], { timeout: ASK_TIMEOUT_MS })).stdout.trim());
8340
+ if (Number.isInteger(hertz) && hertz > 0) {
8341
+ ticksAnswered = hertz;
8342
+ return hertz;
8343
+ }
8344
+ } catch {}
8345
+ return null;
7921
8346
  }
7922
- function jsonSnippet(launch, container, shape) {
7923
- return JSON.stringify({ [container]: { [SERVER_KEY]: entryFor(shape, launch) } }, null, 2);
8347
+ function printedBy(command, args, env) {
8348
+ return runHidden(command, args, { timeout: ASK_TIMEOUT_MS, env }).then(({ stdout }) => stdout, (error) => {
8349
+ const printed = error.stdout;
8350
+ return typeof printed === "string" ? printed : "";
8351
+ });
7924
8352
  }
7925
- var HARNESSES = [
7926
- {
7927
- id: "claude-code",
7928
- name: "Claude Code",
7929
- scope: "project",
7930
- file: ".mcp.json",
7931
- container: "mcpServers",
7932
- shape: "plain",
7933
- home: ".claude",
7934
- marker: ".claude",
7935
- skills: { dir: join3(".claude", "skills"), shared: false }
7936
- },
7937
- {
7938
- id: "copilot-cli",
7939
- name: "Copilot CLI",
7940
- scope: "project",
7941
- file: ".mcp.json",
7942
- container: "mcpServers",
7943
- shape: "plain",
7944
- home: ".copilot",
7945
- skills: { dir: join3(".github", "skills"), shared: true },
7946
- manual: "Trust the folder when Copilot CLI first asks, or it reads no config here."
7947
- },
7948
- {
7949
- id: "qoder",
7950
- name: "Qoder",
7951
- scope: "project",
7952
- file: ".mcp.json",
7953
- container: "mcpServers",
7954
- shape: "plain",
7955
- home: ".qoder",
7956
- marker: ".qoder"
7957
- },
7958
- {
7959
- id: "command-code",
7960
- name: "Command Code",
7961
- scope: "project",
7962
- file: ".mcp.json",
7963
- container: "mcpServers",
7964
- shape: "plain",
7965
- home: ".commandcode"
7966
- },
7967
- {
7968
- id: "cursor",
7969
- name: "Cursor",
7970
- scope: "project",
7971
- file: join3(".cursor", "mcp.json"),
7972
- container: "mcpServers",
7973
- shape: "plain",
7974
- home: ".cursor",
7975
- skills: { dir: join3(".cursor", "skills"), shared: true },
7976
- manual: "Cursor asks before each tool call by default, and the server can be toggled off."
7977
- },
7978
- {
7979
- id: "vscode",
7980
- name: "VS Code",
7981
- scope: "project",
7982
- file: join3(".vscode", "mcp.json"),
7983
- container: "servers",
7984
- shape: "typed",
7985
- home: ".vscode",
7986
- skills: { dir: join3(".github", "skills"), shared: true },
7987
- manual: "VS Code asks you to trust a server the first time it starts one."
7988
- },
7989
- {
7990
- id: "opencode",
7991
- name: "opencode",
7992
- scope: "project",
7993
- file: "opencode.json",
7994
- container: "mcp",
7995
- shape: "opencode",
7996
- home: join3(".config", "opencode"),
7997
- marker: ".opencode",
7998
- skills: { dir: join3(".opencode", "skills"), shared: true }
7999
- },
8000
- {
8001
- id: "junie",
8002
- name: "Junie",
8003
- scope: "project",
8004
- file: join3(".junie", "mcp", "mcp.json"),
8005
- container: "mcpServers",
8006
- shape: "plain",
8007
- home: ".junie",
8008
- skills: { dir: join3(".junie", "skills"), shared: true }
8009
- },
8010
- {
8011
- id: "kiro",
8012
- name: "Kiro",
8013
- scope: "project",
8014
- file: join3(".kiro", "settings", "mcp.json"),
8015
- container: "mcpServers",
8016
- shape: "plain",
8017
- home: ".kiro",
8018
- skills: { dir: join3(".kiro", "skills"), shared: false }
8019
- },
8020
- {
8021
- id: "gemini",
8022
- name: "Gemini CLI",
8023
- scope: "project",
8024
- file: join3(".gemini", "settings.json"),
8025
- container: "mcpServers",
8026
- shape: "plain",
8027
- home: ".gemini",
8028
- skills: { dir: join3(".gemini", "skills"), shared: true }
8029
- },
8030
- {
8031
- id: "roo",
8032
- name: "Roo Code",
8033
- scope: "project",
8034
- file: join3(".roo", "mcp.json"),
8035
- container: "mcpServers",
8036
- shape: "plain"
8037
- },
8038
- {
8039
- id: "kilo",
8040
- name: "Kilo Code",
8041
- scope: "project",
8042
- file: join3(".kilocode", "mcp.json"),
8043
- container: "mcpServers",
8044
- shape: "plain"
8045
- },
8046
- {
8047
- id: "amazon-q",
8048
- name: "Amazon Q Developer CLI",
8049
- scope: "project",
8050
- file: join3(".amazonq", "mcp.json"),
8051
- container: "mcpServers",
8052
- shape: "plain",
8053
- home: join3(".aws", "amazonq")
8054
- },
8055
- {
8056
- id: "zed",
8057
- name: "Zed",
8058
- scope: "project",
8059
- file: join3(".zed", "settings.json"),
8060
- container: "context_servers",
8061
- shape: "plain"
8062
- },
8063
- {
8064
- id: "amp",
8065
- name: "Amp",
8066
- scope: "project",
8067
- file: join3(".amp", "settings.json"),
8068
- container: "amp.mcpServers",
8069
- shape: "plain",
8070
- home: join3(".config", "amp")
8071
- },
8072
- {
8073
- id: "warp",
8074
- name: "Warp",
8075
- scope: "project",
8076
- file: join3(".warp", ".mcp.json"),
8077
- container: "mcpServers",
8078
- shape: "plain",
8079
- home: ".warp",
8080
- manual: "Warp starts a project server only once you toggle it on, on its MCP servers page."
8081
- },
8082
- {
8083
- id: "trae",
8084
- name: "Trae",
8085
- scope: "project",
8086
- file: join3(".trae", "mcp.json"),
8087
- container: "mcpServers",
8088
- shape: "plain",
8089
- home: ".trae"
8090
- },
8091
- {
8092
- id: "factory",
8093
- name: "Factory Droid",
8094
- scope: "project",
8095
- file: join3(".factory", "mcp.json"),
8096
- container: "mcpServers",
8097
- shape: "typed",
8098
- home: ".factory"
8099
- },
8100
- {
8101
- id: "tabnine",
8102
- name: "Tabnine",
8103
- scope: "project",
8104
- file: join3(".tabnine", "mcp_servers.json"),
8105
- container: "mcpServers",
8106
- shape: "plain",
8107
- home: ".tabnine"
8108
- },
8109
- {
8110
- id: "firebender",
8111
- name: "Firebender",
8112
- scope: "project",
8113
- file: "firebender.json",
8114
- container: "mcpServers",
8115
- shape: "plain"
8116
- },
8117
- {
8118
- id: "pi",
8119
- name: "pi",
8120
- scope: "project",
8121
- file: join3(".pi", "mcp.json"),
8122
- container: "mcpServers",
8123
- shape: "plain",
8124
- home: ".pi"
8125
- },
8126
- {
8127
- id: "mistral-vibe",
8128
- name: "Mistral Vibe",
8129
- scope: "project",
8130
- file: join3(".vibe", "config.toml"),
8131
- container: "mcp_servers",
8132
- shape: "plain",
8133
- toml: {
8134
- header: "[[mcp_servers]]",
8135
- identity: `name = "${SERVER_KEY}"`,
8136
- block: (launch) => [
8137
- "[[mcp_servers]]",
8138
- `name = "${SERVER_KEY}"`,
8139
- 'transport = "stdio"',
8140
- `command = "${launch.command}"`,
8141
- `args = ${tomlArray(launch.args)}`,
8142
- tomlEnv(launch)
8143
- ].join(`
8144
- `)
8145
- }
8146
- },
8147
- {
8148
- id: "vtcode",
8149
- name: "VT Code",
8150
- scope: "project",
8151
- file: "vtcode.toml",
8152
- container: "mcp.providers",
8153
- shape: "plain",
8154
- marker: ".vtcode",
8155
- toml: {
8156
- header: "[[mcp.providers]]",
8157
- identity: `name = "${SERVER_KEY}"`,
8158
- enable: { header: "[mcp]", line: "enabled = true" },
8159
- block: (launch) => [
8160
- "[[mcp.providers]]",
8161
- `name = "${SERVER_KEY}"`,
8162
- "enabled = true",
8163
- `command = "${launch.command}"`,
8164
- `args = ${tomlArray(launch.args)}`,
8165
- tomlEnv(launch)
8166
- ].join(`
8167
- `)
8353
+ var ASK_TIMEOUT_MS = 15000;
8354
+ var ONE_SHOT_OPTIONS = [
8355
+ "--import",
8356
+ "--quit",
8357
+ "--quit-after",
8358
+ "--export-release",
8359
+ "--export-debug",
8360
+ "--export-pack",
8361
+ "--export-patch",
8362
+ "--doctool",
8363
+ "--gdscript-docs",
8364
+ "--script",
8365
+ "-s",
8366
+ "--check-only",
8367
+ "--build-solutions",
8368
+ "--dump-gdextension-interface",
8369
+ "--dump-extension-api",
8370
+ "--dump-extension-api-with-docs",
8371
+ "--validate-extension-api",
8372
+ "--convert-3to4",
8373
+ "--validate-conversion-3to4"
8374
+ ];
8375
+ function readCommandLine(command) {
8376
+ const all = commandWords(command);
8377
+ const separator = all.findIndex((word) => word === "--" || word === "++");
8378
+ const words = separator === -1 ? all : all.slice(0, separator);
8379
+ const executable = words[0] === undefined ? "" : basename3(words[0].replaceAll("\\", "/"));
8380
+ const at = words.indexOf("--path");
8381
+ let projectPath = null;
8382
+ if (at !== -1 && at + 1 < words.length) {
8383
+ const taken = [];
8384
+ for (const word of words.slice(at + 1)) {
8385
+ if (taken.length > 0 && (word.startsWith("-") || /^[a-z]+:\/\//.test(word))) {
8386
+ break;
8387
+ }
8388
+ taken.push(word);
8168
8389
  }
8169
- },
8170
- {
8171
- id: "fast-agent",
8172
- name: "fast-agent",
8173
- scope: "project",
8174
- file: "fastagent.config.yaml",
8175
- container: "mcp",
8176
- shape: "plain",
8177
- yaml: {
8178
- path: ["mcp", "servers", SERVER_KEY],
8179
- entry: (launch) => ({ command: launch.command, args: [...launch.args], env: { ...launch.env } })
8390
+ projectPath = taken.join(" ").replaceAll("%20", " ");
8391
+ }
8392
+ return {
8393
+ executable,
8394
+ projectPath,
8395
+ editor: words.some((word) => word === "-e" || word === "--editor"),
8396
+ oneShot: words.find((word) => ONE_SHOT_OPTIONS.includes(word)) ?? null
8397
+ };
8398
+ }
8399
+ function commandWords(command) {
8400
+ const words = [];
8401
+ let word = "";
8402
+ let quoted = false;
8403
+ let started = false;
8404
+ for (const char of command) {
8405
+ if (char === '"') {
8406
+ quoted = !quoted;
8407
+ started = true;
8408
+ } else if (!quoted && /\s/.test(char)) {
8409
+ if (started) {
8410
+ words.push(word);
8411
+ word = "";
8412
+ started = false;
8413
+ }
8414
+ } else {
8415
+ word += char;
8416
+ started = true;
8180
8417
  }
8181
- },
8182
- {
8183
- id: "codex",
8184
- name: "Codex CLI",
8185
- scope: "home",
8186
- file: join3(".codex", "config.toml"),
8187
- container: "mcp_servers",
8188
- shape: "plain",
8189
- manual: "Codex reads its config at startup, so restart it.",
8190
- toml: {
8191
- header: `[mcp_servers.${SERVER_KEY}]`,
8192
- block: (launch) => [
8193
- `[mcp_servers.${SERVER_KEY}]`,
8194
- `command = "${launch.command}"`,
8195
- `args = ${tomlArray(launch.args)}`,
8196
- tomlEnv(launch)
8197
- ].join(`
8198
- `)
8418
+ }
8419
+ if (started) {
8420
+ words.push(word);
8421
+ }
8422
+ return words;
8423
+ }
8424
+
8425
+ // src/engine-slots.ts
8426
+ function holderOf(content) {
8427
+ try {
8428
+ const parsed = JSON.parse(content);
8429
+ if (typeof parsed !== "object" || parsed === null) {
8430
+ return null;
8199
8431
  }
8200
- },
8201
- {
8202
- id: "cline",
8203
- name: "Cline",
8204
- scope: "home",
8205
- file: join3(".cline", "data", "settings", "cline_mcp_settings.json"),
8206
- container: "mcpServers",
8207
- shape: "plain",
8208
- marker: ".cline",
8209
- skills: { dir: join3(".cline", "skills"), shared: false }
8210
- },
8211
- {
8212
- id: "goose",
8213
- name: "Goose",
8214
- scope: "home",
8215
- file: {
8216
- win32: { appData: join3("Block", "goose", "config", "config.yaml") },
8217
- darwin: join3(".config", "goose", "config.yaml"),
8218
- linux: join3(".config", "goose", "config.yaml")
8219
- },
8220
- container: "extensions",
8221
- shape: "plain",
8222
- yaml: {
8223
- path: ["extensions", SERVER_KEY],
8224
- entry: (launch) => ({
8225
- name: SERVER_KEY,
8226
- type: "stdio",
8227
- enabled: true,
8228
- cmd: launch.command,
8229
- args: [...launch.args],
8230
- envs: { ...launch.env },
8231
- timeout: 300
8232
- })
8432
+ const { pid, started, by } = parsed;
8433
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || typeof started !== "number") {
8434
+ return null;
8233
8435
  }
8234
- },
8235
- {
8236
- id: "windsurf",
8237
- name: "Windsurf",
8238
- scope: "home",
8239
- file: join3(".codeium", "windsurf", "mcp_config.json"),
8240
- container: "mcpServers",
8241
- shape: "plain",
8242
- skills: { dir: join3(".windsurf", "skills"), shared: true }
8243
- },
8244
- {
8245
- id: "hermes",
8246
- name: "Hermes",
8247
- scope: "home",
8248
- file: join3(".hermes", "config.yaml"),
8249
- container: "mcp_servers",
8250
- shape: "plain",
8251
- skills: { dir: join3(".hermes", "skills"), shared: true },
8252
- yaml: {
8253
- path: ["mcp_servers", SERVER_KEY],
8254
- entry: (launch) => ({
8255
- command: launch.command,
8256
- args: [...launch.args],
8257
- env: { ...launch.env },
8258
- enabled: true
8259
- })
8436
+ return typeof by === "string" && by !== "" ? { pid, started, by } : { pid, started };
8437
+ } catch {
8438
+ return null;
8439
+ }
8440
+ }
8441
+ async function ownStart() {
8442
+ const said = (await startTimesOf([process4.pid])).get(process4.pid);
8443
+ return said ?? Math.round(Date.now() - process4.uptime() * 1000);
8444
+ }
8445
+ function handSlot(lock, from, to) {
8446
+ let content;
8447
+ try {
8448
+ content = readWhole(lock);
8449
+ } catch {
8450
+ return false;
8451
+ }
8452
+ return content !== null && holderOf(content)?.pid === from && writeWhole(lock, JSON.stringify(to));
8453
+ }
8454
+ function giveBackSlot(lock, pid) {
8455
+ try {
8456
+ if (holderOf(readFileSync4(lock, "utf8"))?.pid === pid) {
8457
+ removeWhole(lock);
8260
8458
  }
8261
- },
8262
- {
8263
- id: "openclaw",
8264
- name: "OpenClaw",
8265
- scope: "home",
8266
- file: join3(".openclaw", "openclaw.json"),
8267
- container: "mcpServers",
8268
- shape: "plain"
8269
- },
8270
- {
8271
- id: "claude-desktop",
8272
- name: "Claude Desktop",
8273
- scope: "home",
8274
- file: {
8275
- win32: { appData: join3("Claude", "claude_desktop_config.json") },
8276
- darwin: join3("Library", "Application Support", "Claude", "claude_desktop_config.json"),
8277
- linux: join3(".config", "Claude", "claude_desktop_config.json")
8278
- },
8279
- container: "mcpServers",
8280
- shape: "plain"
8281
- },
8282
- {
8283
- id: "zeroclaw",
8284
- name: "ZeroClaw",
8285
- scope: "home",
8286
- file: join3(".zeroclaw", "config.toml"),
8287
- container: "mcp.servers",
8288
- shape: "plain",
8289
- toml: {
8290
- header: "[[mcp.servers]]",
8291
- identity: `name = "${SERVER_KEY}"`,
8292
- enable: { header: "[mcp]", line: "enabled = true" },
8293
- block: (launch) => [
8294
- "[[mcp.servers]]",
8295
- `name = "${SERVER_KEY}"`,
8296
- `command = "${launch.command}"`,
8297
- `args = ${tomlArray(launch.args)}`,
8298
- tomlEnv(launch)
8299
- ].join(`
8300
- `)
8459
+ } catch {}
8460
+ }
8461
+
8462
+ // src/error-mode.ts
8463
+ import process5 from "node:process";
8464
+ var NO_FAULT_ERROR_BOX = 2;
8465
+ var cleared = process5.platform !== "win32";
8466
+ async function letWindowsReportCrashes() {
8467
+ if (cleared || !("bun" in process5.versions)) {
8468
+ return;
8469
+ }
8470
+ try {
8471
+ const { dlopen, FFIType } = await import("bun:ffi");
8472
+ const kernel32 = dlopen("kernel32.dll", {
8473
+ GetErrorMode: { args: [], returns: FFIType.u32 },
8474
+ SetErrorMode: { args: [FFIType.u32], returns: FFIType.u32 }
8475
+ });
8476
+ try {
8477
+ const mode = kernel32.symbols.GetErrorMode();
8478
+ kernel32.symbols.SetErrorMode(mode & ~NO_FAULT_ERROR_BOX);
8479
+ cleared = true;
8480
+ } finally {
8481
+ kernel32.close();
8301
8482
  }
8302
- },
8303
- {
8304
- id: "deepcode",
8305
- name: "Deep Code",
8306
- scope: "home",
8307
- file: join3(".deepcode", "settings.json"),
8308
- container: "mcpServers",
8309
- shape: "plain"
8310
- },
8311
- {
8312
- id: "nanobot",
8313
- name: "nanobot",
8314
- scope: "home",
8315
- file: join3(".nanobot", "config.json"),
8316
- container: "mcpServers",
8317
- shape: "plain",
8318
- snippet: (launch) => jsonSnippet(launch, "mcpServers", "plain")
8319
- },
8320
- {
8321
- id: "autohand",
8322
- name: "Autohand",
8323
- scope: "home",
8324
- file: join3(".autohand", "config.json"),
8325
- container: "mcpServers",
8326
- shape: "plain",
8327
- snippet: (launch) => jsonSnippet(launch, "mcpServers", "plain")
8483
+ } catch {}
8484
+ }
8485
+ function crashesReported() {
8486
+ return cleared;
8487
+ }
8488
+
8489
+ // src/game-debugger.ts
8490
+ import { readFileSync as readFileSync6 } from "node:fs";
8491
+ import { createServer } from "node:net";
8492
+ import { join as join6 } from "node:path";
8493
+
8494
+ // src/godot-variant.ts
8495
+ var NIL = 0;
8496
+ var BOOL = 1;
8497
+ var INT = 2;
8498
+ var FLOAT = 3;
8499
+ var STRING = 4;
8500
+ var ARRAY = 28;
8501
+ var FLAG_64 = 1 << 16;
8502
+ var MATH = {
8503
+ 5: { name: "Vector2", count: 2 },
8504
+ 6: { name: "Vector2i", count: 2, ints: true },
8505
+ 7: { name: "Rect2", count: 4 },
8506
+ 8: { name: "Rect2i", count: 4, ints: true },
8507
+ 9: { name: "Vector3", count: 3 },
8508
+ 10: { name: "Vector3i", count: 3, ints: true },
8509
+ 11: { name: "Transform2D", count: 6 },
8510
+ 12: { name: "Vector4", count: 4 },
8511
+ 13: { name: "Vector4i", count: 4, ints: true },
8512
+ 14: { name: "Plane", count: 4 },
8513
+ 15: { name: "Quaternion", count: 4 },
8514
+ 16: { name: "AABB", count: 6 },
8515
+ 17: { name: "Basis", count: 9 },
8516
+ 18: { name: "Transform3D", count: 12 },
8517
+ 19: { name: "Projection", count: 16 }
8518
+ };
8519
+ var PACKED_VECTORS = { 35: 2, 36: 3, 38: 4 };
8520
+
8521
+ class Reader {
8522
+ offset = 0;
8523
+ view;
8524
+ constructor(view) {
8525
+ this.view = view;
8328
8526
  }
8329
- ];
8330
- var OURS = ["GODOT_PATH", "GDHARNESS_PROJECT"];
8331
- function environmentFor(launch, held) {
8332
- const kept = {};
8333
- if (typeof held === "object" && held !== null && !Array.isArray(held)) {
8334
- for (const [name, value] of Object.entries(held)) {
8335
- if (typeof value === "string" && !OURS.includes(name)) {
8336
- kept[name] = value;
8337
- }
8527
+ get remaining() {
8528
+ return this.view.byteLength - this.offset;
8529
+ }
8530
+ need(bytes) {
8531
+ if (bytes < 0 || bytes > this.remaining) {
8532
+ throw new RangeError(`a value runs past the end of its message (${bytes} bytes wanted, ${this.remaining} left)`);
8338
8533
  }
8339
8534
  }
8340
- return { ...kept, ...launch.env };
8535
+ u32() {
8536
+ this.need(4);
8537
+ const value = this.view.getUint32(this.offset, true);
8538
+ this.offset += 4;
8539
+ return value;
8540
+ }
8541
+ i32() {
8542
+ this.need(4);
8543
+ const value = this.view.getInt32(this.offset, true);
8544
+ this.offset += 4;
8545
+ return value;
8546
+ }
8547
+ i64() {
8548
+ this.need(8);
8549
+ const value = Number(this.view.getBigInt64(this.offset, true));
8550
+ this.offset += 8;
8551
+ return value;
8552
+ }
8553
+ u64() {
8554
+ this.need(8);
8555
+ const value = Number(this.view.getBigUint64(this.offset, true));
8556
+ this.offset += 8;
8557
+ return value;
8558
+ }
8559
+ f32() {
8560
+ this.need(4);
8561
+ const value = this.view.getFloat32(this.offset, true);
8562
+ this.offset += 4;
8563
+ return value;
8564
+ }
8565
+ f64() {
8566
+ this.need(8);
8567
+ const value = this.view.getFloat64(this.offset, true);
8568
+ this.offset += 8;
8569
+ return value;
8570
+ }
8571
+ real(wide) {
8572
+ return wide ? this.f64() : this.f32();
8573
+ }
8574
+ bytes(count) {
8575
+ this.need(count);
8576
+ const out = new Uint8Array(this.view.buffer, this.view.byteOffset + this.offset, count);
8577
+ this.offset += count;
8578
+ return out;
8579
+ }
8580
+ string() {
8581
+ const length = this.u32();
8582
+ const text = new TextDecoder().decode(this.bytes(length));
8583
+ this.bytes((4 - length % 4) % 4);
8584
+ return text;
8585
+ }
8341
8586
  }
8342
- function entryFor(shape, launch, held) {
8343
- const before = typeof held === "object" && held !== null && !Array.isArray(held) ? { ...held } : {};
8344
- if (shape === "opencode") {
8587
+ var MAX_DEPTH = 1024;
8588
+ function containerType(reader, kind) {
8589
+ if (kind === 1) {
8590
+ reader.u32();
8591
+ } else if (kind === 2 || kind === 3) {
8592
+ reader.string();
8593
+ }
8594
+ }
8595
+ function decodeValue(reader, depth) {
8596
+ if (depth > MAX_DEPTH) {
8597
+ throw new RangeError("a value nests deeper than the engine allows");
8598
+ }
8599
+ const header = reader.u32();
8600
+ const type = header & 255;
8601
+ const wide = (header & FLAG_64) !== 0;
8602
+ const math = MATH[type];
8603
+ if (math !== undefined) {
8345
8604
  return {
8346
- ...before,
8347
- type: "local",
8348
- command: [launch.command, ...launch.args],
8349
- environment: environmentFor(launch, before["environment"]),
8350
- enabled: true
8605
+ kind: math.name,
8606
+ values: Array.from({ length: math.count }, () => math.ints === true ? reader.i32() : reader.real(wide))
8351
8607
  };
8352
8608
  }
8353
- return {
8354
- ...before,
8355
- ...shape === "typed" ? { type: "stdio" } : {},
8356
- command: launch.command,
8357
- args: [...launch.args],
8358
- env: environmentFor(launch, before["env"])
8359
- };
8360
- }
8361
-
8362
- // src/engine-leaks.ts
8363
- var REFCOUNTED_HELD = "A RefCounted alive at exit is held by a cycle of references (two holding each other, or one " + "holding a lambda that captures it) or by an Object never freed, rather than by a node left out " + "of the tree; a WeakRef on one side of the cycle, or clearing the reference once done, frees it.";
8364
- var WALK_STOPPED_SENTENCE = "The engine's shutdown stopped part way through its scripts (godot#117991, fixed in 4.8 by " + "godot#120976), so it reported as leaked at exit what it never got to free, and the report is the " + "engine's rather than the run's. gdharness holds every script loaded from a file past that walk, " + "which keeps it whole, so this one stopped on a script it could not hold, such as one made in code. " + "A leak of the run's own is not told apart in such a run.";
8365
- var NAME2 = "[\\p{L}_][\\p{L}\\p{N}_]*";
8366
- var EXPORTED = new RegExp(`@export(?!_(?:group|subgroup|category)\\b)\\w*(?:\\([^)]*\\))?\\s+(?:@${NAME2}(?:\\([^)]*\\))?\\s+)*` + `(?:static\\s+)?var\\s+(${NAME2})\\s*:\\s*(?:Array|Dictionary)\\s*\\[([^\\]]*)\\]`, "gu");
8367
-
8368
- // src/junit.ts
8369
- var ESCAPE = String.fromCharCode(27);
8370
- var LOCATION = `'[^'\\n]*' in \\S+:\\d+`;
8371
- var IGNORING_CASE = " \\(ignoring case\\)";
8372
- var PRINTED_ACTUAL = new RegExp(` but was\\n '((?:(?!\\tat ${LOCATION})[\\s\\S])*?)'((?:${ESCAPE}\\[[0-9;]*m)*)(${IGNORING_CASE})?((?:${ESCAPE}\\[[0-9;]*m)*)\\tat (${LOCATION})`, "g");
8373
- var QUOTED_EQUALITY = new RegExp(`^Expecting(?: error message)?:\\n '([\\s\\S]*)'\\n but was\\n '([\\s\\S]*)'(?:${IGNORING_CASE})?\\n\\tat `);
8374
- var COLOUR = new RegExp(`${ESCAPE}\\[([0-9;]*)m`, "g");
8375
- var HOOKS_LEFT = new Map([
8376
- ["before", "before()"],
8377
- ["after", "after()"],
8378
- ["before or after", "before() or after()"]
8379
- ]);
8380
-
8381
- // src/suite-judge.ts
8382
- var TIME = String.raw`(?:\d+h )?(?:\d+min )?(?:\d+s )?\d+ms`;
8383
- var CASE_LINE = new RegExp(String.raw`^\s*(res://.+?\.gd) > (.+?) (STARTED|PASSED|FAILED|FLAKY|SKIPPED|WARNING)(?: \((?:retry \d+|\d+ retries)\))?(?: (${TIME}))?\s*$`);
8384
- var STATISTICS = new RegExp(String.raw`^Statistics:.*\|\s*([A-Z]+)(?:\s+\(.*?\))?\s+(${TIME})\s*$`);
8385
- var WINDOWS_EXITS = new Map([
8386
- [3221225477, "STATUS_ACCESS_VIOLATION"],
8387
- [3221225495, "STATUS_NO_MEMORY"],
8388
- [3221225773, "STATUS_COMMITMENT_LIMIT"],
8389
- [3221225794, "STATUS_DLL_INIT_FAILED"],
8390
- [3221226505, "STATUS_STACK_BUFFER_OVERRUN"],
8391
- [3228369022, "a module that could not be found (VcppException)"],
8392
- [3228369023, "a procedure that could not be found (VcppException)"]
8393
- ]);
8394
- var MEMORY_EXITS = new Set([3221225495, 3221225773]);
8395
- var GDUNIT_FAILED = 100;
8396
- var GDUNIT_ORPHANS = 101;
8397
- var GDUNIT_EXITS = new Map([
8398
- [GDUNIT_FAILED, "gdUnit4's code for a case that failed or errored"],
8399
- [GDUNIT_ORPHANS, "gdUnit4's code for orphan nodes left behind"],
8400
- [103, "gdUnit4's code for a suite that cannot run headless"],
8401
- [104, "gdUnit4's code for a Godot version it does not support"],
8402
- [105, "gdUnit4's code for scripts that did not compile"]
8403
- ]);
8404
- var GODOT_EXITS = new Set([1, 255]);
8405
-
8406
- // src/windows-ask.ts
8407
- var SCRIPT = [
8408
- "[Console]::OutputEncoding = [Text.Encoding]::UTF8",
8409
- "$in = [Console]::In",
8410
- "while ($null -ne ($line = $in.ReadLine())) {",
8411
- ' $parts = $line.Split(" ", 2)',
8412
- " try {",
8413
- " $asked = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($parts[1]))",
8414
- " $said = (& ([ScriptBlock]::Create($asked)) | Out-String -Width 1000000)",
8415
- ' $tag = "OK"',
8416
- " } catch {",
8417
- " $said = $_.Exception.Message",
8418
- ' $tag = "ERR"',
8419
- " }",
8420
- ' [Console]::Out.Write($parts[0] + " " + $tag + " " + [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($said)) + "`n")',
8421
- " [Console]::Out.Flush()",
8422
- "}"
8423
- ].join(`
8424
- `);
8425
-
8426
- // src/engine-launch.ts
8427
- var CONTROL_SEQUENCE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[A-Za-z]`, "g");
8428
- var MEMORY_KEPT = 4 * 1024 ** 3;
8429
- var MEMORY_PER_ENGINE = 1.5 * 1024 ** 3;
8430
-
8431
- // src/process-children.ts
8432
- import { readFileSync as readFileSync3 } from "node:fs";
8433
- import { basename as basename3 } from "node:path";
8434
- async function linuxStartTimes(pids) {
8435
- const began = new Map;
8436
- const hertz = await ticksPerSecond();
8437
- let booted;
8438
- try {
8439
- booted = Number(/^btime\s+(\d+)$/m.exec(readFileSync3("/proc/stat", "utf8"))?.[1] ?? Number.NaN) * 1000;
8440
- } catch {
8441
- return began;
8609
+ const vectorWidth = PACKED_VECTORS[type];
8610
+ if (vectorWidth !== undefined) {
8611
+ const count = reader.u32();
8612
+ return Array.from({ length: count }, () => ({
8613
+ kind: "Vector",
8614
+ values: Array.from({ length: vectorWidth }, () => reader.real(wide))
8615
+ }));
8616
+ }
8617
+ switch (type) {
8618
+ case NIL:
8619
+ return null;
8620
+ case BOOL:
8621
+ return reader.u32() !== 0;
8622
+ case INT:
8623
+ return wide ? reader.i64() : reader.i32();
8624
+ case FLOAT:
8625
+ return wide ? reader.f64() : reader.f32();
8626
+ case STRING:
8627
+ case 21:
8628
+ return reader.string();
8629
+ case 20:
8630
+ return { kind: "Color", values: [reader.f32(), reader.f32(), reader.f32(), reader.f32()] };
8631
+ case 22: {
8632
+ const first = reader.u32();
8633
+ if ((first & 2147483648) === 0) {
8634
+ throw new RangeError("a node path in the format before 3.0");
8635
+ }
8636
+ const names = first & 2147483647;
8637
+ let subnames = reader.u32();
8638
+ const flags = reader.u32();
8639
+ if ((flags & 2) !== 0) {
8640
+ subnames += 1;
8641
+ }
8642
+ return { kind: "NodePath", values: Array.from({ length: names + subnames }, () => reader.string()) };
8643
+ }
8644
+ case 23:
8645
+ return { kind: "RID", values: [reader.u64()] };
8646
+ case 24:
8647
+ if ((header & FLAG_64) === 0) {
8648
+ throw new RangeError("a full object, which this reader does not take");
8649
+ }
8650
+ return { kind: "Object", values: [reader.u64()] };
8651
+ case 25:
8652
+ return { kind: "Callable", values: [] };
8653
+ case 26:
8654
+ return { kind: "Signal", values: [reader.string(), reader.u64()] };
8655
+ case 27: {
8656
+ containerType(reader, header >> 16 & 3);
8657
+ containerType(reader, header >> 18 & 3);
8658
+ const count = reader.u32() & 2147483647;
8659
+ const entries = [];
8660
+ for (let index = 0;index < count; index += 1) {
8661
+ const key = decodeValue(reader, depth + 1);
8662
+ entries.push([key, decodeValue(reader, depth + 1)]);
8663
+ }
8664
+ return { kind: "dictionary", entries };
8665
+ }
8666
+ case ARRAY: {
8667
+ containerType(reader, header >> 16 & 3);
8668
+ const count = reader.u32() & 2147483647;
8669
+ return Array.from({ length: count }, () => decodeValue(reader, depth + 1));
8670
+ }
8671
+ case 29: {
8672
+ const count = reader.u32();
8673
+ const bytes = [...reader.bytes(count)];
8674
+ reader.bytes((4 - count % 4) % 4);
8675
+ return bytes;
8676
+ }
8677
+ case 30:
8678
+ return Array.from({ length: reader.u32() }, () => reader.i32());
8679
+ case 31:
8680
+ return Array.from({ length: reader.u32() }, () => reader.i64());
8681
+ case 32:
8682
+ return Array.from({ length: reader.u32() }, () => reader.f32());
8683
+ case 33:
8684
+ return Array.from({ length: reader.u32() }, () => reader.f64());
8685
+ case 34:
8686
+ return Array.from({ length: reader.u32() }, () => reader.string().replace(/\0$/, ""));
8687
+ case 37:
8688
+ return Array.from({ length: reader.u32() }, () => ({
8689
+ kind: "Color",
8690
+ values: [reader.f32(), reader.f32(), reader.f32(), reader.f32()]
8691
+ }));
8692
+ default:
8693
+ throw new RangeError(`a value of type ${type}, which Godot 4.7 does not have`);
8442
8694
  }
8443
- if (hertz === null || Number.isNaN(booted)) {
8444
- return began;
8695
+ }
8696
+ function decodeVariant(bytes) {
8697
+ const reader = new Reader(new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength));
8698
+ const value = decodeValue(reader, 0);
8699
+ if (reader.remaining !== 0) {
8700
+ throw new RangeError(`${reader.remaining} bytes left over after the value`);
8445
8701
  }
8446
- for (const pid of pids) {
8447
- let stat;
8448
- try {
8449
- stat = readFileSync3(`/proc/${String(pid)}/stat`, "utf8");
8450
- } catch {
8451
- continue;
8702
+ return value;
8703
+ }
8704
+ function pushU32(out, value) {
8705
+ out.push(value & 255, value >>> 8 & 255, value >>> 16 & 255, value >>> 24 & 255);
8706
+ }
8707
+ function encodeInto(out, value) {
8708
+ if (value === null) {
8709
+ pushU32(out, NIL);
8710
+ } else if (typeof value === "boolean") {
8711
+ pushU32(out, BOOL);
8712
+ pushU32(out, value ? 1 : 0);
8713
+ } else if (typeof value === "number") {
8714
+ const view = new DataView(new ArrayBuffer(8));
8715
+ if (Number.isInteger(value) && value >= -2147483648 && value <= 2147483647) {
8716
+ pushU32(out, INT);
8717
+ view.setInt32(0, value, true);
8718
+ out.push(...new Uint8Array(view.buffer, 0, 4));
8719
+ } else if (Number.isInteger(value)) {
8720
+ pushU32(out, INT | FLAG_64);
8721
+ view.setBigInt64(0, BigInt(value), true);
8722
+ out.push(...new Uint8Array(view.buffer));
8723
+ } else {
8724
+ pushU32(out, FLOAT | FLAG_64);
8725
+ view.setFloat64(0, value, true);
8726
+ out.push(...new Uint8Array(view.buffer));
8452
8727
  }
8453
- const ticks = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19]);
8454
- if (Number.isInteger(ticks) && ticks >= 0) {
8455
- began.set(pid, booted + Math.round(ticks * 1000 / hertz));
8728
+ } else if (typeof value === "string") {
8729
+ const bytes = new TextEncoder().encode(value);
8730
+ pushU32(out, STRING);
8731
+ pushU32(out, bytes.length);
8732
+ out.push(...bytes, ...Array.from({ length: (4 - bytes.length % 4) % 4 }, () => 0));
8733
+ } else {
8734
+ pushU32(out, ARRAY);
8735
+ pushU32(out, value.length);
8736
+ for (const element of value) {
8737
+ encodeInto(out, element);
8456
8738
  }
8457
8739
  }
8458
- return began;
8459
8740
  }
8460
- async function linuxStartEarliest() {
8461
- return 1000 + Math.ceil(1000 / (await ticksPerSecond() ?? 100));
8741
+ function encodeVariant(value) {
8742
+ const out = [];
8743
+ encodeInto(out, value);
8744
+ return Uint8Array.from(out);
8462
8745
  }
8463
- var ticksAnswered;
8464
- async function ticksPerSecond() {
8465
- if (ticksAnswered !== undefined) {
8466
- return ticksAnswered;
8467
- }
8468
- try {
8469
- const hertz = Number((await runHidden("getconf", ["CLK_TCK"], { timeout: ASK_TIMEOUT_MS })).stdout.trim());
8470
- if (Number.isInteger(hertz) && hertz > 0) {
8471
- ticksAnswered = hertz;
8472
- return hertz;
8473
- }
8474
- } catch {}
8475
- return null;
8746
+ var MAIN_THREAD = 1;
8747
+ function framedCommand(message, data, thread = MAIN_THREAD) {
8748
+ const body = encodeVariant([message, thread, data]);
8749
+ const framed = new Uint8Array(body.length + 4);
8750
+ new DataView(framed.buffer).setUint32(0, body.length, true);
8751
+ framed.set(body, 4);
8752
+ return framed;
8476
8753
  }
8477
- var ASK_TIMEOUT_MS = 15000;
8478
- var ONE_SHOT_OPTIONS = [
8479
- "--import",
8480
- "--quit",
8481
- "--quit-after",
8482
- "--export-release",
8483
- "--export-debug",
8484
- "--export-pack",
8485
- "--export-patch",
8486
- "--doctool",
8487
- "--gdscript-docs",
8488
- "--script",
8489
- "-s",
8490
- "--check-only",
8491
- "--build-solutions",
8492
- "--dump-gdextension-interface",
8493
- "--dump-extension-api",
8494
- "--dump-extension-api-with-docs",
8495
- "--validate-extension-api",
8496
- "--convert-3to4",
8497
- "--validate-conversion-3to4"
8498
- ];
8499
- function readCommandLine(command) {
8500
- const all = commandWords(command);
8501
- const separator = all.findIndex((word) => word === "--" || word === "++");
8502
- const words = separator === -1 ? all : all.slice(0, separator);
8503
- const executable = words[0] === undefined ? "" : basename3(words[0].replaceAll("\\", "/"));
8504
- const at = words.indexOf("--path");
8505
- let projectPath = null;
8506
- if (at !== -1 && at + 1 < words.length) {
8507
- const taken = [];
8508
- for (const word of words.slice(at + 1)) {
8509
- if (taken.length > 0 && (word.startsWith("-") || /^[a-z]+:\/\//.test(word))) {
8754
+ var MOST_MESSAGE_BYTES = (8 << 20) + 4;
8755
+
8756
+ class DebuggerStream {
8757
+ pending = new Uint8Array(0);
8758
+ push(chunk) {
8759
+ const joined = new Uint8Array(this.pending.length + chunk.length);
8760
+ joined.set(this.pending, 0);
8761
+ joined.set(chunk, this.pending.length);
8762
+ const out = [];
8763
+ let at = 0;
8764
+ while (joined.length - at >= 4) {
8765
+ const length = new DataView(joined.buffer, joined.byteOffset + at, 4).getUint32(0, true);
8766
+ if (length > MOST_MESSAGE_BYTES) {
8767
+ throw new RangeError(`a message of ${length} bytes, more than the engine sends`);
8768
+ }
8769
+ if (joined.length - at - 4 < length) {
8510
8770
  break;
8511
8771
  }
8512
- taken.push(word);
8772
+ out.push(readMessage(joined.subarray(at + 4, at + 4 + length)));
8773
+ at += 4 + length;
8513
8774
  }
8514
- projectPath = taken.join(" ").replaceAll("%20", " ");
8775
+ this.pending = joined.slice(at);
8776
+ return out;
8515
8777
  }
8516
- return {
8517
- executable,
8518
- projectPath,
8519
- editor: words.some((word) => word === "-e" || word === "--editor"),
8520
- oneShot: words.find((word) => ONE_SHOT_OPTIONS.includes(word)) ?? null
8521
- };
8522
8778
  }
8523
- function commandWords(command) {
8524
- const words = [];
8525
- let word = "";
8526
- let quoted = false;
8527
- let started = false;
8528
- for (const char of command) {
8529
- if (char === '"') {
8530
- quoted = !quoted;
8531
- started = true;
8532
- } else if (!quoted && /\s/.test(char)) {
8533
- if (started) {
8534
- words.push(word);
8535
- word = "";
8536
- started = false;
8537
- }
8538
- } else {
8539
- word += char;
8540
- started = true;
8541
- }
8779
+ function readMessage(body) {
8780
+ let value;
8781
+ try {
8782
+ value = decodeVariant(body);
8783
+ } catch (error) {
8784
+ return { unreadable: error instanceof Error ? error.message : String(error) };
8542
8785
  }
8543
- if (started) {
8544
- words.push(word);
8786
+ if (!Array.isArray(value) || value.length !== 3) {
8787
+ return { unreadable: "a message that is not [message, thread, data]" };
8545
8788
  }
8546
- return words;
8789
+ const [message, thread, data] = value;
8790
+ if (typeof message !== "string" || typeof thread !== "number" || !Array.isArray(data)) {
8791
+ return { unreadable: "a message whose name, thread or data is the wrong type" };
8792
+ }
8793
+ return { message, thread, data };
8794
+ }
8795
+
8796
+ // src/setup.ts
8797
+ import { dirname as dirname3, join as join5 } from "node:path";
8798
+ import { fileURLToPath } from "node:url";
8799
+
8800
+ // src/dictionary.ts
8801
+ function emptyRecord() {
8802
+ return Object.create(null);
8547
8803
  }
8548
8804
 
8549
8805
  // src/game-log.ts
@@ -8653,16 +8909,16 @@ var IMPORTABLE = new Set(IMPORTABLE_EXTENSIONS);
8653
8909
  var hashes = new Map;
8654
8910
 
8655
8911
  // src/scratch.ts
8656
- import { existsSync as existsSync2, mkdtempSync as mkdtempSync2, readdirSync, rmSync as rmSync3 } from "node:fs";
8657
- import { tmpdir as tmpdir2 } from "node:os";
8912
+ import { existsSync as existsSync2, mkdtempSync as mkdtempSync2, readdirSync as readdirSync2, rmSync as rmSync4 } from "node:fs";
8913
+ import { tmpdir as tmpdir3 } from "node:os";
8658
8914
  import { join as join4 } from "node:path";
8659
- import process4 from "node:process";
8915
+ import process6 from "node:process";
8660
8916
  var SWEPT = { recursive: true, force: true };
8661
8917
  var AGAIN_AFTER_MS = [250, 500, 1000, 2000, 4000, 8000, 16000];
8662
8918
  var waiting = new Set;
8663
8919
  var atExit = new Set;
8664
8920
  function scratchDirectory(kind) {
8665
- return mkdtempSync2(join4(tmpdir2(), `gdharness-${kind}-${process4.pid}-`));
8921
+ return mkdtempSync2(join4(tmpdir3(), `gdharness-${kind}-${process6.pid}-`));
8666
8922
  }
8667
8923
  function discardWith(remove, paths) {
8668
8924
  const left = [];
@@ -8686,7 +8942,7 @@ function discard(...paths) {
8686
8942
  return left;
8687
8943
  }
8688
8944
  function remove(path) {
8689
- rmSync3(path, SWEPT);
8945
+ rmSync4(path, SWEPT);
8690
8946
  }
8691
8947
  function tryAgain(path, attempt) {
8692
8948
  if (attempt === 0) {
@@ -8720,7 +8976,7 @@ function lastTryOnExit() {
8720
8976
  return;
8721
8977
  }
8722
8978
  lastTryWatched = true;
8723
- process4.once("exit", () => {
8979
+ process6.once("exit", () => {
8724
8980
  for (const path of [...waiting, ...atExit]) {
8725
8981
  try {
8726
8982
  remove(path);
@@ -8847,7 +9103,7 @@ function parseIniLikeValue(value) {
8847
9103
  }
8848
9104
 
8849
9105
  // src/server-version.ts
8850
- import { readFileSync as readFileSync4 } from "node:fs";
9106
+ import { readFileSync as readFileSync5 } from "node:fs";
8851
9107
 
8852
9108
  // src/update-check.ts
8853
9109
  var CACHE_MS = 10 * 60 * 1000;
@@ -8858,7 +9114,7 @@ var MAX_RETRY_MS = 60 * 60 * 1000;
8858
9114
  var DEBUG_MODE = process.env["GDHARNESS_DEBUG"] === "1";
8859
9115
  var SERVER_VERSION = (() => {
8860
9116
  try {
8861
- const pkg = JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8"));
9117
+ const pkg = JSON.parse(readFileSync5(new URL("../package.json", import.meta.url), "utf8"));
8862
9118
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
8863
9119
  } catch {
8864
9120
  return "0.0.0";
@@ -8884,7 +9140,7 @@ var RUNTIME_AUTOLOAD2 = `${RUNTIME_ADDON}runtime_autoload.gd`;
8884
9140
  var frameEnd = null;
8885
9141
  function frameEndLine() {
8886
9142
  if (frameEnd === null) {
8887
- const lines = readFileSync5(join6(shippedAddonsDirectory(), "gdharness_runtime", "runtime_autoload.gd"), "utf8").split(`
9143
+ const lines = readFileSync6(join6(shippedAddonsDirectory(), "gdharness_runtime", "runtime_autoload.gd"), "utf8").split(`
8888
9144
  `);
8889
9145
  const declared = lines.findIndex((line) => line.startsWith("func _notification("));
8890
9146
  if (declared === -1) {
@@ -9008,6 +9264,7 @@ class GameDebugger {
9008
9264
  onClosed = [];
9009
9265
  output = [];
9010
9266
  outputDropped = 0;
9267
+ expressionErrors = [];
9011
9268
  queue = Promise.resolve();
9012
9269
  server;
9013
9270
  port;
@@ -9132,6 +9389,14 @@ class GameDebugger {
9132
9389
  this.outputDropped += over;
9133
9390
  }
9134
9391
  }
9392
+ } else if (received.message === "error") {
9393
+ const [, , , , file, , , error, described] = received.data;
9394
+ if (file === "core/math/expression.cpp" && this.expressionErrors.length < 10) {
9395
+ const why = typeof described === "string" && described !== "" ? described : error;
9396
+ if (typeof why === "string") {
9397
+ this.expressionErrors.push(why);
9398
+ }
9399
+ }
9135
9400
  }
9136
9401
  const at = this.waiters.findIndex((waiter) => waiter.wanted(received));
9137
9402
  if (at !== -1) {
@@ -9478,6 +9743,7 @@ class GameDebugger {
9478
9743
  };
9479
9744
  }
9480
9745
  this.forget("evaluation_return");
9746
+ this.expressionErrors.length = 0;
9481
9747
  this.send("evaluate", [expression, frame], held.thread);
9482
9748
  const answered = await this.next(GameDebugger.named("evaluation_return"), EVALUATE_MS);
9483
9749
  if (answered === null || !("data" in answered)) {
@@ -9486,16 +9752,24 @@ class GameDebugger {
9486
9752
  };
9487
9753
  }
9488
9754
  const [, , type, value, hint] = answered.data;
9489
- return {
9755
+ const answer = {
9490
9756
  held: this.heldState(),
9491
9757
  frame,
9492
9758
  expression,
9493
9759
  value: shownValue(value ?? null, typeof hint === "string" ? hint : ""),
9494
- type: typeNamed(type, hint),
9495
- ...value === null || value === undefined ? {
9496
- note: "null is also what an expression comes to when it does not parse or fails as it runs. The game prints why as an error, which editor_output reads, since its debugger hands errors over apart from the console debug_state output keeps."
9497
- } : {}
9760
+ type: typeNamed(type, hint)
9498
9761
  };
9762
+ if (value !== null && value !== undefined) {
9763
+ return answer;
9764
+ }
9765
+ if (!await this.taken()) {
9766
+ return {
9767
+ ...answer,
9768
+ note: "null is also what an expression comes to when it does not parse or fails as it runs, and the game did not answer the question asked after it to say whether this one failed. editor_output has any error it printed."
9769
+ };
9770
+ }
9771
+ const why = this.expressionErrors.join(" ");
9772
+ return why === "" ? answer : { ...answer, failed: why };
9499
9773
  }
9500
9774
  async taken() {
9501
9775
  this.forget("scene:inspect_object");
@@ -9518,11 +9792,11 @@ class GameDebugger {
9518
9792
 
9519
9793
  // src/keeper-channel.ts
9520
9794
  import { randomUUID } from "node:crypto";
9521
- import { mkdtempSync as mkdtempSync3, rmSync as rmSync4 } from "node:fs";
9795
+ import { mkdtempSync as mkdtempSync3, rmSync as rmSync5 } from "node:fs";
9522
9796
  import { connect, createServer as createServer2 } from "node:net";
9523
- import { tmpdir as tmpdir3 } from "node:os";
9797
+ import { tmpdir as tmpdir4 } from "node:os";
9524
9798
  import { join as join7 } from "node:path";
9525
- import process5 from "node:process";
9799
+ import process7 from "node:process";
9526
9800
  var DEBUG_PREFIX = "debug ";
9527
9801
  function endedOrWhyNot(end) {
9528
9802
  try {
@@ -9549,7 +9823,7 @@ function errorText(error) {
9549
9823
  async function listenForTheServer(end, debug) {
9550
9824
  let directory = null;
9551
9825
  try {
9552
- directory = process5.platform === "win32" ? null : mkdtempSync3(join7(tmpdir3(), "gdharness-keeper-"));
9826
+ directory = process7.platform === "win32" ? null : mkdtempSync3(join7(tmpdir4(), "gdharness-keeper-"));
9553
9827
  const address = directory === null ? `\\\\.\\pipe\\gdharness-keeper-${randomUUID()}` : join7(directory, "keeper.sock");
9554
9828
  const server = createServer2((socket) => {
9555
9829
  socket.setEncoding("utf8");
@@ -9586,13 +9860,13 @@ async function listenForTheServer(end, debug) {
9586
9860
  close: () => {
9587
9861
  server.close();
9588
9862
  if (made !== null) {
9589
- rmSync4(made, { recursive: true, force: true });
9863
+ rmSync5(made, { recursive: true, force: true });
9590
9864
  }
9591
9865
  }
9592
9866
  };
9593
9867
  } catch {
9594
9868
  if (directory !== null) {
9595
- rmSync4(directory, { recursive: true, force: true });
9869
+ rmSync5(directory, { recursive: true, force: true });
9596
9870
  }
9597
9871
  return null;
9598
9872
  }
@@ -9643,11 +9917,11 @@ function readLaunched(printed) {
9643
9917
 
9644
9918
  // src/run-record.ts
9645
9919
  import { createHash as createHash2 } from "node:crypto";
9646
- import { mkdirSync, openSync as openSync2, readdirSync as readdirSync2, readFileSync as readFileSync6, rmSync as rmSync5, statSync as statSync3 } from "node:fs";
9920
+ import { mkdirSync as mkdirSync2, openSync as openSync3, readdirSync as readdirSync3, readFileSync as readFileSync7, rmSync as rmSync6, statSync as statSync4 } from "node:fs";
9647
9921
  import { basename as basename4, join as join9, resolve as resolve4 } from "node:path";
9648
9922
 
9649
9923
  // src/runtime-client.ts
9650
- import { tmpdir as tmpdir4 } from "node:os";
9924
+ import { tmpdir as tmpdir5 } from "node:os";
9651
9925
  import { dirname as dirname4, join as join8, resolve as resolve3 } from "node:path";
9652
9926
 
9653
9927
  // src/launch.ts
@@ -9693,7 +9967,7 @@ function runtimeDirectory(variables = process.env) {
9693
9967
  return explicit;
9694
9968
  }
9695
9969
  const perUser = envValue("XDG_RUNTIME_DIR", variables);
9696
- return join8(perUser ?? tmpdir4(), "gdharness");
9970
+ return join8(perUser ?? tmpdir5(), "gdharness");
9697
9971
  }
9698
9972
  var ERROR_REPORT_KEEP_MS = 60 * 60 * 1000;
9699
9973
  var verdicts = new Map;
@@ -9714,7 +9988,7 @@ function runRecordPath(projectPath, directory = runtimeDirectory()) {
9714
9988
  return join9(directory, "runs", `note-${projectKey(projectPath)}.json`);
9715
9989
  }
9716
9990
  function writeRunRecord(record) {
9717
- mkdirSync(runsDirectory(), { recursive: true });
9991
+ mkdirSync2(runsDirectory(), { recursive: true });
9718
9992
  const path = runRecordPath(record.projectPath);
9719
9993
  if (!writeWhole(path, JSON.stringify(record, null, 2))) {
9720
9994
  throw new Error(`the run note could not be written at ${path}`);
@@ -9739,7 +10013,7 @@ function recordRunEnded(projectPath, pid, ending) {
9739
10013
  function recordAt(path) {
9740
10014
  let parsed;
9741
10015
  try {
9742
- parsed = JSON.parse(readFileSync6(path, "utf8"));
10016
+ parsed = JSON.parse(readFileSync7(path, "utf8"));
9743
10017
  } catch {
9744
10018
  return null;
9745
10019
  }
@@ -10178,7 +10452,7 @@ async function startThroughHelper(spec, output, stopFile) {
10178
10452
  step: stepFile
10179
10453
  });
10180
10454
  const envChanges = spec.desktop === undefined ? spec.envChanges : { ...spec.envChanges, [DESKTOP_VARIABLE]: spec.desktop };
10181
- const started = await startDirectly({ ...spec, command: wrapper.command, args: wrapper.args, envChanges }, output, true, false, "ignore", tmpdir5());
10455
+ const started = await startDirectly({ ...spec, command: wrapper.command, args: wrapper.args, envChanges }, output, true, false, "ignore", tmpdir6());
10182
10456
  if ("error" in started) {
10183
10457
  return started;
10184
10458
  }
@@ -10187,7 +10461,7 @@ async function startThroughHelper(spec, output, stopFile) {
10187
10461
  exited.now = true;
10188
10462
  });
10189
10463
  for (let waited = 0;waited < DESKTOP_START_MS; waited += 50) {
10190
- const pid = existsSync3(pidFile) ? Number(readFileSync7(pidFile, "utf8").trim()) : Number.NaN;
10464
+ const pid = existsSync3(pidFile) ? Number(readFileSync8(pidFile, "utf8").trim()) : Number.NaN;
10191
10465
  if (Number.isInteger(pid) && pid > 0) {
10192
10466
  return { child: started.child, pid };
10193
10467
  }
@@ -10218,7 +10492,7 @@ function profiledBy(path, projectPath) {
10218
10492
  };
10219
10493
  let projectGodot = null;
10220
10494
  try {
10221
- projectGodot = readFileSync7(join10(projectPath, "project.godot"), "utf8");
10495
+ projectGodot = readFileSync8(join10(projectPath, "project.godot"), "utf8");
10222
10496
  } catch {}
10223
10497
  const observer = new ProfileObserver((totals) => {
10224
10498
  const first = latest === null;
@@ -10319,19 +10593,20 @@ async function keep() {
10319
10593
  }
10320
10594
  const profiled = run?.profile === undefined ? null : profiledBy(run.profile, run.projectPath);
10321
10595
  const debuggerPeer = run === undefined || run.debugged === false ? null : await GameDebugger.open(run.breakpoints ?? [], profiled === null ? [] : [profiled.observer]);
10322
- const exitNotes = run === undefined ? null : mkdtempSync4(join10(tmpdir5(), "gdharness-exit-"));
10596
+ const exitNotes = run === undefined ? null : mkdtempSync4(join10(tmpdir6(), "gdharness-exit-"));
10323
10597
  const noted = exitNotes === null ? spec : { ...spec, envChanges: { ...spec.envChanges, [EXIT_NOTES_VARIABLE]: exitNotes } };
10324
10598
  const toStart = debuggerPeer === null ? noted : { ...noted, args: withEngineArguments(noted.args, debuggerPeer.engineArguments) };
10325
- const transcript = run === undefined ? "ignore" : openSync3(run.transcript, "a");
10326
- const own = run !== undefined && throughTheHelper(spec) ? mkdtempSync4(join10(tmpdir5(), "gdharness-stop-")) : null;
10599
+ const transcript = run === undefined ? "ignore" : openSync4(run.transcript, "a");
10600
+ const own = run !== undefined && throughTheHelper(spec) ? mkdtempSync4(join10(tmpdir6(), "gdharness-stop-")) : null;
10327
10601
  const stopFile = own === null ? "" : join10(own, "stop");
10328
10602
  let started;
10603
+ const keeperStarted = run?.slot === undefined ? undefined : ownStart();
10329
10604
  const spawnedAt = Date.now();
10330
10605
  try {
10331
10606
  started = await startDetached(toStart, transcript, run === undefined, stopFile);
10332
10607
  } finally {
10333
10608
  if (transcript !== "ignore") {
10334
- closeSync2(transcript);
10609
+ closeSync3(transcript);
10335
10610
  }
10336
10611
  }
10337
10612
  if ("error" in started) {
@@ -10382,6 +10657,14 @@ async function keep() {
10382
10657
  process.stdout.end(`pid ${pid}
10383
10658
  `, resolve);
10384
10659
  });
10660
+ const slot = run?.slot;
10661
+ if (slot !== undefined && keeperStarted !== undefined) {
10662
+ handSlot(slot.lock, slot.from, {
10663
+ pid: process.pid,
10664
+ started: await keeperStarted,
10665
+ by: `${slot.by}, through the keeper process that runs it`
10666
+ });
10667
+ }
10385
10668
  let { code, signal } = await exited;
10386
10669
  let exitedAt = Date.now();
10387
10670
  let note = record === null ? null : listening === null ? record : { ...record, keeper: listening.address };
@@ -10442,11 +10725,14 @@ async function keep() {
10442
10725
  notTold
10443
10726
  });
10444
10727
  }
10728
+ if (slot !== undefined) {
10729
+ giveBackSlot(slot.lock, process.pid);
10730
+ }
10445
10731
  debuggerPeer?.close();
10446
10732
  listening?.close();
10447
10733
  for (const made of [own, exitNotes]) {
10448
10734
  if (made !== null) {
10449
- rmSync6(made, { recursive: true, force: true });
10735
+ rmSync7(made, { recursive: true, force: true });
10450
10736
  }
10451
10737
  }
10452
10738
  process.exit(0);
@@ -10457,7 +10743,7 @@ function restartSaid(notes, pid) {
10457
10743
  return null;
10458
10744
  }
10459
10745
  try {
10460
- const said = JSON.parse(readFileSync7(join10(notes, `exit-${String(pid)}.json`), "utf8"));
10746
+ const said = JSON.parse(readFileSync8(join10(notes, `exit-${String(pid)}.json`), "utf8"));
10461
10747
  return typeof said === "object" && said !== null && "restart" in said && typeof said.restart === "boolean" ? said.restart : null;
10462
10748
  } catch {
10463
10749
  return null;