windows-development-sandbox 1.13.0 → 1.14.0

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 (125) hide show
  1. package/dist/app/features/accounts/accounts.def.d.ts +1 -0
  2. package/dist/app/features/accounts/accounts.def.js +12 -3
  3. package/dist/app/features/accounts/accounts.run.d.ts +1 -0
  4. package/dist/app/features/accounts/accounts.run.js +20 -11
  5. package/dist/app/features/defs.d.ts +1 -0
  6. package/dist/app/features/settings/settings-file.d.ts +7 -0
  7. package/dist/app/features/settings/settings-file.js +19 -2
  8. package/dist/app/features/settings/settings-rows.js +9 -1
  9. package/dist/app/features/storage/storage.run.js +1 -1
  10. package/dist/bin/agent-sandbox.js +7 -1
  11. package/dist/bin/chunk-BQ23GBLC.js +78790 -0
  12. package/dist/bin/chunk-F7IMME5G.js +10432 -0
  13. package/dist/bin/chunk-LQHEUN2A.js +7 -0
  14. package/dist/bin/claude-host.js +10 -4
  15. package/dist/bin/claude-sbx.js +8 -2
  16. package/dist/bin/codex-sbx.js +8 -2
  17. package/dist/bin/gemini-sbx.js +8 -2
  18. package/dist/bin/sandbox.js +7 -2
  19. package/dist/bin/sbx-hide.js +8 -2
  20. package/dist/bin/sbx-show.js +8 -2
  21. package/dist/cli/commands/launch/launch.js +1 -1
  22. package/dist/cli/commands/launch/prepare.js +24 -7
  23. package/dist/cli/commands/launch/running.js +4 -0
  24. package/dist/cli/commands/repair/checks/contained.js +1 -1
  25. package/dist/cli/commands/storage/offer.js +1 -1
  26. package/dist/cli/menu/live.d.ts +29 -0
  27. package/dist/cli/menu/live.js +92 -21
  28. package/dist/cli/menu/menu.d.ts +2 -1
  29. package/dist/cli/menu/menu.js +16 -2
  30. package/dist/cli/menu/pages/list-page.d.ts +5 -0
  31. package/dist/cli/menu/pages/list-page.js +19 -0
  32. package/dist/cli/menu/shell.d.ts +2 -0
  33. package/dist/cli/menu/shell.js +1 -1
  34. package/dist/cli/services/accounts-service.d.ts +7 -0
  35. package/dist/cli/services/accounts-service.js +20 -0
  36. package/dist/cli/tui/app.d.ts +4 -0
  37. package/dist/cli/tui/app.js +5 -0
  38. package/dist/cli/tui/terminal.d.ts +6 -0
  39. package/dist/cli/tui/terminal.js +8 -1
  40. package/dist/cli/validate.js +8 -4
  41. package/dist/core/package-info.js +1 -1
  42. package/dist/guest/daemon/sessions.d.ts +7 -0
  43. package/dist/guest/daemon/sessions.js +23 -2
  44. package/dist/guest/entries/daemon.js +1 -0
  45. package/dist/guest/sessions/pty-host.d.ts +10 -0
  46. package/dist/guest/sessions/pty-host.js +48 -3
  47. package/dist/host/channel/channel.js +1 -1
  48. package/dist/host/install/install-record.d.ts +1 -0
  49. package/dist/host/install/install-record.js +2 -0
  50. package/dist/host/sandbox/contained-deps.d.ts +2 -1
  51. package/dist/host/sandbox/contained-deps.js +6 -4
  52. package/dist/host/sandbox/platform.d.ts +2 -0
  53. package/dist/host/sandbox/platform.js +19 -4
  54. package/dist/host/sharing/lifecycle.js +9 -1
  55. package/dist/host/storage/contained/contained-storage.js +13 -0
  56. package/dist/host/storage/contained/disk-port.d.ts +5 -0
  57. package/dist/host/storage/contained/helper-port.d.ts +72 -0
  58. package/dist/host/storage/contained/helper-port.js +162 -0
  59. package/dist/host/storage/contained/index.d.ts +1 -0
  60. package/dist/host/storage/contained/index.js +1 -0
  61. package/dist/host/storage/contained/state.d.ts +3 -2
  62. package/dist/host/storage/contained/state.js +5 -2
  63. package/dist/host/web/bridge.d.ts +5 -0
  64. package/dist/host/web/bridge.js +11 -1
  65. package/dist/host/web/server.d.ts +2 -0
  66. package/dist/host/web/server.js +11 -1
  67. package/dist/runtime/agent.js +9 -0
  68. package/dist/runtime/daemon.js +57 -3
  69. package/dist/runtime/db.js +9 -0
  70. package/dist/runtime/main.js +195 -12
  71. package/dist/runtime/netproxy.js +9 -0
  72. package/dist/runtime/view.js +195 -12
  73. package/dist/selftest/channel.test.js +72 -1
  74. package/dist/selftest/contained.test.js +1 -37
  75. package/dist/selftest/hcs.test.js +5 -2
  76. package/dist/selftest/index.js +2 -0
  77. package/dist/selftest/install.test.js +11 -0
  78. package/dist/selftest/layout.test.js +10 -1
  79. package/dist/selftest/live-daemon.test.js +51 -1
  80. package/dist/selftest/live-terminal.test.js +112 -4
  81. package/dist/selftest/menu-perf.test.js +198 -12
  82. package/dist/selftest/menu.test.js +4 -0
  83. package/dist/selftest/storage-helper.test.d.ts +2 -0
  84. package/dist/selftest/storage-helper.test.js +493 -0
  85. package/dist/selftest/support/vhdx.d.ts +14 -0
  86. package/dist/selftest/support/vhdx.js +72 -0
  87. package/dist/selftest/web.test.js +14 -2
  88. package/dist/shared/channel/run-dir.d.ts +6 -0
  89. package/dist/shared/channel/run-dir.js +9 -0
  90. package/dist/shared/pty/index.d.ts +3 -1
  91. package/dist/shared/pty/index.js +3 -1
  92. package/dist/shared/pty/key-gate.d.ts +24 -0
  93. package/dist/shared/pty/key-gate.js +45 -0
  94. package/dist/shared/pty/mouse-modes.d.ts +27 -0
  95. package/dist/shared/pty/mouse-modes.js +155 -0
  96. package/dist/shared/pty/terminal.d.ts +0 -5
  97. package/dist/shared/pty/terminal.js +51 -20
  98. package/dist/sync/watcher.js +1 -1
  99. package/dist/web/index.html +1 -1
  100. package/dist/web/{main-Z2WYAE2Z.js → main-6XNBTJLT.js} +34 -34
  101. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  102. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  103. package/native/csharp/StorageHelper/AssemblyInfo.cs +15 -0
  104. package/native/csharp/StorageHelper/Client.cs +297 -0
  105. package/native/csharp/StorageHelper/Log.cs +177 -0
  106. package/native/csharp/StorageHelper/MountRecords.cs +160 -0
  107. package/native/csharp/StorageHelper/Operations.cs +596 -0
  108. package/native/csharp/StorageHelper/Pins.cs +270 -0
  109. package/native/csharp/StorageHelper/Privileges.cs +78 -0
  110. package/native/csharp/StorageHelper/Program.cs +349 -0
  111. package/native/csharp/StorageHelper/Protocol.cs +107 -0
  112. package/native/csharp/StorageHelper/RateLimiter.cs +97 -0
  113. package/native/csharp/StorageHelper/Rules.cs +156 -0
  114. package/native/csharp/StorageHelper/Server.cs +656 -0
  115. package/native/csharp/StorageHelper/Settings.cs +297 -0
  116. package/native/csharp/StorageHelper/StorageService.cs +53 -0
  117. package/native/csharp/StorageHelper/UserSettings.cs +206 -0
  118. package/native/csharp/StorageHelper/Vhdx.cs +230 -0
  119. package/native/csharp/StorageHelper/VirtualDisk.cs +241 -0
  120. package/native/csharp/StorageHelper/Volumes.cs +379 -0
  121. package/native/csharp/StorageHelper/Watchdog.cs +128 -0
  122. package/native/csharp/StorageHelper/Win32.cs +250 -0
  123. package/native/scripts/set-storage-helper.ps1 +203 -0
  124. package/package.json +1 -1
  125. package/sandbox/csharp/PtyHost.cs +236 -1
