windows-development-sandbox 1.13.2 → 1.15.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 (153) hide show
  1. package/config.example.json +2 -1
  2. package/dist/app/features/defs.d.ts +1 -1
  3. package/dist/app/features/projects/projects.def.d.ts +1 -1
  4. package/dist/app/features/projects/projects.run.d.ts +1 -1
  5. package/dist/app/features/projects/projects.run.js +10 -2
  6. package/dist/app/features/sandbox/sandbox.def.js +3 -1
  7. package/dist/app/features/settings/categories.def.js +1 -0
  8. package/dist/app/features/storage/storage.run.js +1 -1
  9. package/dist/bin/agent-sandbox.js +2 -2
  10. package/dist/bin/{chunk-VENWSGX2.js → chunk-D7CM63BS.js} +2303 -1462
  11. package/dist/bin/{chunk-K4SU3MHJ.js → chunk-HTQT5SBY.js} +1 -1
  12. package/dist/bin/{chunk-IL7BXJF2.js → chunk-RQ4BWBYX.js} +1 -1
  13. package/dist/bin/claude-host.js +2 -2
  14. package/dist/bin/claude-sbx.js +2 -2
  15. package/dist/bin/codex-sbx.js +2 -2
  16. package/dist/bin/gemini-sbx.js +2 -2
  17. package/dist/bin/sandbox.js +2 -2
  18. package/dist/bin/sbx-hide.js +2 -2
  19. package/dist/bin/sbx-show.js +2 -2
  20. package/dist/cli/commands/launch/launch.js +1 -1
  21. package/dist/cli/commands/projects/here.js +2 -0
  22. package/dist/cli/commands/repair/checks/contained.js +1 -1
  23. package/dist/cli/services/health-service.js +15 -3
  24. package/dist/cli/services/projects-service.d.ts +8 -3
  25. package/dist/cli/services/projects-service.js +21 -4
  26. package/dist/core/config/defaults.js +1 -0
  27. package/dist/core/schema/config.d.ts +4 -0
  28. package/dist/core/schema/projects.d.ts +4 -0
  29. package/dist/core/schema/projects.js +3 -0
  30. package/dist/guest/boot/boot.js +8 -2
  31. package/dist/guest/daemon/daemon.js +3 -0
  32. package/dist/guest/daemon/live-folders.d.ts +48 -0
  33. package/dist/guest/daemon/live-folders.js +245 -0
  34. package/dist/guest/daemon/live.d.ts +4 -0
  35. package/dist/guest/daemon/live.js +57 -0
  36. package/dist/guest/daemon/sessions.d.ts +7 -0
  37. package/dist/guest/daemon/sessions.js +23 -2
  38. package/dist/guest/entries/daemon.js +5 -2
  39. package/dist/guest/runtime/csc.d.ts +1 -0
  40. package/dist/guest/runtime/csc.js +1 -0
  41. package/dist/guest/runtime/secrets.d.ts +5 -2
  42. package/dist/guest/runtime/secrets.js +12 -5
  43. package/dist/guest/sessions/pty-host.d.ts +10 -0
  44. package/dist/guest/sessions/pty-host.js +48 -3
  45. package/dist/host/channel/channel.js +1 -1
  46. package/dist/host/engines/hcs/live-folders.d.ts +117 -0
  47. package/dist/host/engines/hcs/live-folders.js +347 -0
  48. package/dist/host/engines/index.d.ts +1 -0
  49. package/dist/host/engines/index.js +1 -0
  50. package/dist/host/install/install-record.d.ts +1 -0
  51. package/dist/host/install/install-record.js +2 -0
  52. package/dist/host/launch/generate.js +8 -1
  53. package/dist/host/projects/join.d.ts +4 -0
  54. package/dist/host/projects/join.js +11 -3
  55. package/dist/host/projects/mapper.d.ts +34 -5
  56. package/dist/host/projects/mapper.js +88 -20
  57. package/dist/host/projects/registry.d.ts +2 -0
  58. package/dist/host/projects/registry.js +1 -1
  59. package/dist/host/sandbox/contained-deps.d.ts +2 -1
  60. package/dist/host/sandbox/contained-deps.js +6 -4
  61. package/dist/host/secrets/launch-secrets.js +2 -1
  62. package/dist/host/sharing/lifecycle.js +9 -1
  63. package/dist/host/storage/contained/contained-storage.js +13 -0
  64. package/dist/host/storage/contained/disk-port.d.ts +5 -0
  65. package/dist/host/storage/contained/helper-port.d.ts +72 -0
  66. package/dist/host/storage/contained/helper-port.js +162 -0
  67. package/dist/host/storage/contained/index.d.ts +1 -0
  68. package/dist/host/storage/contained/index.js +1 -0
  69. package/dist/host/web/bridge.d.ts +5 -0
  70. package/dist/host/web/bridge.js +11 -1
  71. package/dist/host/web/server.d.ts +2 -0
  72. package/dist/host/web/server.js +11 -1
  73. package/dist/runtime/agent.js +23 -0
  74. package/dist/runtime/daemon.js +523 -135
  75. package/dist/runtime/db.js +23 -0
  76. package/dist/runtime/main.js +221 -15
  77. package/dist/runtime/netproxy.js +23 -0
  78. package/dist/runtime/view.js +209 -12
  79. package/dist/selftest/channel.test.js +72 -1
  80. package/dist/selftest/contained.test.js +1 -37
  81. package/dist/selftest/hcs.test.js +5 -2
  82. package/dist/selftest/index.js +4 -0
  83. package/dist/selftest/install.test.js +11 -0
  84. package/dist/selftest/layout.test.js +1 -0
  85. package/dist/selftest/live-daemon.test.js +51 -1
  86. package/dist/selftest/live-folders.test.d.ts +2 -0
  87. package/dist/selftest/live-folders.test.js +588 -0
  88. package/dist/selftest/live-terminal.test.js +112 -4
  89. package/dist/selftest/projects.test.js +7 -7
  90. package/dist/selftest/storage-helper.test.d.ts +2 -0
  91. package/dist/selftest/storage-helper.test.js +493 -0
  92. package/dist/selftest/support/engine-golden.js +1 -0
  93. package/dist/selftest/support/fixtures.d.ts +1 -0
  94. package/dist/selftest/support/live.js +2 -0
  95. package/dist/selftest/support/vhdx.d.ts +14 -0
  96. package/dist/selftest/support/vhdx.js +72 -0
  97. package/dist/selftest/web.test.js +14 -2
  98. package/dist/shared/channel/index.d.ts +1 -0
  99. package/dist/shared/channel/index.js +1 -0
  100. package/dist/shared/channel/live.d.ts +53 -0
  101. package/dist/shared/channel/live.js +92 -0
  102. package/dist/shared/channel/run-dir.d.ts +11 -0
  103. package/dist/shared/channel/run-dir.js +20 -0
  104. package/dist/shared/channel/secrets.d.ts +1 -0
  105. package/dist/shared/channel/secrets.js +5 -0
  106. package/dist/shared/paths/projects.d.ts +6 -1
  107. package/dist/shared/paths/projects.js +21 -1
  108. package/dist/shared/pty/index.d.ts +3 -1
  109. package/dist/shared/pty/index.js +3 -1
  110. package/dist/shared/pty/key-gate.d.ts +24 -0
  111. package/dist/shared/pty/key-gate.js +45 -0
  112. package/dist/shared/pty/mouse-modes.d.ts +27 -0
  113. package/dist/shared/pty/mouse-modes.js +155 -0
  114. package/dist/shared/pty/terminal.d.ts +0 -5
  115. package/dist/shared/pty/terminal.js +51 -20
  116. package/dist/sync/watcher.js +10 -1
  117. package/dist/web/index.html +1 -1
  118. package/dist/web/{main-NBQ3LF4E.js → main-7DJZ2M27.js} +34 -34
  119. package/native/csharp/HcsHost/AssemblyInfo.cs +4 -4
  120. package/native/csharp/HcsHost/FolderAccess.cs +361 -0
  121. package/native/csharp/HcsHost/Hcs.cs +31 -3
  122. package/native/csharp/HcsHost/Program.cs +54 -6
  123. package/native/csharp/HcsHost/Share.cs +64 -11
  124. package/native/csharp/HcsHost/WsbGuard.cs +130 -0
  125. package/native/csharp/HcsHost/WsbShare.cs +295 -0
  126. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  127. package/native/csharp/StorageHelper/AssemblyInfo.cs +15 -0
  128. package/native/csharp/StorageHelper/Client.cs +297 -0
  129. package/native/csharp/StorageHelper/Log.cs +177 -0
  130. package/native/csharp/StorageHelper/MountRecords.cs +160 -0
  131. package/native/csharp/StorageHelper/Operations.cs +596 -0
  132. package/native/csharp/StorageHelper/Pins.cs +270 -0
  133. package/native/csharp/StorageHelper/Privileges.cs +78 -0
  134. package/native/csharp/StorageHelper/Program.cs +349 -0
  135. package/native/csharp/StorageHelper/Protocol.cs +107 -0
  136. package/native/csharp/StorageHelper/RateLimiter.cs +97 -0
  137. package/native/csharp/StorageHelper/Rules.cs +156 -0
  138. package/native/csharp/StorageHelper/Server.cs +656 -0
  139. package/native/csharp/StorageHelper/Settings.cs +297 -0
  140. package/native/csharp/StorageHelper/StorageService.cs +53 -0
  141. package/native/csharp/StorageHelper/UserSettings.cs +206 -0
  142. package/native/csharp/StorageHelper/Vhdx.cs +230 -0
  143. package/native/csharp/StorageHelper/VirtualDisk.cs +241 -0
  144. package/native/csharp/StorageHelper/Volumes.cs +379 -0
  145. package/native/csharp/StorageHelper/Watchdog.cs +128 -0
  146. package/native/csharp/StorageHelper/Win32.cs +250 -0
  147. package/native/scripts/contained-common.ps1 +4 -0
  148. package/native/scripts/hcs-live-share.ps1 +191 -0
  149. package/native/scripts/set-storage-helper.ps1 +203 -0
  150. package/package.json +1 -1
  151. package/sandbox/csharp/BindMount.cs +120 -0
  152. package/sandbox/csharp/PtyHost.cs +236 -1
  153. package/schemas/config.schema.json +8 -0
