@rivet-dev/agentos-core 0.0.0-integrate-dylib-into-main.815fcda → 0.0.0-main.2f99c77

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 +5 -8
  2. package/dist/agent-os.d.ts +176 -48
  3. package/dist/agent-os.js +829 -548
  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 +27 -11
  85. package/dist/runtime-compat.js +133 -147
  86. package/dist/runtime.d.ts +37 -7
  87. package/dist/sandbox.d.ts +112 -0
  88. package/dist/sandbox.js +264 -0
  89. package/dist/sidecar/agentos-protocol.d.ts +99 -1
  90. package/dist/sidecar/agentos-protocol.js +155 -18
  91. package/dist/sidecar/native-process-client.d.ts +6 -2
  92. package/dist/sidecar/native-process-client.js +8 -1
  93. package/dist/sidecar/permissions.d.ts +1 -1
  94. package/dist/sidecar/permissions.js +4 -4
  95. package/dist/sidecar/rpc-client.d.ts +86 -6
  96. package/dist/sidecar/rpc-client.js +388 -93
  97. package/dist/types.d.ts +10 -3
  98. package/package.json +40 -33
  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,17 @@ 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;
217
+ // Mutable: runtime `linkSoftware` appends via `registerLinkedPackage` so
218
+ // later mount reconfigures resend linked packages too, not just boot ones.
219
+ packages;
220
+ packagesMountAt;
221
+ toolShimCommands;
185
222
  commandDrivers;
186
223
  onWasmCommandResolved;
187
224
  onDispose;
@@ -199,6 +236,7 @@ export class NativeSidecarKernelProxy {
199
236
  zombieTimerCountRefresh = null;
200
237
  disposed = false;
201
238
  pumpError = null;
239
+ mountReconfigurePromise = null;
202
240
  nextSyntheticPid = SYNTHETIC_PID_BASE;
203
241
  eventPumpAbortController = new AbortController();
204
242
  eventPump;
@@ -206,10 +244,20 @@ export class NativeSidecarKernelProxy {
206
244
  this.client = options.client;
207
245
  this.session = options.session;
208
246
  this.vm = options.vm;
247
+ this.ownsClient = options.ownsClient ?? true;
209
248
  this.env = { ...options.env };
210
249
  this.cwd = options.cwd;
211
250
  this.defaultExecCwd = options.defaultExecCwd;
212
251
  this.localMounts = [...options.localMounts].sort((left, right) => right.path.length - left.path.length);
252
+ const localMountPaths = new Set(this.localMounts.map((mount) => mount.path));
253
+ this.baseSidecarMounts = options.sidecarMounts.filter((mount) => mount.plugin.id !== "js_bridge" ||
254
+ !localMountPaths.has(posixPath.normalize(mount.guestPath)));
255
+ this.permissions = options.permissions;
256
+ this.commandPermissions = options.commandPermissions;
257
+ this.loopbackExemptPorts = options.loopbackExemptPorts;
258
+ this.packages = options.packages ? [...options.packages] : [];
259
+ this.packagesMountAt = options.packagesMountAt;
260
+ this.toolShimCommands = options.toolShimCommands;
213
261
  this.commandDrivers = buildCommandMap(options.commandGuestPaths);
214
262
  this.onWasmCommandResolved = options.onWasmCommandResolved;
215
263
  this.onDispose = options.onDispose;
@@ -233,12 +281,24 @@ export class NativeSidecarKernelProxy {
233
281
  this.commandDrivers.set(name, "wasmvm");
234
282
  }
235
283
  }
284
+ /**
285
+ * Record a runtime-linked package (`linkSoftware`) so mount reconfigures
286
+ * resend it. Rust `configure_vm` rebuilds the whole VM configuration from
287
+ * each payload, so a linked package omitted here would be silently
288
+ * unprojected from `/opt/agentos` by the next `mountFs`/`unmountFs`.
289
+ */
290
+ registerLinkedPackage(descriptor) {
291
+ if (!this.packages.some((pkg) => pkg.path === descriptor.path)) {
292
+ this.packages.push({ path: descriptor.path });
293
+ }
294
+ }
236
295
  async dispose() {
237
296
  if (this.disposed) {
238
297
  return;
239
298
  }
240
299
  this.disposed = true;
241
300
  this.eventPumpAbortController.abort();
301
+ await this.mountReconfigurePromise?.catch(() => { });
242
302
  const liveProcesses = [...this.trackedProcesses.values()].filter((entry) => entry.exitCode === null);
243
303
  await Promise.allSettled(liveProcesses.map((entry) => this.signalProcess(entry, 15)));
244
304
  await this.client.disposeVm(this.session, this.vm).catch(() => { });
@@ -251,9 +311,42 @@ export class NativeSidecarKernelProxy {
251
311
  this.finishProcess(entry, 143);
252
312
  }
253
313
  }
254
- await this.client.dispose().catch(() => { });
314
+ // Only tear down the shared sidecar process when this proxy owns it. VMs
315
+ // leased from an `AgentOsSidecar` handle share one process, which is
316
+ // disposed when the handle is disposed.
317
+ if (this.ownsClient) {
318
+ await this.client.dispose().catch(() => { });
319
+ }
255
320
  await this.eventPump.catch(() => { });
256
321
  await this.onDispose?.().catch(() => { });
322
+ // Drop all per-VM tracking state so a disposed proxy retains nothing.
323
+ for (const entry of this.trackedProcesses.values()) {
324
+ entry.onStdout.clear();
325
+ entry.onStderr.clear();
326
+ }
327
+ this.trackedProcesses.clear();
328
+ this.trackedProcessesById.clear();
329
+ this.signalStates.clear();
330
+ this.signalRefreshes.clear();
331
+ this.localMounts.length = 0;
332
+ }
333
+ /** Test-only snapshot of the per-VM tracking collection sizes. */
334
+ __trackingSizesForTest() {
335
+ return {
336
+ trackedProcesses: this.trackedProcesses.size,
337
+ trackedProcessesById: this.trackedProcessesById.size,
338
+ signalStates: this.signalStates.size,
339
+ signalRefreshes: this.signalRefreshes.size,
340
+ localMounts: this.localMounts.length,
341
+ };
342
+ }
343
+ /** Test-only handle to a tracked entry so its listener Sets can be inspected. */
344
+ __trackedEntryForTest(pid) {
345
+ return this.trackedProcesses.get(pid);
346
+ }
347
+ /** Test-only join point for in-flight signal-state refreshes. */
348
+ async __awaitSignalRefreshesForTest() {
349
+ await Promise.allSettled([...this.signalRefreshes.values()]);
257
350
  }