@@ -3,7 +3,12 @@
3
3
  // .NET Framework csc.exe, so this is C# 5 (no string interpolation, no ?., no expression-bodied members).
4
4
  //
5
5
  // Usage: ptyhost.exe <port> <token> <cols> <rows> <logFile> <commandLine> [<restartCommandLine>]
6
+ // ptyhost.exe --end-tree <recordFile> ends what a ptyhost that was itself ended (Task Manager, a crash) left
7
+ // running, from the process tree it recorded (PTYHOST_TREE_FILE)
6
8
  //
9
+ // PTYHOST_TREE_FILE: where the program's process tree (ProcessTree: what it started, by id and start time) is kept
10
+ // while it runs; deleted when ptyhost ends it itself. A file left behind means ptyhost did not get to: the session
11
+ // daemon then runs --end-tree on it.
7
12
  // Wire format, both directions: [type:1][length:4, little endian][payload]
8
13
  // client -> host 1 HELLO [cols:2][rows:2][flags:1][token utf8] flags bit 0 = control-only (no output, no sizing),
9
14
  // bit 1 = status query (answered with STATUS, then closed)
@@ -37,6 +42,10 @@ static class PtyHost
37
42
  {
38
43
  static int Main(string[] args)
39
44
  {
45
+ if (args.Length == 2 && args[0] == "--end-tree")
46
+ {
47
+ return ProcessTree.EndRecorded(args[1]);
48
+ }
40
49
  if (args.Length < 6)
41
50
  {
42
51
  Console.Error.WriteLine("usage: ptyhost <port> <token> <cols> <rows> <logFile> <commandLine> [<restartCommandLine>]");
@@ -886,9 +895,24 @@ sealed class ProcessTree
886
895
  private const uint SnapProcess = 0x00000002;
887
896
  private const uint ProcessTerminate = 0x0001;
888
897
  private const uint ProcessQueryLimited = 0x1000;
898
+ private const uint Synchronize = 0x00100000;
899
+ private const uint WaitTimeout = 0x102;
889
900
  private const int FollowMs = 3000;
890
901
  private const int MaxPath = 260;
902
+ private const uint MoveReplaceExisting = 0x1;
903
+ private const uint MoveWriteThrough = 0x8;
904
+ /// <summary>A record larger than this is not one ptyhost wrote (thousands of processes).</summary>
905
+ private const int MaxRecordBytes = 1 << 20;
906
+ /// <summary>--end-tree exit codes: the ptyhost that wrote the record still runs; no readable record.</summary>
907
+ private const int StillRunning = 3;
908
+ private const int Unreadable = 4;
891
909
  private static readonly IntPtr InvalidHandle = new IntPtr(-1);
910
+ private static readonly Regex RecordEntry = new Regex("\\{\"pid\":(\\d{1,10}),\"start\":\"(\\d{1,20})\"\\}");
911
+ /// <summary>The pseudo console's own process (Windows' or conpty.dll's), started by ptyhost itself.</summary>
912
+ private static readonly HashSet<string> ConsoleHosts = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
913
+ {
914
+ "conhost.exe", "OpenConsole.exe",
915
+ };
892
916
  private static readonly HashSet<string> Shared = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
893
917
  {
894
918
  "postgres.exe", "pg_ctl.exe", "mysqld.exe", "mariadbd.exe", "redis-server.exe", "mongod.exe", "sccache.exe",
@@ -937,13 +961,28 @@ sealed class ProcessTree
937
961
 
938
962
  [DllImport("kernel32.dll", SetLastError = true)]
939
963
  public static extern bool CloseHandle(IntPtr handle);
964
+
965
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
966
+ public static extern bool MoveFileExW(string existing, string replacement, uint flags);
967
+
968
+ [DllImport("kernel32.dll")]
969
+ public static extern int GetCurrentProcessId();
970
+
971
+ [DllImport("kernel32.dll", SetLastError = true)]
972
+ public static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds);
940
973
  }
941
974
 
942
975
  private readonly object gate = new object();
943
976
  /// <summary>The program and what it started: process id -> start time.</summary>
944
977
  private readonly Dictionary<int, long> known = new Dictionary<int, long>();
978
+ /// <summary>The program (closing the console ends it), spared by EndAll (0 in --end-tree: none).</summary>
945
979
  private readonly int root;
946
980
  private readonly Timer timer;
981
+ /// <summary>Where the tree is recorded (PTYHOST_TREE_FILE), or null.</summary>
982
+ private readonly string recordFile;
983
+ private int recorded = -1;
984
+ /// <summary>The last process snapshot Refresh took (Record reuses it), or null.</summary>
985
+ private List<ProcessEntry> lastSnapshot;
947
986
  private bool ended;
948
987
 
949
988
  public ProcessTree(IntPtr rootProcess)
@@ -954,9 +993,24 @@ sealed class ProcessTree
954
993
  {
955
994
  known[root] = start;
956
995
  }
996
+ string file = Environment.GetEnvironmentVariable("PTYHOST_TREE_FILE");
997
+ recordFile = string.IsNullOrEmpty(file) ? null : file;
998
+ lock (gate)
999
+ {
1000
+ Record();
1001
+ }
957
1002
  timer = new Timer(Follow, null, FollowMs, FollowMs);
958
1003
  }
959
1004
 
1005
+ /// <summary>--end-tree: the processes of a record that still run as the same process (id and start time).</summary>
1006
+ private ProcessTree(Dictionary<int, long> alive)
1007
+ {
1008
+ foreach (KeyValuePair<int, long> p in alive)
1009
+ {
1010
+ known[p.Key] = p.Value;
1011
+ }
1012
+ }
1013
+
960
1014
  private void Follow(object unused)
961
1015
  {
962
1016
  lock (gate)
@@ -964,8 +1018,158 @@ sealed class ProcessTree
964
1018
  if (!ended)
965
1019
  {
966
1020
  Refresh();
1021
+ Record();
1022
+ }
1023
+ }
1024
+ }
1025
+
1026
+ /// <summary>
1027
+ /// Writes the tree to the record file when it grew: {"ptyhost":{"pid":1,"start":"2"},"processes":[{...}]}, start
1028
+ /// times as FILETIME strings (too large for a JSON number). Written aside and moved over the old one, so a ptyhost
1029
+ /// ended at any moment leaves a whole record.
1030
+ /// </summary>
1031
+ private void Record()
1032
+ {
1033
+ if (recordFile == null)
1034
+ {
1035
+ return;
1036
+ }
1037
+ // The pseudo console's process too: one whose ptyhost was ended does not always go away by itself.
1038
+ int self = NativeMethods.GetCurrentProcessId();
1039
+ long selfStart = StartTime(self);
1040
+ Dictionary<int, long> all = new Dictionary<int, long>(known);
1041
+ foreach (ProcessEntry p in lastSnapshot ?? Snapshot())
1042
+ {
1043
+ int pid = (int)p.ProcessId;
1044
+ if ((int)p.ParentProcessId != self || !ConsoleHosts.Contains(p.ExeFile) || all.ContainsKey(pid))
1045
+ {
1046
+ continue;
1047
+ }
1048
+ long start = StartTime(pid);
1049
+ if (start != 0 && start >= selfStart)
1050
+ {
1051
+ all[pid] = start;
967
1052
  }
968
1053
  }
1054
+ if (all.Count == recorded)
1055
+ {
1056
+ return;
1057
+ }
1058
+ StringBuilder json = new StringBuilder();
1059
+ json.Append("{\"ptyhost\":").Append(Entry(self, selfStart)).Append(",\"processes\":[");
1060
+ bool first = true;
1061
+ foreach (KeyValuePair<int, long> p in all)
1062
+ {
1063
+ json.Append(first ? "" : ",").Append(Entry(p.Key, p.Value));
1064
+ first = false;
1065
+ }
1066
+ json.Append("]}");
1067
+ string temp = recordFile + ".tmp";
1068
+ try
1069
+ {
1070
+ File.WriteAllText(temp, json.ToString());
1071
+ if (NativeMethods.MoveFileExW(temp, recordFile, MoveReplaceExisting | MoveWriteThrough))
1072
+ {
1073
+ recorded = all.Count;
1074
+ }
1075
+ }
1076
+ catch (IOException)
1077
+ {
1078
+ // Tried again on the next round (every FollowMs).
1079
+ }
1080
+ catch (UnauthorizedAccessException)
1081
+ {
1082
+ // Same.
1083
+ }
1084
+ }
1085
+
1086
+ private static string Entry(int pid, long start)
1087
+ {
1088
+ return "{\"pid\":" + pid + ",\"start\":\"" + start + "\"}";
1089
+ }
1090
+
1091
+ /// <summary>The tree ended with ptyhost's own help: nothing is left for the daemon to end.</summary>
1092
+ private void Unrecord()
1093
+ {
1094
+ if (recordFile == null)
1095
+ {
1096
+ return;
1097
+ }
1098
+ try
1099
+ {
1100
+ File.Delete(recordFile);
1101
+ }
1102
+ catch (IOException)
1103
+ {
1104
+ // The daemon finds the processes gone (their ids and start times no longer match).
1105
+ }
1106
+ catch (UnauthorizedAccessException)
1107
+ {
1108
+ // Same.
1109
+ }
1110
+ }
1111
+
1112
+ /// <summary>
1113
+ /// --end-tree: ends what a ptyhost recorded, unless that ptyhost still runs. Only processes that still run as the
1114
+ /// recorded process (same id and start time) count, and the processes they started since; the Shared ones
1115
+ /// (databases, sccache ...) are spared as always. Prints how many were ended. 0 = done, 3 = the ptyhost still
1116
+ /// runs, 4 = no readable record.
1117
+ /// </summary>
1118
+ public static int EndRecorded(string file)
1119
+ {
1120
+ string text;
1121
+ try
1122
+ {
1123
+ FileInfo info = new FileInfo(file);
1124
+ if (!info.Exists || info.Length > MaxRecordBytes)
1125
+ {
1126
+ return Unreadable;
1127
+ }
1128
+ text = File.ReadAllText(file);
1129
+ }
1130
+ catch (IOException)
1131
+ {
1132
+ return Unreadable;
1133
+ }
1134
+ catch (UnauthorizedAccessException)
1135
+ {
1136
+ return Unreadable;
1137
+ }
1138
+ if (!text.StartsWith("{\"ptyhost\":"))
1139
+ {
1140
+ return Unreadable;
1141
+ }
1142
+ MatchCollection entries = RecordEntry.Matches(text);
1143
+ if (entries.Count == 0)
1144
+ {
1145
+ return Unreadable;
1146
+ }
1147
+ Dictionary<int, long> alive = new Dictionary<int, long>();
1148
+ for (int i = 0; i < entries.Count; i++)
1149
+ {
1150
+ int pid;
1151
+ long start;
1152
+ if (!int.TryParse(entries[i].Groups[1].Value, out pid) ||
1153
+ !long.TryParse(entries[i].Groups[2].Value, out start))
1154
+ {
1155
+ return Unreadable;
1156
+ }
1157
+ bool same = pid != 0 && start != 0 && Running(pid, start);
1158
+ if (i == 0)
1159
+ {
1160
+ if (same)
1161
+ {
1162
+ return StillRunning;
1163
+ }
1164
+ }
1165
+ else if (same)
1166
+ {
1167
+ alive[pid] = start;
1168
+ }
1169
+ }
1170
+ int ended = new ProcessTree(alive).EndAll();
1171
+ Console.Out.WriteLine("ended " + ended + " process(es)");
1172
+ return 0;
969
1173
  }