@@ -3,20 +3,37 @@ import { OutputFilter } from './output-filter.js';
3
3
  import { enableVtInput } from './console-input.js';
4
4
  import { InputJoiner, JOIN_MS } from './input-joiner.js';
5
5
  import { CTRL_C_CONFIRM_MS, CtrlCGuard } from './ctrl-c.js';
6
+ import { KeyGate } from './key-gate.js';
7
+ import { MouseModes } from './mouse-modes.js';
6
8
  /**
7
9
  * Turns off every mouse tracking mode a program may have turned on in your terminal (normal, button, any, UTF-8, SGR,
8
10
  * urxvt).
9
11
  */
10
12
  export const MOUSE_OFF = '\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1005l\x1b[?1006l\x1b[?1015l';
11
- /**
12
- * Node on Windows reads keys through the console API, where Backspace is ^H and Ctrl+Backspace is DEL: the reverse of
13
- * what terminals send. ConPTY reads ^H as Ctrl+Backspace (delete a word), so swap them back.
14
- */
15
- export function fixWindowsKeys(chunk) {
16
- if (!chunk.includes(0x08) && !chunk.includes(0x7f))
17
- return chunk;
18
- return Buffer.from(chunk.map((b) => (b === 0x08 ? 0x7f : b === 0x7f ? 0x08 : b)));
13
+ /** After leaving: mouse reports still on their way are dropped until input is this quiet (at most SETTLE_MAX_MS). */
14
+ const SETTLE_QUIET_MS = 50;
15
+ const SETTLE_MAX_MS = 300;
16
+ /** Waits until `stream` has been quiet for SETTLE_QUIET_MS (or SETTLE_MAX_MS passed), dropping what it reads. */
17
+ function drainInput(stream) {
18
+ return new Promise((done) => {
19
+ let quiet;
20
+ const end = () => {
21
+ clearTimeout(quiet);
22
+ clearTimeout(cap);
23
+ stream.off('data', onData);
24
+ done();
25
+ };
26
+ const onData = () => {
27
+ clearTimeout(quiet);
28
+ quiet = setTimeout(end, SETTLE_QUIET_MS);
29
+ };
30
+ const cap = setTimeout(end, SETTLE_MAX_MS);
31
+ stream.on('data', onData);
32
+ onData();
33
+ });
19
34
  }
35
+ /** Longest wait for the console's input mode before typed keys are sent anyway (it normally takes about 2 s). */
36
+ const VT_WAIT_MS = 10000;
20
37
  /**
21
38
  * Bridges this process's terminal to the remote program until it exits, the connection drops, or the user presses
22
39
  * the detach key. Restores the terminal before resolving.
@@ -32,8 +49,8 @@ options = {}) {
32
49
  const [c, r] = stdout.isTTY ? stdout.getWindowSize() : [0, 0];
33
50
  return [c || 120, r || 30];
34
51
  };
35
- // Until the console passes the terminal's input through as it is (enableVtInput), Node turns Backspace into ^H.
36
- let swapKeys = process.platform === 'win32' && stdin.isTTY;
52
+ // Until the console's input mode is known (enableVtInput), what is typed waits: see KeyGate.
53
+ const keys = new KeyGate(process.platform === 'win32' && Boolean(stdin.isTTY));
37
54
  return new Promise((resolve) => {
38
55
  let done = false;
39
56
  const reader = new FrameReader();
@@ -42,7 +59,10 @@ options = {}) {
42
59
  let resizeTimer;
43
60
  // Set once raw mode is on (below); finish() may run before that.
44
61
  // eslint-disable-next-line prefer-const
45
- let vtInput;
62
+ let vtInput, vtTimer;
63
+ // The agent's mouse modes, read from its output; only known when it is passed through (conpty.dll, see below).
64
+ const mouse = new MouseModes();
65
+ let passthrough = false;
46
66
  const finish = (result) => {
47
67
  if (done)
48
68
  return;
@@ -54,11 +74,16 @@ options = {}) {
54
74
  clearInterval(sizeWatch);
55
75
  clearTimeout(joinTimer);
56
76
  clearTimeout(hintTimer);
77
+ clearTimeout(vtTimer);
57
78
  socket.destroy();
58
- // The agent's mouse tracking ends with it: your terminal's wheel scrolls again (and clicks select text).
59
- stdout.write('\x1b[0m\x1b[?25h' + MOUSE_OFF + '\r\n');
79
+ // The agent's mouse tracking ends with it: your terminal's wheel scrolls again (and clicks select text). Reports
80
+ // the terminal sent before it read that are dropped here, or they would reach your shell as text.
81
+ const tracked = mouse.tracking();
82
+ const reset = '\x1b[0m\x1b[?25h' + MOUSE_OFF + (mouse.has(2004) ? '\x1b[?2004l' : '') + '\r\n';
83
+ const flushed = new Promise((written) => stdout.write(reset, () => written()));
84
+ const settled = tracked ? flushed.then(() => drainInput(stdin)) : Promise.resolve();
60
85
  // Wait for a console-mode change still in progress, then give the console back in its normal mode.
61
- void (vtInput?.stop() ?? Promise.resolve()).then(() => {
86
+ void Promise.all([settled, vtInput?.stop()]).then(() => {
62
87
  if (stdin.isTTY)
63
88
  stdin.setRawMode(false);
64
89
  stdin.pause();
@@ -67,10 +92,12 @@ options = {}) {
67
92
  };
68
93
  const joiner = new InputJoiner();
69
94
  let joinTimer;
70
- const sendInput = (data) => {
95
+ const write = (data) => {
71
96
  if (data.length && !done)
72
- socket.write(encodeFrame(FrameType.Input, swapKeys ? fixWindowsKeys(data) : data));
97
+ socket.write(encodeFrame(FrameType.Input, data));
73
98
  };
99
+ // Mouse reports the agent has not asked for (motion without 1002/1003 ...) are dropped: see MouseModes.
100
+ const sendInput = (data) => write(keys.push(passthrough ? mouse.filter(data) : data));
74
101
  const guard = options.confirmCtrlC ? new CtrlCGuard() : null;
75
102
  let hintTimer;
76
103
  const onInput = (chunk) => {
@@ -99,7 +126,6 @@ options = {}) {
99
126
  // repaints racing each other left the screen garbled. Wait until the size settles, clear what this terminal
100
127
  // reflowed on its own, then let ptyhost repaint everything once at the new size. With conpty.dll (passthrough)
101
128
  // nothing is repainted: this terminal's own reflow and scrollback stay, and the program redraws what it needs.
102
- let passthrough = false;
103
129
  const sendSize = () => {
104
130
  const [c, r] = size();
105
131
  if (c === sent[0] && r === sent[1])
@@ -134,13 +160,16 @@ options = {}) {
134
160
  for (const frame of frames) {
135
161
  if (frame.type === FrameType.Output) {
136
162
  const out = filter.push(frame.payload);
163
+ mouse.watch(out);
137
164
  guard?.watchOutput(out.toString('latin1'));
138
165
  stdout.write(out);
139
166
  }
140
167
  else if (frame.type === FrameType.Console)
141
168
  passthrough = frame.payload.toString('latin1') === 'conpty.dll';
142
- else if (frame.type === FrameType.Restarting)
169
+ else if (frame.type === FrameType.Restarting) {
170
+ mouse.reset();
143
171
  stdout.write(MOUSE_OFF + '\x1b[2J\x1b[3J\x1b[H');
172
+ }
144
173
  else if (frame.type === FrameType.Exit)
145
174
  finish({ reason: 'exit', exitCode: frame.payload.length >= 4 ? frame.payload.readInt32LE(0) : 1 });
146
175
  else if (frame.type === FrameType.Error)
@@ -163,9 +192,11 @@ options = {}) {
163
192
  // After setRawMode (which sets the console mode): mouse reports, focus and keys then arrive as the terminal sent
164
193
  // them.
165
194
  vtInput = enableVtInput();
195
+ // A helper that hangs must not hold the keyboard: after VT_WAIT_MS keys go as console keys until it is done.
196
+ vtTimer = setTimeout(() => write(keys.decide(false)), VT_WAIT_MS);
166
197
  void vtInput.ready.then((on) => {
167
- if (on)
168
- swapKeys = false;
198
+ clearTimeout(vtTimer);
199
+ write(keys.decide(on));
169
200
  });
170
201
  stdin.on('data', onInput);
171
202
  stdin.resume();
@@ -18,6 +18,7 @@ import { SandboxChannel } from '../host/channel/index.js';
18
18
  import { ProjectRegistry } from '../host/projects/registry.js';
19
19
  import { ContainedStorage, setNameOf, storageOf } from '../host/storage/index.js';
20
20
  import { clearSecrets } from '../host/secrets/index.js';
21
+ import { clearLiveRecord } from '../host/engines/index.js';
21
22
  import { endSharing, sharingDeps } from '../host/sharing/index.js';
22
23
  import { PACKAGE_ROOT } from '../core/package-info.js';
23
24
  import { syncBack } from './sync-cycle.js';
@@ -137,7 +138,7 @@ export class Watcher {
137
138
  /** Contained storage of this data set (only when a mounted image is on record), else null. */
138
139
  contained() {
139
140
  try {
140
- const storage = new ContainedStorage(realContainedDeps(this.storageRoot, 64), setNameOf(this.dataPath));
141
+ const storage = new ContainedStorage(realContainedDeps(this.storageRoot, 64, Boolean(this.resume.config)), setNameOf(this.dataPath));
141
142
  return storage.isRecorded() ? storage : null;
142
143
  }
143
144
  catch {
@@ -373,6 +374,14 @@ export class Watcher {
373
374
  catch (e) {
374
375
  this.note('could not remove the one-shot secrets: ' + errMsg(e));
375
376
  }
377
+ // Live folders (HCS): their shares ended with the sandbox, so their record and key go too.
378
+ try {
379
+ if (!isSandboxRunning() && clearLiveRecord(this.storageRoot, this.dataPath))
380
+ this.note('live folders: record cleared');
381
+ }
382
+ catch (e) {
383
+ this.note('could not clear the live-folder record: ' + errMsg(e));
384
+ }
376
385
  // An all-users install: the record of whose sandbox this was goes, and the group loses access to the folders.
377
386
  const sharing = sharingDeps(PACKAGE_ROOT);
378
387
  if (sharing)
@@ -6,7 +6,7 @@
6
6
  <title>Sandbox</title>
7
7
  <link rel="icon" href="data:," />
8
8
  <link rel="stylesheet" href="/main-UAQPI5XD.css" />
9
- <script type="module" src="/main-NBQ3LF4E.js"></script>
9
+ <script type="module" src="/main-7DJZ2M27.js"></script>
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>