@rivet-dev/agentos-core 0.0.0-codex-agentos-native-platform-e2e.3f8b27c → 0.0.0-codex-actor-plugin-contract-coverage.115ca8a

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 (101) hide show
  1. package/README.md +4 -7
  2. package/dist/agent-os.d.ts +150 -45
  3. package/dist/agent-os.js +769 -541
  4. package/dist/agentos-package.d.ts +29 -0
  5. package/dist/agentos-package.js +88 -0
  6. package/dist/base-filesystem.d.ts +2 -15
  7. package/dist/base-filesystem.js +23 -171
  8. package/dist/cron/types.d.ts +1 -1
  9. package/dist/default-software.d.ts +8 -0
  10. package/dist/default-software.js +10 -0
  11. package/dist/generated/AcpLimitsConfig.d.ts +4 -0
  12. package/dist/generated/AcpLimitsConfig.js +2 -0
  13. package/dist/generated/CreateVmConfig.d.ts +26 -0
  14. package/dist/generated/CreateVmConfig.js +1 -0
  15. package/dist/generated/FsPermissionRule.d.ts +6 -0
  16. package/dist/generated/FsPermissionRule.js +1 -0
  17. package/dist/generated/FsPermissionRuleSet.d.ts +6 -0
  18. package/dist/generated/FsPermissionRuleSet.js +1 -0
  19. package/dist/generated/FsPermissionScope.d.ts +3 -0
  20. package/dist/generated/FsPermissionScope.js +1 -0
  21. package/dist/generated/HttpLimitsConfig.d.ts +3 -0
  22. package/dist/generated/HttpLimitsConfig.js +2 -0
  23. package/dist/generated/JsModuleResolution.d.ts +1 -0
  24. package/dist/generated/JsModuleResolution.js +2 -0
  25. package/dist/generated/JsRuntimeConfig.d.ts +31 -0
  26. package/dist/generated/JsRuntimeConfig.js +1 -0
  27. package/dist/generated/JsRuntimeLimitsConfig.d.ts +11 -0
  28. package/dist/generated/JsRuntimeLimitsConfig.js +2 -0
  29. package/dist/generated/JsRuntimePlatform.d.ts +1 -0
  30. package/dist/generated/JsRuntimePlatform.js +2 -0
  31. package/dist/generated/MountPluginDescriptor.d.ts +4 -0
  32. package/dist/generated/MountPluginDescriptor.js +2 -0
  33. package/dist/generated/NativeRootFilesystemConfig.d.ts +5 -0
  34. package/dist/generated/NativeRootFilesystemConfig.js +1 -0
  35. package/dist/generated/PatternPermissionRule.d.ts +6 -0
  36. package/dist/generated/PatternPermissionRule.js +1 -0
  37. package/dist/generated/PatternPermissionRuleSet.d.ts +6 -0
  38. package/dist/generated/PatternPermissionRuleSet.js +1 -0
  39. package/dist/generated/PatternPermissionScope.d.ts +3 -0
  40. package/dist/generated/PatternPermissionScope.js +1 -0
  41. package/dist/generated/PermissionMode.d.ts +1 -0
  42. package/dist/generated/PermissionMode.js +2 -0
  43. package/dist/generated/PermissionsPolicy.d.ts +10 -0
  44. package/dist/generated/PermissionsPolicy.js +1 -0
  45. package/dist/generated/PluginLimitsConfig.d.ts +4 -0
  46. package/dist/generated/PluginLimitsConfig.js +2 -0
  47. package/dist/generated/PythonLimitsConfig.d.ts +6 -0
  48. package/dist/generated/PythonLimitsConfig.js +2 -0
  49. package/dist/generated/ResourceLimitsConfig.d.ts +24 -0
  50. package/dist/generated/ResourceLimitsConfig.js +2 -0
  51. package/dist/generated/RootFilesystemConfig.d.ts +9 -0
  52. package/dist/generated/RootFilesystemConfig.js +1 -0
  53. package/dist/generated/RootFilesystemEntry.d.ts +13 -0
  54. package/dist/generated/RootFilesystemEntry.js +1 -0
  55. package/dist/generated/RootFilesystemEntryEncoding.d.ts +1 -0
  56. package/dist/generated/RootFilesystemEntryEncoding.js +2 -0
  57. package/dist/generated/RootFilesystemEntryKind.d.ts +1 -0
  58. package/dist/generated/RootFilesystemEntryKind.js +2 -0
  59. package/dist/generated/RootFilesystemLowerDescriptor.d.ts +7 -0
  60. package/dist/generated/RootFilesystemLowerDescriptor.js +1 -0
  61. package/dist/generated/RootFilesystemMode.d.ts +1 -0
  62. package/dist/generated/RootFilesystemMode.js +2 -0
  63. package/dist/generated/ToolLimitsConfig.d.ts +10 -0
  64. package/dist/generated/ToolLimitsConfig.js +2 -0
  65. package/dist/generated/VmDnsConfig.d.ts +6 -0
  66. package/dist/generated/VmDnsConfig.js +2 -0
  67. package/dist/generated/VmLimitsConfig.d.ts +18 -0
  68. package/dist/generated/VmLimitsConfig.js +1 -0
  69. package/dist/generated/VmListenPolicyConfig.d.ts +5 -0
  70. package/dist/generated/VmListenPolicyConfig.js +2 -0
  71. package/dist/generated/WasmLimitsConfig.d.ts +7 -0
  72. package/dist/generated/WasmLimitsConfig.js +2 -0
  73. package/dist/host-dir-mount.d.ts +1 -1
  74. package/dist/host-tools-zod.js +27 -7
  75. package/dist/host-tools.js +9 -0
  76. package/dist/index.d.ts +3 -1
  77. package/dist/index.js +2 -1
  78. package/dist/layers.d.ts +2 -2
  79. package/dist/layers.js +19 -10
  80. package/dist/options-schema.d.ts +453 -0
  81. package/dist/options-schema.js +268 -0
  82. package/dist/packages.d.ts +17 -116
  83. package/dist/packages.js +35 -322
  84. package/dist/runtime-compat.d.ts +22 -9
  85. package/dist/runtime-compat.js +126 -141
  86. package/dist/runtime.d.ts +32 -5
  87. package/dist/sandbox.d.ts +112 -0
  88. package/dist/sandbox.js +264 -0
  89. package/dist/sidecar/agentos-protocol.d.ts +88 -1
  90. package/dist/sidecar/agentos-protocol.js +134 -18
  91. package/dist/sidecar/native-process-client.d.ts +6 -3
  92. package/dist/sidecar/native-process-client.js +5 -3
  93. package/dist/sidecar/permissions.d.ts +1 -1
  94. package/dist/sidecar/permissions.js +3 -3
  95. package/dist/sidecar/rpc-client.d.ts +62 -4
  96. package/dist/sidecar/rpc-client.js +339 -85
  97. package/dist/types.d.ts +10 -3
  98. package/package.json +40 -34
  99. package/dist/agents.d.ts +0 -63
  100. package/dist/agents.js +0 -42
  101. package/fixtures/AGENTOS_SYSTEM_PROMPT.md +0 -21
