windows-development-sandbox 1.4.0 → 1.4.1

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 (73) hide show
  1. package/.sandbox-config.example.json +2 -1
  2. package/config.example.json +1 -0
  3. package/dist/cli/boot-progress.d.ts +22 -0
  4. package/dist/cli/boot-progress.js +59 -0
  5. package/dist/cli/commands/launch.d.ts +5 -2
  6. package/dist/cli/commands/launch.js +77 -22
  7. package/dist/cli/commands/maintenance.js +36 -3
  8. package/dist/cli/commands/project.d.ts +10 -6
  9. package/dist/cli/commands/project.js +42 -3
  10. package/dist/cli/commands/restart.js +17 -7
  11. package/dist/cli/commands/sessions.js +2 -1
  12. package/dist/cli/manifest.js +1 -1
  13. package/dist/cli/progress.d.ts +15 -0
  14. package/dist/cli/progress.js +52 -0
  15. package/dist/core/config.js +1 -1
  16. package/dist/core/local-config.d.ts +16 -1
  17. package/dist/core/local-config.js +15 -9
  18. package/dist/core/project-policy.d.ts +50 -3
  19. package/dist/core/project-policy.js +90 -4
  20. package/dist/core/schema/config.d.ts +3 -2
  21. package/dist/core/schema/config.js +1 -1
  22. package/dist/core/schema/projects.d.ts +1 -0
  23. package/dist/core/schema/projects.js +1 -0
  24. package/dist/core/schema/sandbox.d.ts +1 -1
  25. package/dist/core/schema/sandbox.js +1 -1
  26. package/dist/core/types.d.ts +18 -1
  27. package/dist/core/workspace.d.ts +21 -4
  28. package/dist/core/workspace.js +87 -26
  29. package/dist/guest/env.d.ts +6 -2
  30. package/dist/guest/env.js +15 -3
  31. package/dist/guest/main.js +5 -1
  32. package/dist/guest/toolchains/index.js +29 -3
  33. package/dist/host/channel.d.ts +2 -2
  34. package/dist/host/channel.js +3 -2
  35. package/dist/host/generate.d.ts +1 -1
  36. package/dist/host/generate.js +13 -6
  37. package/dist/host/join.d.ts +7 -0
  38. package/dist/host/join.js +23 -9
  39. package/dist/host/launch/mappings.d.ts +7 -2
  40. package/dist/host/launch/mappings.js +64 -18
  41. package/dist/host/launch/projects.d.ts +2 -0
  42. package/dist/host/launch/projects.js +11 -5
  43. package/dist/host/projects.d.ts +25 -2
  44. package/dist/host/projects.js +54 -5
  45. package/dist/runtime/daemon.js +71 -9
  46. package/dist/runtime/db.js +33 -3
  47. package/dist/runtime/main.js +191 -30
  48. package/dist/runtime/netproxy.js +15 -3
  49. package/dist/runtime/view.js +262 -128
  50. package/dist/selftest/channel.test.js +38 -2
  51. package/dist/selftest/fixtures.d.ts +2 -1
  52. package/dist/selftest/index.js +2 -1
  53. package/dist/selftest/live-terminal.test.d.ts +2 -0
  54. package/dist/selftest/live-terminal.test.js +169 -0
  55. package/dist/selftest/local-config.test.js +28 -3
  56. package/dist/selftest/mirror.test.js +36 -7
  57. package/dist/selftest/projects.test.js +76 -7
  58. package/dist/shared/console-input.d.ts +11 -0
  59. package/dist/shared/console-input.js +61 -0
  60. package/dist/shared/download.d.ts +3 -0
  61. package/dist/shared/download.js +25 -2
  62. package/dist/shared/guest-paths.d.ts +27 -0
  63. package/dist/shared/guest-paths.js +68 -0
  64. package/dist/shared/projects.d.ts +27 -4
  65. package/dist/shared/projects.js +72 -13
  66. package/dist/shared/pty-client.d.ts +17 -0
  67. package/dist/shared/pty-client.js +65 -8
  68. package/dist/shared/schema.d.ts +24 -0
  69. package/dist/shared/schema.js +12 -0
  70. package/package.json +1 -1
  71. package/sandbox/ptyhost.cs +60 -3
  72. package/schemas/config.schema.json +15 -1
  73. package/schemas/sandbox-config.schema.json +4 -3
@@ -18,7 +18,8 @@
18
18
  // 20 STATUS [viewers:2][remoteViewers:2] viewers heard from recently (remote = not loopback, i.e. the PC)