258
351
  async exec(command, options) {
259
352
  if (!this.commands.has("sh")) {
@@ -263,9 +356,6 @@ export class NativeSidecarKernelProxy {
263
356
  const stderrChunks = [];
264
357
  const effectiveCwd = options?.cwd ?? this.defaultExecCwd ?? this.cwd;
265
358
  const parsedCommand = parseSimpleExecCommand(command);
266
- const resolveExecPath = (targetPath) => targetPath.startsWith("/")
267
- ? posixPath.normalize(targetPath)
268
- : posixPath.normalize(posixPath.join(effectiveCwd, targetPath));
269
359
  const runAndCapture = async (proc, stdinOverride, readExitCode) => {
270
360
  if (stdinOverride !== undefined) {
271
361
  proc.writeStdin(stdinOverride);
@@ -301,43 +391,6 @@ export class NativeSidecarKernelProxy {
301
391
  stderr: Buffer.concat(stderrChunks.map((chunk) => Buffer.from(chunk))).toString("utf8"),
302
392
  };
303
393
  };
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
394
  const parsedCommandDriver = parsedCommand
342
395
  ? this.commands.get(parsedCommand[0])
343
396
  : undefined;
@@ -447,11 +500,15 @@ export class NativeSidecarKernelProxy {
447
500
  processId,
448
501
  command: spawnCommand,
449
502
  args: spawnArgs,
450
- driver: spawnCommand === "node" ? "node" : "wasmvm",
503
+ driver: spawnCommand === "node"
504
+ ? "node"
505
+ : spawnCommand === "python" || spawnCommand === "python3"
506
+ ? "python"
507
+ : "wasmvm",
451
508
  cwd: options?.cwd ?? this.cwd,
452
509
  env: {
453
510
  ...(options?.env ?? {}),
454
- ...(options?.streamStdin ? { SECURE_EXEC_KEEP_STDIN_OPEN: "1" } : {}),
511
+ ...(options?.streamStdin ? { AGENTOS_KEEP_STDIN_OPEN: "1" } : {}),
455
512
  },
456
513
  startTime: Date.now(),
457
514
  exitTime: null,
@@ -479,16 +536,16 @@ export class NativeSidecarKernelProxy {
479
536
  pid,
480
537
  writeStdin: (data) => {
481
538
  if (entry.exitCode !== null) {
482
- return;
539
+ return Promise.resolve();
483
540
  }
484
541
  entry.pendingStdin.push(data);
485
- void this.flushPendingStdin(entry).catch((error) => {
542
+ return this.flushPendingStdin(entry).catch((error) => {
486
543
  this.handleBackgroundProcessError(entry, error);
487
544
  });
488
545
  },
489
546
  closeStdin: () => {
490
547
  entry.pendingCloseStdin = true;
491
- void this.closeTrackedStdin(entry).catch((error) => {
548
+ return this.closeTrackedStdin(entry).catch((error) => {
492
549
  this.handleBackgroundProcessError(entry, error);
493
550
  });
494
551
  },
@@ -532,7 +589,6 @@ export class NativeSidecarKernelProxy {
532
589
  const args = options?.args ??
533
590
  (command === "sh" || command === "/bin/sh" ? ["-i"] : []);
534
591
  const synthesizePrompt = !options?.command && !options?.args;
535
- const autoCloseExplicitCommandStdin = Boolean(options?.command) && !["sh", "/bin/sh", "bash"].includes(command);
536
592
  const promptText = "sh-0.4$ ";
537
593
  const textEncoder = new TextEncoder();
538
594
  const textDecoder = new TextDecoder();
@@ -542,14 +598,18 @@ export class NativeSidecarKernelProxy {
542
598
  .replace(/\u001b\[[0-9;]*m/g, "")
543
599
  .replace(/^.*WARN could not retrieve pid for child process\n?/gm, "")
544
600
  .replace(/^ProcessExitError:.*\n(?:\s+at .*\n)*/gm, "");
601
+ const sanitizeNativeShellOutput = (chunk) => {
602
+ const text = textDecoder.decode(chunk);
603
+ const sanitized = text.replace(/^.*WARN could not retrieve pid for child process\n?/gm, "");
604
+ return sanitized.length > 0 ? textEncoder.encode(sanitized) : null;
605
+ };
545
606
  let bufferedInput = "";
546
607
  let bufferedCommand = "";
547
608
  let activeForegroundProcess = null;
548
- let shellEnv = { ...(options?.env ?? {}) };
609
+ let shellEnv = { ...(options?.env ?? {}), AGENTOS_EXEC_TTY: "1" };
549
610
  let shellCwd = options?.cwd ?? this.cwd;
550
611
  let syntheticCommandQueue = Promise.resolve();
551
612
  let promptTimer = null;
552
- let closeStdinTimer = null;
553
613
  let commandInFlight = false;
554
614
  let syntheticCursorAtLineStart = true;
555
615
  const syntheticPid = this.nextSyntheticPid++;
@@ -564,12 +624,6 @@ export class NativeSidecarKernelProxy {
564
624
  promptTimer = null;
565
625
  }
566
626
  };
567
- const clearCloseStdinTimer = () => {
568
- if (closeStdinTimer !== null) {
569
- clearTimeout(closeStdinTimer);
570
- closeStdinTimer = null;
571
- }
572
- };
573
627
  const normalizeSyntheticTerminalText = (text) => text.replace(/\r?\n/g, "\r\n");
574
628
  const updateSyntheticCursor = (text) => {
575
629
  if (!text) {
@@ -665,15 +719,36 @@ export class NativeSidecarKernelProxy {
665
719
  }
666
720
  return parsed;
667
721
  };
668
- const writeForegroundInput = (proc, data) => {
722
+ const writeForegroundInput = async (proc, data) => {
669
723
  if (typeof data === "string") {
670
724
  for (const character of data) {
671
- proc.writeStdin(character);
725
+ await proc.writeStdin(character);
672
726
  }
673
727
  return;
674
728
  }
675
729
  for (const byte of data) {
676
- proc.writeStdin(new Uint8Array([byte]));
730
+ await proc.writeStdin(new Uint8Array([byte]));
731
+ }
732
+ };
733
+ const appendSyntheticInput = (input) => {
734
+ for (const character of input) {
735
+ if (character === "\u0004") {
736
+ continue;
737
+ }
738
+ if (character === "\b" || character === "\u007f") {
739
+ if (bufferedInput.length > 0) {
740
+ bufferedInput = bufferedInput.slice(0, -1);
741
+ emitSyntheticTerminal("\b \b");
742
+ }
743
+ continue;
744
+ }
745
+ bufferedInput += character;
746
+ if (character === "\n") {
747
+ emitSyntheticTerminal("\n");
748
+ }
749
+ else if (character >= " ") {
750
+ emitSyntheticTerminal(character);
751
+ }
677
752
  }
678
753
  };
679
754
  let onData = null;
@@ -685,7 +760,7 @@ export class NativeSidecarKernelProxy {
685
760
  schedulePrompt(0);
686
761
  return {
687
762
  pid: syntheticPid,
688
- write(data) {
763
+ async write(data) {
689
764
  if (syntheticExitCode !== null) {
690
765
  return;
691
766
  }
@@ -696,13 +771,13 @@ export class NativeSidecarKernelProxy {
696
771
  if (rawText.includes("\u0003")) {
697
772
  const [beforeInterrupt] = rawText.split("\u0003");
698
773
  if (beforeInterrupt) {
699
- writeForegroundInput(activeForegroundProcess, beforeInterrupt);
774
+ await writeForegroundInput(activeForegroundProcess, beforeInterrupt);
700
775
  }
701
776
  emitSyntheticTerminal("^C\n");
702
777
  activeForegroundProcess.kill(2);
703
778
  return;
704
779
  }
705
- writeForegroundInput(activeForegroundProcess, data);
780
+ await writeForegroundInput(activeForegroundProcess, data);
706
781
  return;
707
782
  }
708
783
  const rawText = typeof data === "string"
@@ -725,7 +800,7 @@ export class NativeSidecarKernelProxy {
725
800
  finishSyntheticShell(0);
726
801
  return;
727
802
  }
728
- bufferedInput += text.replace(/\u0004/g, "");
803
+ appendSyntheticInput(text.replace(/\r\n/g, "\n").replace(/\r/g, "\n"));
729
804
  while (true) {
730
805
  const newlineIndex = bufferedInput.indexOf("\n");
731
806
  if (newlineIndex < 0) {
@@ -735,7 +810,6 @@ export class NativeSidecarKernelProxy {
735
810
  .slice(0, newlineIndex)
736
811
  .replace(/\r$/, "");
737
812
  bufferedInput = bufferedInput.slice(newlineIndex + 1);
738
- emitSyntheticStdout(`${line}\n`);
739
813
  const nextCommand = bufferedCommand
740
814
  ? `${bufferedCommand}\n${line}`
741
815
  : line;
@@ -834,20 +908,33 @@ export class NativeSidecarKernelProxy {
834
908
  };
835
909
  }
836
910
  const proc = this.spawn(command, args, {
837
- env: options?.env,
911
+ env: {
912
+ ...(options?.env ?? {}),
913
+ ...(options?.cols ? { COLUMNS: String(Math.trunc(options.cols)) } : {}),
914
+ ...(options?.rows ? { LINES: String(Math.trunc(options.rows)) } : {}),
915
+ AGENTOS_EXEC_TTY: "1",
916
+ },
838
917
  cwd: options?.cwd,
839
918
  streamStdin: true,
840
919
  onStdout: (chunk) => {
920
+ const sanitized = sanitizeNativeShellOutput(chunk);
921
+ if (!sanitized) {
922
+ return;
923
+ }
841
924
  for (const handler of stdoutHandlers) {
842
- handler(chunk);
925
+ handler(sanitized);
843
926
  }
844
927
  if (commandInFlight) {
845
928
  schedulePrompt(120);
846
929
  }
847
930
  },
848
931
  onStderr: (chunk) => {
932
+ const sanitized = sanitizeNativeShellOutput(chunk);
933
+ if (!sanitized) {
934
+ return;
935
+ }
849
936
  for (const handler of stderrHandlers) {
850
- handler(chunk);
937
+ handler(sanitized);
851
938
  }
852
939
  if (commandInFlight) {
853
940
  schedulePrompt(120);
@@ -856,18 +943,11 @@ export class NativeSidecarKernelProxy {
856
943
  });
857
944
  return {
858
945
  pid: proc.pid,
859
- write(data) {
946
+ async write(data) {
860
947
  if (synthesizePrompt) {
861
948
  return;
862
949
  }
863
- proc.writeStdin(data);
864
- if (autoCloseExplicitCommandStdin) {
865
- clearCloseStdinTimer();
866
- closeStdinTimer = setTimeout(() => {
867
- closeStdinTimer = null;
868
- proc.closeStdin();
869
- }, 100);
870
- }
950
+ await proc.writeStdin(data);
871
951
  if (synthesizePrompt &&
872
952
  typeof data === "string" &&
873
953
  (data.includes("\n") || data.includes("\r"))) {
@@ -881,11 +961,18 @@ export class NativeSidecarKernelProxy {
881
961
  set onData(handler) {
882
962
  onData = handler;
883
963
  },
884
- resize() {
885
- // The current stdio-native path is process-backed rather than PTY-backed.
964
+ resize: (cols, rows) => {
965
+ const entry = this.trackedProcesses.get(proc.pid);
966
+ if (!entry || entry.exitCode !== null) {
967
+ return;
968
+ }
969
+ void entry.startPromise
970
+ .then(() => this.client.resizePty(this.session, this.vm, entry.processId, Math.trunc(cols), Math.trunc(rows)))
971
+ .catch((error) => {
972
+ this.handleBackgroundProcessError(entry, error);
973
+ });
886
974
  },
887
975
  kill(signal) {
888
- clearCloseStdinTimer();
889
976
  clearPromptTimer();
890
977
  proc.kill(signal);
891
978
  },
@@ -985,12 +1072,41 @@ export class NativeSidecarKernelProxy {
985
1072
  const entries = await this.client.readdir(this.session, this.vm, path);
986
1073
  return [...new Set([...entries, ...this.mountedChildNames(path)])].sort((a, b) => a.localeCompare(b));
987
1074
  }
1075
+ async readdirRecursive(path, options) {
1076
+ const local = this.resolveLocalMount(path);
1077
+ if (local) {
1078
+ return this.readdirRecursiveLocal(local.mount.fs, local.relativePath, options?.maxDepth);
1079
+ }
1080
+ return (await this.client.readdirRecursive(this.session, this.vm, path, options)).map((entry) => ({ ...entry, size: Number(entry.size) }));
1081
+ }
988
1082
  async removeFile(path) {
989
1083
  return this.dispatchWrite(path, (mount, relativePath) => mount.fs.removeFile(relativePath), () => this.client.removeFile(this.session, this.vm, path));
990
1084
  }
991
1085
  async removeDir(path) {
992
1086
  return this.dispatchWrite(path, (mount, relativePath) => mount.fs.removeDir(relativePath), () => this.client.removeDir(this.session, this.vm, path));
993
1087
  }
1088
+ async removePath(path, options) {
1089
+ return this.dispatchWrite(path, (mount, relativePath) => this.removePathLocal(mount.fs, relativePath, options?.recursive ?? false), () => this.client.removePath(this.session, this.vm, path, {
1090
+ recursive: options?.recursive ?? false,
1091
+ }));
1092
+ }
1093
+ async copyPath(fromPath, toPath, options) {
1094
+ const from = this.resolveLocalMount(fromPath);
1095
+ const to = this.resolveLocalMount(toPath);
1096
+ if (!!from !== !!to) {
1097
+ throw errnoError("EXDEV", "cross-device link not permitted");
1098
+ }
1099
+ if (from && to) {
1100
+ if (from.mount.path !== to.mount.path) {
1101
+ throw errnoError("EXDEV", "cross-device link not permitted");
1102
+ }
1103
+ this.assertLocalWritable(to.mount);
1104
+ return this.copyPathLocal(from.mount.fs, from.relativePath, to.relativePath, options?.recursive ?? false);
1105
+ }
1106
+ return this.client.copyPath(this.session, this.vm, fromPath, toPath, {
1107
+ recursive: options?.recursive ?? false,
1108
+ });
1109
+ }
994
1110
  async rename(oldPath, newPath) {
995
1111
  const from = this.resolveLocalMount(oldPath);
996
1112
  const to = this.resolveLocalMount(newPath);
@@ -1006,19 +1122,93 @@ export class NativeSidecarKernelProxy {
1006
1122
  }
1007
1123
  return this.client.rename(this.session, this.vm, oldPath, newPath);
1008
1124
  }
1125
+ async movePath(oldPath, newPath) {
1126
+ const from = this.resolveLocalMount(oldPath);
1127
+ const to = this.resolveLocalMount(newPath);
1128
+ if (!!from !== !!to) {
1129
+ throw errnoError("EXDEV", "cross-device link not permitted");
1130
+ }
1131
+ if (from && to) {
1132
+ if (from.mount.path !== to.mount.path) {
1133
+ throw errnoError("EXDEV", "cross-device link not permitted");
1134
+ }
1135
+ this.assertLocalWritable(from.mount);
1136
+ return from.mount.fs.rename(from.relativePath, to.relativePath);
1137
+ }
1138
+ return this.client.movePath(this.session, this.vm, oldPath, newPath);
1139
+ }
1009
1140
  mountFs(path, driver, options) {
1010
1141
  this.localMounts.unshift({
1011
1142
  path: posixPath.normalize(path),
1012
1143
  fs: driver,
1013
1144
  readOnly: options?.readOnly ?? false,
1145
+ sidecarMount: options?.sidecarMount,
1014
1146
  });
1015
1147
  this.localMounts.sort((left, right) => right.path.length - left.path.length);
1148
+ // Resolves once the sidecar has the mount; a swallowed rejection here
1149
+ // used to turn reconfigure failures into silently missing guest mounts.
1150
+ // The local catch only guards callers that drop the promise — awaiting
1151
+ // callers still observe the rejection.
1152
+ const applied = this.reconfigureSidecarMounts();
1153
+ applied.catch(() => { });
1154
+ return applied;
1016
1155
  }
1017
1156
  unmountFs(path) {
1018
1157
  const normalized = posixPath.normalize(path);
1019
1158
  const index = this.localMounts.findIndex((mount) => mount.path === normalized);
1020
- if (index >= 0) {
1021
- this.localMounts.splice(index, 1);
1159
+ if (index < 0) {
1160
+ return Promise.resolve();
1161
+ }
1162
+ this.localMounts.splice(index, 1);
1163
+ const applied = this.reconfigureSidecarMounts();
1164
+ applied.catch(() => { });
1165
+ return applied;
1166
+ }
1167
+ desiredSidecarMounts() {
1168
+ return [
1169
+ ...this.baseSidecarMounts,
1170
+ ...this.localMounts.map((mount) => mount.sidecarMount ?? {
1171
+ guestPath: mount.path,
1172
+ readOnly: mount.readOnly,
1173
+ plugin: {
1174
+ id: "js_bridge",
1175
+ config: {},
1176
+ },
1177
+ }),
1178
+ ];
1179
+ }
1180
+ reconfigureSidecarMounts() {
1181
+ const run = async () => {
1182
+ if (this.disposed) {
1183
+ return;
1184
+ }
1185
+ // Rust `configure_vm` rebuilds the whole VM configuration from this
1186
+ // payload, so resend the boot packages / tool shim commands too —
1187
+ // omitting them here strips the `/opt/agentos` projections and tool
1188
+ // shims from the VM as a side effect of a runtime mount change.
1189
+ await this.client.configureVm(this.session, this.vm, {
1190
+ mounts: this.desiredSidecarMounts(),
1191
+ permissions: this.permissions,
1192
+ commandPermissions: this.commandPermissions,
1193
+ loopbackExemptPorts: this.loopbackExemptPorts,
1194
+ packages: this.packages,
1195
+ packagesMountAt: this.packagesMountAt,
1196
+ toolShimCommands: this.toolShimCommands,
1197
+ });
1198
+ };
1199
+ const previous = this.mountReconfigurePromise ?? Promise.resolve();
1200
+ const next = previous.then(run, run);
1201
+ const tracked = next.finally(() => {
1202
+ if (this.mountReconfigurePromise === tracked) {
1203
+ this.mountReconfigurePromise = null;
1204
+ }
1205
+ });
1206
+ this.mountReconfigurePromise = tracked;
1207
+ return tracked;
1208
+ }
1209
+ async waitForMountReconfigure() {
1210
+ if (this.mountReconfigurePromise) {
1211
+ await this.mountReconfigurePromise;
1022
1212
  }
1023
1213
  }
1024
1214
  snapshotProcesses() {
@@ -1132,6 +1322,7 @@ export class NativeSidecarKernelProxy {
1132
1322
  }
1133
1323
  }
1134
1324
  async startTrackedProcess(entry) {
1325
+ await this.waitForMountReconfigure();
1135
1326
  const started = await this.client.execute(this.session, this.vm, {
1136
1327
  processId: entry.processId,
1137
1328
  command: entry.command,
@@ -1157,11 +1348,13 @@ export class NativeSidecarKernelProxy {
1157
1348
  }
1158
1349
  }
1159
1350
  async runEventPump() {
1351
+ // Scope the pump to THIS VM's ownership so multiple proxies can share one
1352
+ // sidecar process: events for other VMs stay buffered for their own pumps
1353
+ // rather than being consumed (and dropped) here.
1354
+ const vmId = this.vm.vmId;
1160
1355
  while (!this.disposed) {
1161
1356
  try {
1162
- const event = await this.client.waitForEvent({ any: true }, undefined, {
1163
- signal: this.eventPumpAbortController.signal,
1164
- });
1357
+ const event = await this.client.waitForEvent((frame) => frame.ownership.scope === "vm" && frame.ownership.vm_id === vmId, undefined, { signal: this.eventPumpAbortController.signal });
1165
1358
  if (event.payload.type === "process_output") {
1166
1359
  const entry = this.trackedProcessesById.get(event.payload.process_id);
1167
1360
  if (!entry) {
@@ -1188,8 +1381,11 @@ export class NativeSidecarKernelProxy {
1188
1381
  continue;
1189
1382
  }
1190
1383
  void this.refreshProcessSnapshot().catch(() => { });
1191
- this.signalRefreshes.delete(entry.pid);
1192
1384
  this.finishProcess(entry, event.payload.exit_code);
1385
+ continue;
1386
+ }
1387
+ if (event.payload.type === "structured") {
1388
+ logStructuredSidecarEvent(event.payload.name, event.payload.detail);
1193
1389
  }
1194
1390
  }
1195
1391
  catch (error) {
@@ -1220,6 +1416,25 @@ export class NativeSidecarKernelProxy {
1220
1416
  entry.exitTime = Date.now();
1221
1417
  this.updateTrackedProcessSnapshot(entry);
1222
1418
  entry.resolveWait(exitCode);
1419
+ // Release per-process tracking now that the process has terminated so these
1420
+ // maps/Sets don't grow without bound. Defer the release until trailing
1421
+ // output has drained: `wait()`'s drain and late `process_output` events
1422
+ // still need the entry + its listeners during the drain window. The exited
1423
+ // record lives on in `processes` for listing.
1424
+ void this.releaseProcessTrackingAfterDrain(entry);
1425
+ }
1426
+ async releaseProcessTrackingAfterDrain(entry) {
1427
+ try {
1428
+ await this.drainTrailingProcessOutput(entry);
1429
+ }
1430
+ finally {
1431
+ this.trackedProcesses.delete(entry.pid);
1432
+ this.trackedProcessesById.delete(entry.processId);
1433
+ this.signalRefreshes.delete(entry.pid);
1434
+ this.signalStates.delete(entry.pid);
1435
+ entry.onStdout.clear();
1436
+ entry.onStderr.clear();
1437
+ }
1223
1438
  }
1224
1439
  waitForTrackedProcess(entry) {
1225
1440
  if (entry.exitCode !== null) {
@@ -1370,6 +1585,74 @@ export class NativeSidecarKernelProxy {
1370
1585
  handler(stderr);
1371
1586
  }
1372
1587
  }
1588
+ async readdirRecursiveLocal(fs, path, maxDepth) {
1589
+ const results = [];
1590
+ const queue = [{ path, depth: 0 }];
1591
+ while (queue.length > 0) {
1592
+ const current = queue.shift();
1593
+ if (!current)
1594
+ break;
1595
+ for (const name of await fs.readDir(current.path)) {
1596
+ if (name === "." || name === "..")
1597
+ continue;
1598
+ const child = posixPath.join(current.path, name);
1599
+ const stat = await fs.lstat(child);
1600
+ results.push({
1601
+ name,
1602
+ path: child,
1603
+ isDirectory: stat.isDirectory,
1604
+ isSymbolicLink: stat.isSymbolicLink,
1605
+ size: stat.size,
1606
+ });
1607
+ if (stat.isDirectory &&
1608
+ !stat.isSymbolicLink &&
1609
+ (maxDepth === undefined || current.depth < maxDepth)) {
1610
+ queue.push({ path: child, depth: current.depth + 1 });
1611
+ }
1612
+ }
1613
+ }
1614
+ return results;
1615
+ }
1616
+ async removePathLocal(fs, path, recursive) {
1617
+ const stat = await fs.lstat(path);
1618
+ if (stat.isDirectory && !stat.isSymbolicLink) {
1619
+ if (recursive) {
1620
+ for (const name of await fs.readDir(path)) {
1621
+ if (name === "." || name === "..")
1622
+ continue;
1623
+ await this.removePathLocal(fs, posixPath.join(path, name), true);
1624
+ }
1625
+ }
1626
+ return fs.removeDir(path);
1627
+ }
1628
+ return fs.removeFile(path);
1629
+ }
1630
+ async copyPathLocal(fs, fromPath, toPath, recursive) {
1631
+ const stat = await fs.lstat(fromPath);
1632
+ if (stat.isSymbolicLink) {
1633
+ return fs.symlink(await fs.readlink(fromPath), toPath);
1634
+ }
1635
+ if (stat.isDirectory) {
1636
+ if (!recursive) {
1637
+ throw errnoError("EISDIR", "illegal operation on a directory");
1638
+ }
1639
+ await fs.mkdir(posixPath.dirname(toPath), { recursive: true });
1640
+ if (!(await fs.exists(toPath))) {
1641
+ await fs.createDir(toPath);
1642
+ }
1643
+ await fs.chmod(toPath, stat.mode);
1644
+ await fs.chown(toPath, stat.uid, stat.gid);
1645
+ for (const name of await fs.readDir(fromPath)) {
1646
+ if (name === "." || name === "..")
1647
+ continue;
1648
+ await this.copyPathLocal(fs, posixPath.join(fromPath, name), posixPath.join(toPath, name), true);
1649
+ }
1650
+ return;
1651
+ }
1652
+ await fs.writeFile(toPath, await fs.readFile(fromPath));
1653
+ await fs.chmod(toPath, stat.mode);
1654
+ await fs.chown(toPath, stat.uid, stat.gid);
1655
+ }
1373
1656
  createFilesystemView(includeLocalMounts) {
1374
1657
  return {
1375
1658
  readFile: (path) => this.dispatchRead(path, (mount, relativePath) => mount.fs.readFile(relativePath), includeLocalMounts),
@@ -1580,7 +1863,8 @@ export class NativeSidecarKernelProxy {
1580
1863
  }
1581
1864
  return this.dispatchNativeRead(path);
1582
1865
  }
1583
- dispatchNativeRead(path) {
1866
+ async dispatchNativeRead(path) {
1867
+ await this.waitForMountReconfigure();
1584
1868
  return this.client.readFile(this.session, this.vm, path);
1585
1869
  }
1586
1870
  async dispatchWrite(path, handler, nativeHandler, includeLocalMounts = true) {
@@ -1591,6 +1875,7 @@ export class NativeSidecarKernelProxy {
1591
1875
  await handler(local.mount, local.relativePath);
1592
1876
  return;
1593
1877
  }
1878
+ await this.waitForMountReconfigure();
1594
1879
  await nativeHandler();
1595
1880
  }
1596
1881
  resolveLocalMount(path) {
@@ -1666,6 +1951,10 @@ function buildCommandMap(commandGuestPaths) {
1666
1951
  ["node", "node"],
1667
1952
  ["npm", "node"],
1668
1953
  ["npx", "node"],
1954
+ // `python` / `python3` are served by the embedded Pyodide runtime,
1955
+ // mirroring how `node` is served by the embedded V8 runtime.
1956
+ ["python", "python"],
1957
+ ["python3", "python"],
1669
1958
  ]);
1670
1959
  for (const name of commandGuestPaths.keys()) {
1671
1960
  commands.set(name, "wasmvm");
@@ -1703,21 +1992,27 @@ function isMissingHostProcessError(error) {
1703
1992
  function errnoError(code, message) {
1704
1993
  return Object.assign(new Error(`${code}: ${message}`), { code });
1705
1994
  }
1995
+ // VirtualStat is a numeric, Node-default-shaped view: u64 fields above
1996
+ // Number.MAX_SAFE_INTEGER lose precision here, same as Node's non-bigint
1997
+ // fs.stat on the host.
1706
1998
  function toVirtualStat(stat) {
1707
1999
  return {
1708
2000
  mode: stat.mode,
1709
- size: stat.size,
1710
- blocks: stat.blocks,
1711
- dev: stat.dev,
1712
- rdev: stat.rdev,
2001
+ size: Number(stat.size),
2002
+ sizeExact: stat.size,
2003
+ blocks: Number(stat.blocks),
2004
+ dev: Number(stat.dev),
2005
+ rdev: Number(stat.rdev),
1713
2006
  isDirectory: stat.is_directory,
1714
2007
  isSymbolicLink: stat.is_symbolic_link,
1715
2008
  atimeMs: stat.atime_ms,
1716
2009
  mtimeMs: stat.mtime_ms,
1717
2010
  ctimeMs: stat.ctime_ms,
1718
2011
  birthtimeMs: stat.birthtime_ms,
1719
- ino: stat.ino,
1720
- nlink: stat.nlink,
2012
+ ino: Number(stat.ino),
2013
+ inoExact: stat.ino,
2014
+ nlink: Number(stat.nlink),
2015
+ nlinkExact: stat.nlink,
1721
2016
  uid: stat.uid,
1722
2017
  gid: stat.gid,
1723
2018
  };
@@ -1750,7 +2045,7 @@ function socketLookupKey(kind, request) {
1750
2045
  path: request.path ?? null,
1751
2046
  });
1752
2047
  }
1753
- export { NATIVE_SIDECAR_FRAME_TIMEOUT_MS, SidecarProcess, SidecarEventBufferOverflow, SidecarProcessError, SidecarProcessExited, } from "./native-process-client.js";
2048
+ export { NativeSidecarProcessClient, SidecarEventBufferOverflow, SidecarProcess, SidecarProcessError, SidecarProcessExited, } from "./native-process-client.js";
1754
2049
  export class AgentOsSidecarVmHandle {
1755
2050
  client;
1756
2051
  sessionId;