970
1174
 
971
1175
  /// <summary>Adds every process whose parent is known (and that started after it), until nothing new is found.</summary>
@@ -976,6 +1180,7 @@ sealed class ProcessTree
976
1180
  return;
977
1181
  }
978
1182
  List<ProcessEntry> all = Snapshot();
1183
+ lastSnapshot = all;
979
1184
  bool added = true;
980
1185
  while (added)
981
1186
  {
@@ -1012,7 +1217,10 @@ sealed class ProcessTree
1012
1217
  }
1013
1218
  Refresh();
1014
1219
  ended = true;
1015
- timer.Dispose();
1220
+ if (timer != null)
1221
+ {
1222
+ timer.Dispose();
1223
+ }
1016
1224
  int count = 0;
1017
1225
  foreach (KeyValuePair<int, long> p in known)
1018
1226
  {
@@ -1021,6 +1229,7 @@ sealed class ProcessTree
1021
1229
  count++;
1022
1230
  }
1023
1231
  }
1232
+ Unrecord();
1024
1233
  return count;
1025
1234
  }
1026
1235
  }
@@ -1073,6 +1282,32 @@ sealed class ProcessTree
1073
1282
  }
1074
1283
  }
1075
1284
 
1285
+ /// <summary>
1286
+ /// True while process pid is the one that started at `start` and has not ended (an ended process someone still
1287
+ /// holds a handle to keeps its id and start time).
1288
+ /// </summary>
1289
+ private static bool Running(int pid, long start)
1290
+ {
1291
+ IntPtr process = NativeMethods.OpenProcess(ProcessQueryLimited | Synchronize, false, pid);
1292
+ if (process == IntPtr.Zero)
1293
+ {
1294
+ return false;
1295
+ }
1296
+ try
1297
+ {
1298
+ long creation;
1299
+ long exit;
1300
+ long kernel;
1301
+ long user;
1302
+ return NativeMethods.GetProcessTimes(process, out creation, out exit, out kernel, out user) &&
1303
+ creation == start && NativeMethods.WaitForSingleObject(process, 0) == WaitTimeout;
1304
+ }
1305
+ finally
1306
+ {
1307
+ NativeMethods.CloseHandle(process);
1308
+ }
1309
+ }
1310
+
1076
1311
  private static bool Terminate(int pid)
1077
1312
  {
1078
1313
  IntPtr process = NativeMethods.OpenProcess(ProcessTerminate, false, pid);