19
19
  // 21 CONSOLE utf8 "conpty.dll" or "windows", sent after HELLO. With conpty.dll the output is passed
20
20
  // through (real scrollback, no repaint on resize): a viewer that joins later is sent the
21
- // output since the last screen clear instead, and must not clear its screen on a resize.
21
+ // terminal modes in force (mouse tracking ...) and the output since the last screen clear
22
+ // instead, and must not clear its screen on a resize.
22
23
  using System;
23
24
  using System.Collections.Concurrent;
24
25
  using System.Collections.Generic;
@@ -265,7 +266,12 @@ sealed class ClientHub
265
266
  // Taken and queued under the replay lock, so no output falls between the replay and the live stream.
266
267
  lock (replayGate)
267
268
  {
268
- if (Native.ConsoleKind.StartsWith("conpty") && replay.Length > 0) Send(client, Frame.Output, replay.ToArray());
269
+ if (Native.ConsoleKind.StartsWith("conpty"))
270
+ {
271
+ byte[] prefix = ModePrefix();
272
+ if (prefix.Length > 0) Send(client, Frame.Output, prefix);
273
+ if (replay.Length > 0) Send(client, Frame.Output, replay.ToArray());
274
+ }
269
275
  lock (gate) clients.Add(client);
270
276
  }
271
277
  TakeOwnership(client);
@@ -319,6 +325,7 @@ sealed class ClientHub
319
325
  void Record(byte[] output)
320
326
  {
321
327
  if (!Native.ConsoleKind.StartsWith("conpty")) return;
328
+ TrackModes(output);
322
329
  int clear = LastClear(output);
323
330
  if (clear >= 0) { replay.SetLength(0); replay.Write(output, clear, output.Length - clear); }
324
331
  else replay.Write(output, 0, output.Length);
@@ -330,6 +337,56 @@ sealed class ClientHub
330
337
  if (from + 1 < all.Length) replay.Write(all, from + 1, all.Length - from - 1);
331
338
  }
332
339
 
340
+ /**
341
+ * Terminal modes the program (or conpty.dll) turned on that a late viewer's terminal must have too, or its clicks and
342
+ * wheel never reach the program: mouse tracking (1000, 1002, 1003, 1005, 1006, 1015), focus events (1004), bracketed
343
+ * paste (2004), win32 input (9001), application cursor keys (1), cursor visibility (25), the alternate screen (1049).
344
+ * The replay is cut at the last screen clear, which may come after the program turned them on.
345
+ */
346
+ static readonly int[] ReplayedModes = { 1, 25, 1000, 1002, 1003, 1004, 1005, 1006, 1015, 1049, 2004, 9001 };
347
+ readonly Dictionary<int, bool> modes = new Dictionary<int, bool>();
348
+ /** DECSET / DECRST parser, kept across chunks: 0 text, 1 after ESC, 2 after ESC[, 3 in ESC[? parameters. */
349
+ int modeState;
350
+ readonly StringBuilder modeParams = new StringBuilder();
351
+
352
+ void TrackModes(byte[] output)
353
+ {
354
+ foreach (byte b in output)
355
+ {
356
+ if (b == 0x1b) { modeState = 1; continue; }
357
+ switch (modeState)
358
+ {
359
+ case 1: modeState = b == (byte)'[' ? 2 : 0; break;
360
+ case 2: modeState = b == (byte)'?' ? 3 : 0; modeParams.Length = 0; break;
361
+ case 3:
362
+ if ((b >= (byte)'0' && b <= (byte)'9') || b == (byte)';') { if (modeParams.Length < 64) modeParams.Append((char)b); }
363
+ else
364
+ {
365
+ if (b == (byte)'h' || b == (byte)'l')
366
+ foreach (string p in modeParams.ToString().Split(';'))
367
+ {
368
+ int mode;
369
+ if (int.TryParse(p, out mode) && Array.IndexOf(ReplayedModes, mode) >= 0) modes[mode] = b == (byte)'h';
370
+ }
371
+ modeState = 0;
372
+ }
373
+ break;
374
+ }
375
+ }
376
+ }
377
+
378
+ /** The modes to set before the replay, e.g. ESC[?1000h ESC[?1006h. */
379
+ byte[] ModePrefix()
380
+ {
381
+ var text = new StringBuilder();
382
+ foreach (int mode in ReplayedModes)
383
+ {
384
+ bool on;
385
+ if (modes.TryGetValue(mode, out on)) text.Append("\x1b[?" + mode + (on ? "h" : "l"));
386
+ }
387
+ return Encoding.ASCII.GetBytes(text.ToString());
388
+ }
389
+
333
390
  /** Where the last ESC[2J (clear screen) or ESC c (reset) starts in `data`, or -1. */