@@ -7,6 +7,30 @@ const PROTECTED_READ_ONLY_GUEST_ROOTS = ["/etc/agentos"];
7
7
  const TRAILING_OUTPUT_DRAIN_INTERVAL_MS = 10;
8
8
  const TRAILING_OUTPUT_DRAIN_MAX_MS = 250;
9
9
  const TRAILING_OUTPUT_DRAIN_QUIET_TURNS = 2;
10
+ function shouldLogStructuredSidecarEvent(name) {
11
+ const normalized = name.toLowerCase();
12
+ return (normalized === "limit_warning" ||
13
+ normalized.startsWith("security.") ||
14
+ normalized.includes("warning") ||
15
+ normalized.includes("failed") ||
16
+ normalized.includes("error"));
17
+ }
18
+ function formatStructuredSidecarDetail(detail) {
19
+ const entries = Object.entries(detail);
20
+ if (entries.length === 0) {
21
+ return "";
22
+ }
23
+ return entries.map(([key, value]) => `${key}=${JSON.stringify(value)}`).join(" ");
24
+ }
25
+ function logStructuredSidecarEvent(name, detail) {
26
+ if (!shouldLogStructuredSidecarEvent(name)) {
27
+ return;
28
+ }
29
+ const formatted = formatStructuredSidecarDetail(detail);
30
+ console.warn(formatted
31
+ ? `[agent-os] sidecar ${name}: ${formatted}`
32
+ : `[agent-os] sidecar ${name}`);
33
+ }
10
34
  async function drainTrailingProcessOutputTurn(delayMs = 0) {
11
35
  // Native-sidecar `process_output` events can lag one macrotask behind the
12
36
  // terminal `process_exited` notification for very short-lived processes, and
@@ -142,7 +166,10 @@ function parseSimpleExecCommand(command) {
142
166
  return tokens;
143
167
  }
144
168
  function canUseDirectExec(driver, commandName) {
145
- return driver === "wasmvm" || (driver === "node" && commandName === "node");
169
+ return (driver === "wasmvm" ||
170
+ (driver === "node" && commandName === "node") ||
171
+ (driver === "python" &&
172
+ (commandName === "python" || commandName === "python3")));
146
173
  }
147
174
  function shellSingleQuote(value) {
148
175
  if (value.length === 0) {
@@ -181,7 +208,12 @@ export class NativeSidecarKernelProxy {
181
208
  client;
182
209
  session;
183
210
  vm;
211
+ ownsClient;
184
212
  localMounts;
213
+ baseSidecarMounts;
214
+ permissions;
215
+ commandPermissions;
216
+ loopbackExemptPorts;
185
217
  commandDrivers;
186
218
  onWasmCommandResolved;
187
219
  onDispose;
@@ -199,6 +231,7 @@ export class NativeSidecarKernelProxy {
199
231
  zombieTimerCountRefresh = null;
200
232
  disposed = false;
201
233
  pumpError = null;
234
+ mountReconfigurePromise = null;
202
235
  nextSyntheticPid = SYNTHETIC_PID_BASE;
203
236
  eventPumpAbortController = new AbortController();
204
237
  eventPump;
@@ -206,10 +239,17 @@ export class NativeSidecarKernelProxy {
206
239
  this.client = options.client;
207
240
  this.session = options.session;
208
241
  this.vm = options.vm;
242
+ this.ownsClient = options.ownsClient ?? true;
209
243
  this.env = { ...options.env };
210
244
  this.cwd = options.cwd;
211
245
  this.defaultExecCwd = options.defaultExecCwd;
212
246
  this.localMounts = [...options.localMounts].sort((left, right) => right.path.length - left.path.length);
247
+ const localMountPaths = new Set(this.localMounts.map((mount) => mount.path));
248
+ this.baseSidecarMounts = options.sidecarMounts.filter((mount) => mount.plugin.id !== "js_bridge" ||
249
+ !localMountPaths.has(posixPath.normalize(mount.guestPath)));
250
+ this.permissions = options.permissions;
251
+ this.commandPermissions = options.commandPermissions;
252
+ this.loopbackExemptPorts = options.loopbackExemptPorts;
213
253
  this.commandDrivers = buildCommandMap(options.commandGuestPaths);
214
254
  this.onWasmCommandResolved = options.onWasmCommandResolved;
215
255
  this.onDispose = options.onDispose;
@@ -239,6 +279,7 @@ export class NativeSidecarKernelProxy {
239
279
  }
240
280
  this.disposed = true;
241
281
  this.eventPumpAbortController.abort();
282
+ await this.mountReconfigurePromise?.catch(() => { });
242
283
  const liveProcesses = [...this.trackedProcesses.values()].filter((entry) => entry.exitCode === null);
243
284
  await Promise.allSettled(liveProcesses.map((entry) => this.signalProcess(entry, 15)));
244
285
  await this.client.disposeVm(this.session, this.vm).catch(() => { });
@@ -251,9 +292,42 @@ export class NativeSidecarKernelProxy {
251
292
  this.finishProcess(entry, 143);
252
293
  }
253
294
  }
254
- await this.client.dispose().catch(() => { });
295
+ // Only tear down the shared sidecar process when this proxy owns it. VMs
296
+ // leased from an `AgentOsSidecar` handle share one process, which is
297
+ // disposed when the handle is disposed.
298
+ if (this.ownsClient) {
299
+ await this.client.dispose().catch(() => { });
300
+ }
255
301
  await this.eventPump.catch(() => { });
256
302
  await this.onDispose?.().catch(() => { });
303
+ // Drop all per-VM tracking state so a disposed proxy retains nothing.
304
+ for (const entry of this.trackedProcesses.values()) {
305
+ entry.onStdout.clear();
306
+ entry.onStderr.clear();
307
+ }
308
+ this.trackedProcesses.clear();
309
+ this.trackedProcessesById.clear();
310
+ this.signalStates.clear();
311
+ this.signalRefreshes.clear();
312
+ this.localMounts.length = 0;
313
+ }
314
+ /** Test-only snapshot of the per-VM tracking collection sizes. */
315
+ __trackingSizesForTest() {
316
+ return {
317
+ trackedProcesses: this.trackedProcesses.size,
318
+ trackedProcessesById: this.trackedProcessesById.size,
319
+ signalStates: this.signalStates.size,
320
+ signalRefreshes: this.signalRefreshes.size,
321
+ localMounts: this.localMounts.length,
322
+ };
323
+ }
324
+ /** Test-only handle to a tracked entry so its listener Sets can be inspected. */
325
+ __trackedEntryForTest(pid) {
326
+ return this.trackedProcesses.get(pid);
327
+ }
328
+ /** Test-only join point for in-flight signal-state refreshes. */
329
+ async __awaitSignalRefreshesForTest() {
330
+ await Promise.allSettled([...this.signalRefreshes.values()]);
257
331
  }
258
332
  async exec(command, options) {
259
333
  if (!this.commands.has("sh")) {
@@ -263,9 +337,6 @@ export class NativeSidecarKernelProxy {
263
337
  const stderrChunks = [];
264
338
  const effectiveCwd = options?.cwd ?? this.defaultExecCwd ?? this.cwd;
265
339
  const parsedCommand = parseSimpleExecCommand(command);
266
- const resolveExecPath = (targetPath) => targetPath.startsWith("/")
267
- ? posixPath.normalize(targetPath)
268
- : posixPath.normalize(posixPath.join(effectiveCwd, targetPath));
269
340
  const runAndCapture = async (proc, stdinOverride, readExitCode) => {
270
341
  if (stdinOverride !== undefined) {
271
342
  proc.writeStdin(stdinOverride);
@@ -301,43 +372,6 @@ export class NativeSidecarKernelProxy {
301
372
  stderr: Buffer.concat(stderrChunks.map((chunk) => Buffer.from(chunk))).toString("utf8"),
302
373
  };
303
374
  };
304
- if (parsedCommand &&
305
- (parsedCommand[0] === "sh" || parsedCommand[0] === "/bin/sh") &&
306
- parsedCommand[1] === "-c" &&
307
- parsedCommand.length === 3) {
308
- const shellScript = parsedCommand[2].trim();
309
- const exitMatch = shellScript.match(/^exit(?:\s+(-?\d+))?$/);
310
- if (exitMatch) {
311
- return {
312
- exitCode: Number.parseInt(exitMatch[1] ?? "0", 10),
313
- stdout: "",
314
- stderr: "",
315
- };
316
- }
317
- return this.exec(parsedCommand[2], options);
318
- }
319
- if (parsedCommand &&
320
- parsedCommand[0] === "chmod" &&
321
- parsedCommand.length >= 3 &&
322
- /^[0-7]{3,4}$/.test(parsedCommand[1] ?? "")) {
323
- const mode = Number.parseInt(parsedCommand[1], 8);
324
- for (const target of parsedCommand.slice(2)) {
325
- await this.client.chmod(this.session, this.vm, resolveExecPath(target), mode);
326
- }
327
- return { exitCode: 0, stdout: "", stderr: "" };
328
- }
329
- if (parsedCommand &&
330
- parsedCommand[0] === "stat" &&
331
- parsedCommand.length === 4 &&
332
- parsedCommand[1] === "-c" &&
333
- parsedCommand[2] === "%a") {
334
- const stat = await this.stat(resolveExecPath(parsedCommand[3]));
335
- return {
336
- exitCode: 0,
337
- stdout: `${(stat.mode & 0o777).toString(8)}\n`,
338
- stderr: "",
339
- };
340
- }
341
375
  const parsedCommandDriver = parsedCommand
342
376
  ? this.commands.get(parsedCommand[0])
343
377
  : undefined;
@@ -447,11 +481,15 @@ export class NativeSidecarKernelProxy {
447
481
  processId,
448
482
  command: spawnCommand,
449
483
  args: spawnArgs,
450
- driver: spawnCommand === "node" ? "node" : "wasmvm",
484
+ driver: spawnCommand === "node"
485
+ ? "node"
486
+ : spawnCommand === "python" || spawnCommand === "python3"
487
+ ? "python"
488
+ : "wasmvm",
451
489
  cwd: options?.cwd ?? this.cwd,
452
490
  env: {
453
491
  ...(options?.env ?? {}),
454
- ...(options?.streamStdin ? { SECURE_EXEC_KEEP_STDIN_OPEN: "1" } : {}),
492
+ ...(options?.streamStdin ? { AGENTOS_KEEP_STDIN_OPEN: "1" } : {}),
455
493
  },
456
494
  startTime: Date.now(),
457
495
  exitTime: null,
@@ -479,16 +517,16 @@ export class NativeSidecarKernelProxy {
479
517
  pid,
480
518
  writeStdin: (data) => {
481
519
  if (entry.exitCode !== null) {
482
- return;
520
+ return Promise.resolve();
483
521
  }
484
522
  entry.pendingStdin.push(data);
485
- void this.flushPendingStdin(entry).catch((error) => {
523
+ return this.flushPendingStdin(entry).catch((error) => {
486
524
  this.handleBackgroundProcessError(entry, error);
487
525
  });
488
526
  },
489
527
  closeStdin: () => {
490
528
  entry.pendingCloseStdin = true;
491
- void this.closeTrackedStdin(entry).catch((error) => {
529
+ return this.closeTrackedStdin(entry).catch((error) => {
492
530
  this.handleBackgroundProcessError(entry, error);
493
531
  });
494
532
  },
@@ -532,7 +570,6 @@ export class NativeSidecarKernelProxy {
532
570
  const args = options?.args ??
533
571
  (command === "sh" || command === "/bin/sh" ? ["-i"] : []);
534
572
  const synthesizePrompt = !options?.command && !options?.args;
535
- const autoCloseExplicitCommandStdin = Boolean(options?.command) && !["sh", "/bin/sh", "bash"].includes(command);
536
573
  const promptText = "sh-0.4$ ";
537
574
  const textEncoder = new TextEncoder();
538
575
  const textDecoder = new TextDecoder();
@@ -542,14 +579,18 @@ export class NativeSidecarKernelProxy {
542
579
  .replace(/\u001b\[[0-9;]*m/g, "")
543
580
  .replace(/^.*WARN could not retrieve pid for child process\n?/gm, "")
544
581
  .replace(/^ProcessExitError:.*\n(?:\s+at .*\n)*/gm, "");
582
+ const sanitizeNativeShellOutput = (chunk) => {
583
+ const text = textDecoder.decode(chunk);
584
+ const sanitized = text.replace(/^.*WARN could not retrieve pid for child process\n?/gm, "");
585
+ return sanitized.length > 0 ? textEncoder.encode(sanitized) : null;
586
+ };
545
587
  let bufferedInput = "";
546
588
  let bufferedCommand = "";
547
589
  let activeForegroundProcess = null;
548
- let shellEnv = { ...(options?.env ?? {}) };
590
+ let shellEnv = { ...(options?.env ?? {}), AGENTOS_EXEC_TTY: "1" };
549
591
  let shellCwd = options?.cwd ?? this.cwd;
550
592
  let syntheticCommandQueue = Promise.resolve();
551
593
  let promptTimer = null;
552
- let closeStdinTimer = null;
553
594
  let commandInFlight = false;
554
595
  let syntheticCursorAtLineStart = true;
555
596
  const syntheticPid = this.nextSyntheticPid++;
@@ -564,12 +605,6 @@ export class NativeSidecarKernelProxy {
564
605
  promptTimer = null;
565
606
  }
566
607
  };
567
- const clearCloseStdinTimer = () => {
568
- if (closeStdinTimer !== null) {
569
- clearTimeout(closeStdinTimer);
570
- closeStdinTimer = null;
571
- }
572
- };
573
608
  const normalizeSyntheticTerminalText = (text) => text.replace(/\r?\n/g, "\r\n");
574
609
  const updateSyntheticCursor = (text) => {
575
610
  if (!text) {
@@ -665,15 +700,36 @@ export class NativeSidecarKernelProxy {
665
700
  }
666
701
  return parsed;
667
702
  };
668
- const writeForegroundInput = (proc, data) => {
703
+ const writeForegroundInput = async (proc, data) => {
669
704
  if (typeof data === "string") {
670
705
  for (const character of data) {
671
- proc.writeStdin(character);
706
+ await proc.writeStdin(character);
672
707
  }
673
708
  return;
674
709
  }
675
710
  for (const byte of data) {
676
- proc.writeStdin(new Uint8Array([byte]));
711
+ await proc.writeStdin(new Uint8Array([byte]));
712
+ }
713
+ };
714
+ const appendSyntheticInput = (input) => {
715
+ for (const character of input) {
716
+ if (character === "\u0004") {
717
+ continue;
718
+ }
719
+ if (character === "\b" || character === "\u007f") {
720
+ if (bufferedInput.length > 0) {
721
+ bufferedInput = bufferedInput.slice(0, -1);
722
+ emitSyntheticTerminal("\b \b");
723
+ }
724
+ continue;
725
+ }
726
+ bufferedInput += character;
727
+ if (character === "\n") {
728
+ emitSyntheticTerminal("\n");
729
+ }
730
+ else if (character >= " ") {
731
+ emitSyntheticTerminal(character);
732
+ }
677
733
  }
678
734
  };
679
735
  let onData = null;
@@ -685,7 +741,7 @@ export class NativeSidecarKernelProxy {
685
741
  schedulePrompt(0);
686
742
  return {
687
743
  pid: syntheticPid,
688
- write(data) {
744
+ async write(data) {
689
745
  if (syntheticExitCode !== null) {
690
746
  return;
691
747
  }
@@ -696,13 +752,13 @@ export class NativeSidecarKernelProxy {
696
752
  if (rawText.includes("\u0003")) {
697
753
  const [beforeInterrupt] = rawText.split("\u0003");
698
754
  if (beforeInterrupt) {
699
- writeForegroundInput(activeForegroundProcess, beforeInterrupt);
755
+ await writeForegroundInput(activeForegroundProcess, beforeInterrupt);
700
756
  }
701
757
  emitSyntheticTerminal("^C\n");
702
758
  activeForegroundProcess.kill(2);
703
759
  return;
704
760
  }
705
- writeForegroundInput(activeForegroundProcess, data);
761
+ await writeForegroundInput(activeForegroundProcess, data);
706
762
  return;
707
763
  }
708
764
  const rawText = typeof data === "string"
@@ -725,7 +781,7 @@ export class NativeSidecarKernelProxy {
725
781
  finishSyntheticShell(0);
726
782
  return;
727
783
  }
728
- bufferedInput += text.replace(/\u0004/g, "");
784
+ appendSyntheticInput(text.replace(/\r\n/g, "\n").replace(/\r/g, "\n"));
729
785
  while (true) {
730
786
  const newlineIndex = bufferedInput.indexOf("\n");
731
787
  if (newlineIndex < 0) {
@@ -735,7 +791,6 @@ export class NativeSidecarKernelProxy {
735
791
  .slice(0, newlineIndex)
736
792
  .replace(/\r$/, "");
737
793
  bufferedInput = bufferedInput.slice(newlineIndex + 1);
738
- emitSyntheticStdout(`${line}\n`);
739
794
  const nextCommand = bufferedCommand
740
795
  ? `${bufferedCommand}\n${line}`
741
796
  : line;
@@ -834,20 +889,33 @@ export class NativeSidecarKernelProxy {
834
889
  };
835
890
  }
836
891
  const proc = this.spawn(command, args, {
837
- env: options?.env,
892
+ env: {
893
+ ...(options?.env ?? {}),
894
+ ...(options?.cols ? { COLUMNS: String(Math.trunc(options.cols)) } : {}),
895
+ ...(options?.rows ? { LINES: String(Math.trunc(options.rows)) } : {}),
896
+ AGENTOS_EXEC_TTY: "1",
897
+ },
838
898
  cwd: options?.cwd,
839
899
  streamStdin: true,
840
900
  onStdout: (chunk) => {
901
+ const sanitized = sanitizeNativeShellOutput(chunk);
902
+ if (!sanitized) {
903
+ return;
904
+ }
841
905
  for (const handler of stdoutHandlers) {
842
- handler(chunk);
906
+ handler(sanitized);
843
907
  }
844
908
  if (commandInFlight) {
845
909
  schedulePrompt(120);
846
910
  }
847
911
  },
848
912
  onStderr: (chunk) => {
913
+ const sanitized = sanitizeNativeShellOutput(chunk);
914
+ if (!sanitized) {
915
+ return;
916
+ }
849
917
  for (const handler of stderrHandlers) {
850
- handler(chunk);
918
+ handler(sanitized);
851
919
  }
852
920
  if (commandInFlight) {
853
921
  schedulePrompt(120);
@@ -856,18 +924,11 @@ export class NativeSidecarKernelProxy {
856
924
  });
857
925
  return {
858
926
  pid: proc.pid,
859
- write(data) {
927
+ async write(data) {
860
928
  if (synthesizePrompt) {
861
929
  return;
862
930
  }
863
- proc.writeStdin(data);
864
- if (autoCloseExplicitCommandStdin) {
865
- clearCloseStdinTimer();
866
- closeStdinTimer = setTimeout(() => {
867
- closeStdinTimer = null;
868
- proc.closeStdin();
869
- }, 100);
870
- }
931
+ await proc.writeStdin(data);
871
932
  if (synthesizePrompt &&
872
933
  typeof data === "string" &&
873
934
  (data.includes("\n") || data.includes("\r"))) {
@@ -881,11 +942,18 @@ export class NativeSidecarKernelProxy {
881
942
  set onData(handler) {
882
943
  onData = handler;
883
944
  },
884
- resize() {
885
- // The current stdio-native path is process-backed rather than PTY-backed.
945
+ resize: (cols, rows) => {
946
+ const entry = this.trackedProcesses.get(proc.pid);
947
+ if (!entry || entry.exitCode !== null) {
948
+ return;
949
+ }
950
+ void entry.startPromise
951
+ .then(() => this.client.resizePty(this.session, this.vm, entry.processId, Math.trunc(cols), Math.trunc(rows)))
952
+ .catch((error) => {
953
+ this.handleBackgroundProcessError(entry, error);
954
+ });
886
955
  },
887
956
  kill(signal) {
888
- clearCloseStdinTimer();
889
957
  clearPromptTimer();
890
958
  proc.kill(signal);
891
959
  },
@@ -985,12 +1053,41 @@ export class NativeSidecarKernelProxy {
985
1053
  const entries = await this.client.readdir(this.session, this.vm, path);
986
1054
  return [...new Set([...entries, ...this.mountedChildNames(path)])].sort((a, b) => a.localeCompare(b));
987
1055
  }
1056
+ async readdirRecursive(path, options) {
1057
+ const local = this.resolveLocalMount(path);
1058
+ if (local) {
1059
+ return this.readdirRecursiveLocal(local.mount.fs, local.relativePath, options?.maxDepth);
1060
+ }
1061
+ return this.client.readdirRecursive(this.session, this.vm, path, options);
1062
+ }
988
1063
  async removeFile(path) {
989
1064
  return this.dispatchWrite(path, (mount, relativePath) => mount.fs.removeFile(relativePath), () => this.client.removeFile(this.session, this.vm, path));
990
1065
  }
991
1066
  async removeDir(path) {
992
1067
  return this.dispatchWrite(path, (mount, relativePath) => mount.fs.removeDir(relativePath), () => this.client.removeDir(this.session, this.vm, path));
993
1068
  }
1069
+ async removePath(path, options) {
1070
+ return this.dispatchWrite(path, (mount, relativePath) => this.removePathLocal(mount.fs, relativePath, options?.recursive ?? false), () => this.client.removePath(this.session, this.vm, path, {
1071
+ recursive: options?.recursive ?? false,
1072
+ }));
1073
+ }
1074
+ async copyPath(fromPath, toPath, options) {
1075
+ const from = this.resolveLocalMount(fromPath);
1076
+ const to = this.resolveLocalMount(toPath);
1077
+ if (!!from !== !!to) {
1078
+ throw errnoError("EXDEV", "cross-device link not permitted");
1079
+ }
1080
+ if (from && to) {
1081
+ if (from.mount.path !== to.mount.path) {
1082
+ throw errnoError("EXDEV", "cross-device link not permitted");
1083
+ }
1084
+ this.assertLocalWritable(to.mount);
1085
+ return this.copyPathLocal(from.mount.fs, from.relativePath, to.relativePath, options?.recursive ?? false);
1086
+ }
1087
+ return this.client.copyPath(this.session, this.vm, fromPath, toPath, {
1088
+ recursive: options?.recursive ?? false,
1089
+ });
1090
+ }
994
1091
  async rename(oldPath, newPath) {
995
1092
  const from = this.resolveLocalMount(oldPath);
996
1093
  const to = this.resolveLocalMount(newPath);
@@ -1006,19 +1103,77 @@ export class NativeSidecarKernelProxy {
1006
1103
  }
1007
1104
  return this.client.rename(this.session, this.vm, oldPath, newPath);
1008
1105
  }
1106
+ async movePath(oldPath, newPath) {
1107
+ const from = this.resolveLocalMount(oldPath);
1108
+ const to = this.resolveLocalMount(newPath);
1109
+ if (!!from !== !!to) {
1110
+ throw errnoError("EXDEV", "cross-device link not permitted");
1111
+ }
1112
+ if (from && to) {
1113
+ if (from.mount.path !== to.mount.path) {
1114
+ throw errnoError("EXDEV", "cross-device link not permitted");
1115
+ }
1116
+ this.assertLocalWritable(from.mount);
1117
+ return from.mount.fs.rename(from.relativePath, to.relativePath);
1118
+ }
1119
+ return this.client.movePath(this.session, this.vm, oldPath, newPath);
1120
+ }
1009
1121
  mountFs(path, driver, options) {
1010
1122
  this.localMounts.unshift({
1011
1123
  path: posixPath.normalize(path),
1012
1124
  fs: driver,
1013
1125
  readOnly: options?.readOnly ?? false,
1126
+ sidecarMount: options?.sidecarMount,
1014
1127
  });
1015
1128
  this.localMounts.sort((left, right) => right.path.length - left.path.length);
1129
+ void this.reconfigureSidecarMounts().catch(() => { });
1016
1130
  }
1017
1131
  unmountFs(path) {
1018
1132
  const normalized = posixPath.normalize(path);
1019
1133
  const index = this.localMounts.findIndex((mount) => mount.path === normalized);
1020
1134
  if (index >= 0) {
1021
1135
  this.localMounts.splice(index, 1);
1136
+ void this.reconfigureSidecarMounts().catch(() => { });
1137
+ }
1138
+ }
1139
+ desiredSidecarMounts() {
1140
+ return [
1141
+ ...this.baseSidecarMounts,
1142
+ ...this.localMounts.map((mount) => mount.sidecarMount ?? {
1143
+ guestPath: mount.path,
1144
+ readOnly: mount.readOnly,
1145
+ plugin: {
1146
+ id: "js_bridge",
1147
+ config: {},
1148
+ },
1149
+ }),
1150
+ ];
1151
+ }
1152
+ reconfigureSidecarMounts() {
1153
+ const run = async () => {
1154
+ if (this.disposed) {
1155
+ return;
1156
+ }
1157
+ await this.client.configureVm(this.session, this.vm, {
1158
+ mounts: this.desiredSidecarMounts(),
1159
+ permissions: this.permissions,
1160
+ commandPermissions: this.commandPermissions,
1161
+ loopbackExemptPorts: this.loopbackExemptPorts,
1162
+ });
1163
+ };
1164
+ const previous = this.mountReconfigurePromise ?? Promise.resolve();
1165
+ const next = previous.then(run, run);
1166
+ const tracked = next.finally(() => {
1167
+ if (this.mountReconfigurePromise === tracked) {
1168
+ this.mountReconfigurePromise = null;
1169
+ }
1170
+ });
1171
+ this.mountReconfigurePromise = tracked;
1172
+ return tracked;
1173
+ }
1174
+ async waitForMountReconfigure() {
1175
+ if (this.mountReconfigurePromise) {
1176
+ await this.mountReconfigurePromise;
1022
1177
  }
1023
1178
  }
1024
1179
  snapshotProcesses() {
@@ -1132,6 +1287,7 @@ export class NativeSidecarKernelProxy {
1132
1287
  }
1133
1288
  }
1134
1289
  async startTrackedProcess(entry) {
1290
+ await this.waitForMountReconfigure();
1135
1291
  const started = await this.client.execute(this.session, this.vm, {
1136
1292
  processId: entry.processId,
1137
1293
  command: entry.command,
@@ -1157,11 +1313,13 @@ export class NativeSidecarKernelProxy {
1157
1313
  }
1158
1314
  }
1159
1315
  async runEventPump() {
1316
+ // Scope the pump to THIS VM's ownership so multiple proxies can share one
1317
+ // sidecar process: events for other VMs stay buffered for their own pumps
1318
+ // rather than being consumed (and dropped) here.
1319
+ const vmId = this.vm.vmId;
1160
1320
  while (!this.disposed) {
1161
1321
  try {
1162
- const event = await this.client.waitForEvent({ any: true }, undefined, {
1163
- signal: this.eventPumpAbortController.signal,
1164
- });
1322
+ const event = await this.client.waitForEvent((frame) => frame.ownership.scope === "vm" && frame.ownership.vm_id === vmId, undefined, { signal: this.eventPumpAbortController.signal });
1165
1323
  if (event.payload.type === "process_output") {
1166
1324
  const entry = this.trackedProcessesById.get(event.payload.process_id);
1167
1325
  if (!entry) {
@@ -1188,8 +1346,11 @@ export class NativeSidecarKernelProxy {
1188
1346
  continue;
1189
1347
  }
1190
1348
  void this.refreshProcessSnapshot().catch(() => { });
1191
- this.signalRefreshes.delete(entry.pid);
1192
1349
  this.finishProcess(entry, event.payload.exit_code);
1350
+ continue;
1351
+ }
1352
+ if (event.payload.type === "structured") {
1353
+ logStructuredSidecarEvent(event.payload.name, event.payload.detail);
1193
1354
  }
1194
1355
  }
1195
1356
  catch (error) {
@@ -1220,6 +1381,25 @@ export class NativeSidecarKernelProxy {
1220
1381
  entry.exitTime = Date.now();
1221
1382
  this.updateTrackedProcessSnapshot(entry);
1222
1383
  entry.resolveWait(exitCode);
1384
+ // Release per-process tracking now that the process has terminated so these
1385
+ // maps/Sets don't grow without bound. Defer the release until trailing
1386
+ // output has drained: `wait()`'s drain and late `process_output` events
1387
+ // still need the entry + its listeners during the drain window. The exited
1388
+ // record lives on in `processes` for listing.
1389
+ void this.releaseProcessTrackingAfterDrain(entry);
1390
+ }
1391
+ async releaseProcessTrackingAfterDrain(entry) {
1392
+ try {
1393
+ await this.drainTrailingProcessOutput(entry);
1394
+ }
1395
+ finally {
1396
+ this.trackedProcesses.delete(entry.pid);
1397
+ this.trackedProcessesById.delete(entry.processId);
1398
+ this.signalRefreshes.delete(entry.pid);
1399
+ this.signalStates.delete(entry.pid);
1400
+ entry.onStdout.clear();
1401
+ entry.onStderr.clear();
1402
+ }
1223
1403
  }
1224
1404
  waitForTrackedProcess(entry) {
1225
1405
  if (entry.exitCode !== null) {
@@ -1370,6 +1550,74 @@ export class NativeSidecarKernelProxy {
1370
1550
  handler(stderr);
1371
1551
  }
1372
1552
  }
1553
+ async readdirRecursiveLocal(fs, path, maxDepth) {
1554
+ const results = [];
1555
+ const queue = [{ path, depth: 0 }];
1556
+ while (queue.length > 0) {
1557
+ const current = queue.shift();
1558
+ if (!current)
1559
+ break;
1560
+ for (const name of await fs.readDir(current.path)) {
1561
+ if (name === "." || name === "..")
1562
+ continue;
1563
+ const child = posixPath.join(current.path, name);
1564
+ const stat = await fs.lstat(child);
1565
+ results.push({
1566
+ name,
1567
+ path: child,
1568
+ isDirectory: stat.isDirectory,
1569
+ isSymbolicLink: stat.isSymbolicLink,
1570
+ size: stat.size,
1571
+ });
1572
+ if (stat.isDirectory &&
1573
+ !stat.isSymbolicLink &&
1574
+ (maxDepth === undefined || current.depth < maxDepth)) {
1575
+ queue.push({ path: child, depth: current.depth + 1 });
1576
+ }
1577
+ }
1578
+ }
1579
+ return results;
1580
+ }
1581
+ async removePathLocal(fs, path, recursive) {
1582
+ const stat = await fs.lstat(path);
1583
+ if (stat.isDirectory && !stat.isSymbolicLink) {
1584
+ if (recursive) {
1585
+ for (const name of await fs.readDir(path)) {
1586
+ if (name === "." || name === "..")
1587
+ continue;
1588
+ await this.removePathLocal(fs, posixPath.join(path, name), true);
1589
+ }
1590
+ }
1591
+ return fs.removeDir(path);
1592
+ }
1593
+ return fs.removeFile(path);
1594
+ }
1595
+ async copyPathLocal(fs, fromPath, toPath, recursive) {
1596
+ const stat = await fs.lstat(fromPath);
1597
+ if (stat.isSymbolicLink) {
1598
+ return fs.symlink(await fs.readlink(fromPath), toPath);
1599
+ }
1600
+ if (stat.isDirectory) {
1601
+ if (!recursive) {
1602
+ throw errnoError("EISDIR", "illegal operation on a directory");
1603
+ }
1604
+ await fs.mkdir(posixPath.dirname(toPath), { recursive: true });
1605
+ if (!(await fs.exists(toPath))) {
1606
+ await fs.createDir(toPath);
1607
+ }
1608
+ await fs.chmod(toPath, stat.mode);
1609
+ await fs.chown(toPath, stat.uid, stat.gid);
1610
+ for (const name of await fs.readDir(fromPath)) {
1611
+ if (name === "." || name === "..")
1612
+ continue;
1613
+ await this.copyPathLocal(fs, posixPath.join(fromPath, name), posixPath.join(toPath, name), true);
1614
+ }
1615
+ return;
1616
+ }
1617
+ await fs.writeFile(toPath, await fs.readFile(fromPath));
1618
+ await fs.chmod(toPath, stat.mode);
1619
+ await fs.chown(toPath, stat.uid, stat.gid);
1620
+ }
1373
1621
  createFilesystemView(includeLocalMounts) {
1374
1622
  return {
1375
1623
  readFile: (path) => this.dispatchRead(path, (mount, relativePath) => mount.fs.readFile(relativePath), includeLocalMounts),
@@ -1580,7 +1828,8 @@ export class NativeSidecarKernelProxy {
1580
1828
  }
1581
1829
  return this.dispatchNativeRead(path);
1582
1830
  }
1583
- dispatchNativeRead(path) {
1831
+ async dispatchNativeRead(path) {
1832
+ await this.waitForMountReconfigure();
1584
1833
  return this.client.readFile(this.session, this.vm, path);
1585
1834
  }
1586
1835
  async dispatchWrite(path, handler, nativeHandler, includeLocalMounts = true) {
@@ -1591,6 +1840,7 @@ export class NativeSidecarKernelProxy {
1591
1840
  await handler(local.mount, local.relativePath);
1592
1841
  return;
1593
1842
  }
1843
+ await this.waitForMountReconfigure();
1594
1844
  await nativeHandler();
1595
1845
  }
1596
1846
  resolveLocalMount(path) {
@@ -1666,6 +1916,10 @@ function buildCommandMap(commandGuestPaths) {
1666
1916
  ["node", "node"],
1667
1917
  ["npm", "node"],
1668
1918
  ["npx", "node"],
1919
+ // `python` / `python3` are served by the embedded Pyodide runtime,
1920
+ // mirroring how `node` is served by the embedded V8 runtime.
1921
+ ["python", "python"],
1922
+ ["python3", "python"],
1669
1923
  ]);
1670
1924
  for (const name of commandGuestPaths.keys()) {
1671
1925
  commands.set(name, "wasmvm");
@@ -1750,7 +2004,7 @@ function socketLookupKey(kind, request) {
1750
2004
  path: request.path ?? null,
1751
2005
  });
1752
2006
  }
1753
- export { NATIVE_SIDECAR_FRAME_TIMEOUT_MS, SidecarProcess, SidecarEventBufferOverflow, SidecarProcessError, SidecarProcessExited, } from "./native-process-client.js";
2007
+ export { NATIVE_SIDECAR_FRAME_TIMEOUT_MS, NativeSidecarProcessClient, SidecarEventBufferOverflow, SidecarProcess, SidecarProcessError, SidecarProcessExited, } from "./native-process-client.js";
1754
2008
  export class AgentOsSidecarVmHandle {
1755
2009
  client;
1756
2010
  sessionId;