334
391
  static int LastClear(byte[] data)
335
392
  {
@@ -364,7 +421,7 @@ sealed class ClientHub
364
421
  }
365
422
 
366
423
  /** A restarted program starts on a clean screen (viewers clear theirs on RESTARTING). */
367
- public void ResetReplay() { lock (replayGate) replay.SetLength(0); }
424
+ public void ResetReplay() { lock (replayGate) { replay.SetLength(0); modes.Clear(); modeState = 0; } }
368
425
 
369
426
  public void Notify(byte type)
370
427
  {
@@ -99,8 +99,10 @@
99
99
  "description": "Folder on this PC (relative paths are relative to the config file)."
100
100
  },
101
101
  "guest": {
102
+ "description": "Where it appears inside the sandbox, e.g. C:\\Assets, or on another drive letter (D: to Z:) such as F:\\SteamLibrary\\steamapps\\common\\Game (shown as F: at boot). Leave it out to use the same path as on this PC (a folder inside the project stays where it is in the project). A path the sandbox cannot use (C:\\Windows\\..., C:\\Users\\..., one already in use) goes to C:\\Mapped\\<drive>\\<path> instead, and the launch says so.",
102
103
  "type": "string",
103
- "description": "Where it appears inside the sandbox, e.g. C:\\Assets, or on another drive letter (D: to Z:) such as F:\\SteamLibrary\\steamapps\\common\\Game: mapped to C:\\Mirror\\F\\... and shown as F: (subst) at boot."
104
+ "minLength": 1,
105
+ "maxLength": 1024
104
106
  },
105
107
  "readOnly": {
106
108
  "type": "boolean",
@@ -1449,6 +1451,18 @@
1449
1451
  },
1450
1452
  "description": "Parent folders you said no to (not asked again)."
1451
1453
  },
1454
+ "allowOutsideMappings": {
1455
+ "anyOf": [
1456
+ {
1457
+ "type": "string",
1458
+ "const": "ask"
1459
+ },
1460
+ {
1461
+ "type": "boolean"
1462
+ }
1463
+ ],
1464
+ "description": "A project's .sandbox-config.json may map folders from outside the project (e.g. a game folder on another drive). \"ask\" = an interactive launch asks once per project and set of folders, and remembers your answer; true = always allowed (still listed when you approve the file); false = never (the launch stops and says which folders). Only your own settings decide this, never a project file."
1465
+ },
1452
1466
  "wsbShare": {
1453
1467
  "type": "boolean",
1454
1468
  "description": "On Windows 11 24H2+ with the Windows Sandbox command line (wsb), add other folders to the running sandbox with `wsb share` (untested). Otherwise starting a project outside every root offers a restart."
@@ -61,8 +61,10 @@
61
61
  "description": "Folder on this PC (relative paths are relative to the config file)."
62
62
  },
63
63
  "guest": {
64
+ "description": "Where it appears inside the sandbox, e.g. C:\\Assets, or on another drive letter (D: to Z:) such as F:\\SteamLibrary\\steamapps\\common\\Game (shown as F: at boot). Leave it out to use the same path as on this PC (a folder inside the project stays where it is in the project). A path the sandbox cannot use (C:\\Windows\\..., C:\\Users\\..., one already in use) goes to C:\\Mapped\\<drive>\\<path> instead, and the launch says so.",
64
65
  "type": "string",
65
- "description": "Where it appears inside the sandbox, e.g. C:\\Assets, or on another drive letter (D: to Z:) such as F:\\SteamLibrary\\steamapps\\common\\Game: mapped to C:\\Mirror\\F\\... and shown as F: (subst) at boot."
66
+ "minLength": 1,
67
+ "maxLength": 1024
66
68
  },
67
69
  "readOnly": {
68
70
  "type": "boolean",
@@ -75,12 +77,11 @@
75
77
  },
76
78
  "required": [
77
79
  "host",
78
- "guest",
79
80
  "readOnly"
80
81
  ],
81
82
  "additionalProperties": false
82
83
  },
83
- "description": "Extra folders inside this project (relative to this file), e.g. a read-only data folder at another place in the sandbox."
84
+ "description": "Extra folders (relative paths are relative to this file), e.g. a read-only data folder at another place in the sandbox. Folders outside the project are used only as your projects.allowOutsideMappings setting allows (by default you are asked once)."
84
85
  },
85
86
  "scripts": {
86
87
  "type": "array",