@rivet-dev/agentos-runtime-core 0.0.0-codex-actor-plugin-contract-coverage.115ca8a → 0.0.0-eve-integration.9df6f34

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 (240) hide show
  1. package/README.md +1 -1
  2. package/commands/[ +0 -0
  3. package/commands/_stubs +0 -0
  4. package/commands/acl_tools +0 -0
  5. package/commands/arch +0 -0
  6. package/commands/attr +0 -0
  7. package/commands/awk +0 -0
  8. package/commands/b2sum +0 -0
  9. package/commands/base32 +0 -0
  10. package/commands/base64 +0 -0
  11. package/commands/basename +0 -0
  12. package/commands/basenc +0 -0
  13. package/commands/bash +0 -0
  14. package/commands/bc +0 -0
  15. package/commands/cat +0 -0
  16. package/commands/chacl +0 -0
  17. package/commands/chcon +0 -0
  18. package/commands/chgrp +0 -0
  19. package/commands/chmod +0 -0
  20. package/commands/chown +0 -0
  21. package/commands/chroot +0 -0
  22. package/commands/cksum +0 -0
  23. package/commands/cmp +0 -0
  24. package/commands/column +0 -0
  25. package/commands/comm +0 -0
  26. package/commands/cp +0 -0
  27. package/commands/curl +0 -0
  28. package/commands/cut +0 -0
  29. package/commands/date +0 -0
  30. package/commands/dd +0 -0
  31. package/commands/df +0 -0
  32. package/commands/diff +0 -0
  33. package/commands/dir +0 -0
  34. package/commands/dircolors +0 -0
  35. package/commands/dirname +0 -0
  36. package/commands/du +0 -0
  37. package/commands/echo +0 -0
  38. package/commands/egrep +0 -0
  39. package/commands/env +0 -0
  40. package/commands/envsubst +0 -0
  41. package/commands/expand +0 -0
  42. package/commands/expr +0 -0
  43. package/commands/factor +0 -0
  44. package/commands/false +0 -0
  45. package/commands/fd +0 -0
  46. package/commands/fgrep +0 -0
  47. package/commands/file +0 -0
  48. package/commands/find +0 -0
  49. package/commands/findmnt +0 -0
  50. package/commands/fmt +0 -0
  51. package/commands/fold +0 -0
  52. package/commands/getent +0 -0
  53. package/commands/getfacl +0 -0
  54. package/commands/getfattr +0 -0
  55. package/commands/git +0 -0
  56. package/commands/git-receive-pack +0 -0
  57. package/commands/git-remote-http +0 -0
  58. package/commands/git-remote-https +0 -0
  59. package/commands/git-upload-archive +0 -0
  60. package/commands/git-upload-pack +0 -0
  61. package/commands/grep +0 -0
  62. package/commands/groups +0 -0
  63. package/commands/gunzip +0 -0
  64. package/commands/gzip +0 -0
  65. package/commands/head +0 -0
  66. package/commands/hostid +0 -0
  67. package/commands/hostname +0 -0
  68. package/commands/http-test +0 -0
  69. package/commands/id +0 -0
  70. package/commands/install +0 -0
  71. package/commands/join +0 -0
  72. package/commands/jq +0 -0
  73. package/commands/kill +0 -0
  74. package/commands/link +0 -0
  75. package/commands/ln +0 -0
  76. package/commands/logname +0 -0
  77. package/commands/ls +0 -0
  78. package/commands/md5sum +0 -0
  79. package/commands/mkdir +0 -0
  80. package/commands/mkfifo +0 -0
  81. package/commands/mknod +0 -0
  82. package/commands/mktemp +0 -0
  83. package/commands/more +0 -0
  84. package/commands/mount +0 -0
  85. package/commands/mv +0 -0
  86. package/commands/nice +0 -0
  87. package/commands/nl +0 -0
  88. package/commands/nohup +0 -0
  89. package/commands/nproc +0 -0
  90. package/commands/numfmt +0 -0
  91. package/commands/od +0 -0
  92. package/commands/paste +0 -0
  93. package/commands/pathchk +0 -0
  94. package/commands/pinky +0 -0
  95. package/commands/printenv +0 -0
  96. package/commands/printf +0 -0
  97. package/commands/ptx +0 -0
  98. package/commands/pwd +0 -0
  99. package/commands/readlink +0 -0
  100. package/commands/realpath +0 -0
  101. package/commands/rev +0 -0
  102. package/commands/rg +0 -0
  103. package/commands/rm +0 -0
  104. package/commands/rmdir +0 -0
  105. package/commands/runas +0 -0
  106. package/commands/runcon +0 -0
  107. package/commands/sed +0 -0
  108. package/commands/seq +0 -0
  109. package/commands/setfacl +0 -0
  110. package/commands/setfattr +0 -0
  111. package/commands/sh +0 -0
  112. package/commands/sha1sum +0 -0
  113. package/commands/sha224sum +0 -0
  114. package/commands/sha256sum +0 -0
  115. package/commands/sha384sum +0 -0
  116. package/commands/sha512sum +0 -0
  117. package/commands/shred +0 -0
  118. package/commands/shuf +0 -0
  119. package/commands/sleep +0 -0
  120. package/commands/sort +0 -0
  121. package/commands/spawn-test-host +0 -0
  122. package/commands/split +0 -0
  123. package/commands/sqlite3 +0 -0
  124. package/commands/ssh +0 -0
  125. package/commands/ssh-keysign +0 -0
  126. package/commands/ssh-sk-helper +0 -0
  127. package/commands/stat +0 -0
  128. package/commands/stdbuf +0 -0
  129. package/commands/strings +0 -0
  130. package/commands/stty +0 -0
  131. package/commands/su +0 -0
  132. package/commands/sum +0 -0
  133. package/commands/sync +0 -0
  134. package/commands/tac +0 -0
  135. package/commands/tail +0 -0
  136. package/commands/tar +0 -0
  137. package/commands/tee +0 -0
  138. package/commands/test +0 -0
  139. package/commands/timeout +0 -0
  140. package/commands/touch +0 -0
  141. package/commands/tr +0 -0
  142. package/commands/tree +0 -0
  143. package/commands/true +0 -0
  144. package/commands/truncate +0 -0
  145. package/commands/tsort +0 -0
  146. package/commands/tty +0 -0
  147. package/commands/uname +0 -0
  148. package/commands/unexpand +0 -0
  149. package/commands/uniq +0 -0
  150. package/commands/unlink +0 -0
  151. package/commands/unzip +0 -0
  152. package/commands/uptime +0 -0
  153. package/commands/users +0 -0
  154. package/commands/vdir +0 -0
  155. package/commands/wc +0 -0
  156. package/commands/wget +0 -0
  157. package/commands/which +0 -0
  158. package/commands/who +0 -0
  159. package/commands/whoami +0 -0
  160. package/commands/xargs +0 -0
  161. package/commands/xattr_tools +0 -0
  162. package/commands/xfs_io +0 -0
  163. package/commands/xu +0 -0
  164. package/commands/yes +0 -0
  165. package/commands/yq +0 -0
  166. package/commands/zcat +0 -0
  167. package/commands/zip +0 -0
  168. package/dist/binary.js +4 -3
  169. package/dist/correlation.d.ts +1 -4
  170. package/dist/correlation.js +7 -10
  171. package/dist/descriptors.d.ts +2 -12
  172. package/dist/descriptors.js +10 -6
  173. package/dist/frame-rpc.d.ts +9 -4
  174. package/dist/frame-rpc.js +19 -2
  175. package/dist/frame-stream.d.ts +33 -0
  176. package/dist/frame-stream.js +113 -0
  177. package/dist/generated/AcpLimitsConfig.d.ts +16 -0
  178. package/dist/generated/BindingLimitsConfig.d.ts +10 -0
  179. package/dist/generated/CreateVmConfig.d.ts +4 -0
  180. package/dist/generated/Http2LimitsConfig.d.ts +12 -0
  181. package/dist/generated/Http2LimitsConfig.js +2 -0
  182. package/dist/generated/JsRuntimeLimitsConfig.d.ts +1 -0
  183. package/dist/generated/MountPluginDescriptor.d.ts +1 -1
  184. package/dist/generated/ProcessLimitsConfig.d.ts +7 -0
  185. package/dist/generated/ProcessLimitsConfig.js +2 -0
  186. package/dist/generated/ReactorLimitsConfig.d.ts +23 -0
  187. package/dist/generated/ReactorLimitsConfig.js +2 -0
  188. package/dist/generated/SqliteLimitsConfig.d.ts +3 -0
  189. package/dist/generated/SqliteLimitsConfig.js +2 -0
  190. package/dist/generated/TlsLimitsConfig.d.ts +3 -0
  191. package/dist/generated/TlsLimitsConfig.js +2 -0
  192. package/dist/generated/UdpLimitsConfig.d.ts +4 -0
  193. package/dist/generated/UdpLimitsConfig.js +2 -0
  194. package/dist/generated/VmGroupConfig.d.ts +5 -0
  195. package/dist/generated/VmGroupConfig.js +2 -0
  196. package/dist/generated/VmLimitsConfig.d.ts +14 -2
  197. package/dist/generated/VmSqliteDescriptor.d.ts +10 -0
  198. package/dist/generated/VmSqliteDescriptor.js +2 -0
  199. package/dist/generated/VmUserAccountConfig.d.ts +9 -0
  200. package/dist/generated/VmUserAccountConfig.js +2 -0
  201. package/dist/generated/VmUserConfig.d.ts +19 -0
  202. package/dist/generated/VmUserConfig.js +1 -0
  203. package/dist/generated/WasmLimitsConfig.d.ts +1 -0
  204. package/dist/generated-protocol.d.ts +60 -2
  205. package/dist/generated-protocol.js +166 -31
  206. package/dist/kernel-proxy.d.ts +5 -1
  207. package/dist/kernel-proxy.js +82 -40
  208. package/dist/native-client.d.ts +9 -5
  209. package/dist/native-client.js +56 -11
  210. package/dist/node-runtime-options-schema.d.ts +1 -1
  211. package/dist/node-runtime-options-schema.js +43 -1
  212. package/dist/node-runtime.d.ts +12 -3
  213. package/dist/node-runtime.js +8 -7
  214. package/dist/process.d.ts +5 -1
  215. package/dist/process.js +35 -8
  216. package/dist/protocol-client.d.ts +28 -4
  217. package/dist/protocol-client.js +193 -12
  218. package/dist/protocol-frames.d.ts +14 -3
  219. package/dist/protocol-frames.js +26 -1
  220. package/dist/protocol-schema.d.ts +1 -1
  221. package/dist/protocol-schema.js +1 -1
  222. package/dist/request-payloads.d.ts +4 -1
  223. package/dist/request-payloads.js +7 -2
  224. package/dist/response-payloads.d.ts +23 -2
  225. package/dist/response-payloads.js +34 -2
  226. package/dist/sidecar-errors.d.ts +37 -0
  227. package/dist/sidecar-errors.js +27 -0
  228. package/dist/sidecar-process.d.ts +20 -6
  229. package/dist/sidecar-process.js +79 -15
  230. package/dist/state.d.ts +6 -6
  231. package/dist/state.js +10 -11
  232. package/dist/test-runtime.d.ts +24 -7
  233. package/dist/test-runtime.js +213 -67
  234. package/dist/vm-config.d.ts +5 -1
  235. package/package.json +11 -6
  236. package/commands/codex +0 -0
  237. package/commands/codex-exec +0 -0
  238. package/commands/http_get +0 -0
  239. package/dist/generated/ToolLimitsConfig.d.ts +0 -10
  240. /package/dist/generated/{ToolLimitsConfig.js → BindingLimitsConfig.js} +0 -0
@@ -4,6 +4,8 @@ export function serializeMountConfigForSidecar(mount) {
4
4
  if ("driver" in mount) {
5
5
  return {
6
6
  guestPath: mount.path,
7
+ guestSource: mount.guestSource ?? "js_bridge",
8
+ guestFstype: mount.guestFstype ?? "js_bridge",
7
9
  readOnly: mount.readOnly ?? false,
8
10
  plugin: {
9
11
  id: "js_bridge",
@@ -13,6 +15,8 @@ export function serializeMountConfigForSidecar(mount) {
13
15
  }
14
16
  return {
15
17
  guestPath: mount.path,
18
+ guestSource: mount.guestSource ?? mount.plugin.id,
19
+ guestFstype: mount.guestFstype ?? mount.plugin.id,
16
20
  readOnly: mount.readOnly ?? false,
17
21
  plugin: {
18
22
  id: mount.plugin.id,
@@ -70,6 +74,15 @@ const PREFERRED_SIGNAL_NAMES = [
70
74
  "SIGEMT",
71
75
  "SIGINFO",
72
76
  ];
77
+ const NON_TERMINATING_SIGNALS = new Set([
78
+ "0",
79
+ "SIGCHLD",
80
+ "SIGCONT",
81
+ "SIGSTOP",
82
+ "SIGURG",
83
+ "SIGWINCH",
84
+ ]);
85
+ const LOCALLY_TERMINATING_SIGNALS = new Set(["SIGKILL", "SIGTERM"]);
73
86
  const NON_CANONICAL_SIGNAL_NAMES = new Set([
74
87
  "SIGCLD",
75
88
  "SIGIOT",
@@ -262,7 +275,10 @@ export class NativeSidecarKernelProxy {
262
275
  this.eventPumpAbortController.abort();
263
276
  const liveProcesses = [...this.trackedProcesses.values()].filter((entry) => entry.exitCode === null);
264
277
  await Promise.allSettled(liveProcesses.map((entry) => this.signalProcess(entry, 15)));
265
- await this.client.disposeVm(this.session, this.vm).catch(() => { });
278
+ await Promise.race([
279
+ this.client.disposeVm(this.session, this.vm),
280
+ new Promise((resolve) => setTimeout(resolve, 1000)),
281
+ ]).catch(() => { });
266
282
  for (const entry of liveProcesses) {
267
283
  if (entry.exitCode === null) {
268
284
  // The sidecar dispose path already performs TERM/KILL escalation for any
@@ -477,14 +493,22 @@ export class NativeSidecarKernelProxy {
477
493
  return;
478
494
  }
479
495
  entry.pendingKillSignal = signal;
480
- void entry.startPromise.then(async () => {
481
- if (entry.exitCode !== null || entry.pendingKillSignal === null) {
496
+ void entry.startPromise
497
+ .then(async () => {
498
+ if (entry.pendingKillSignal === null) {
482
499
  return;
483
500
  }
484
501
  const pendingSignal = entry.pendingKillSignal;
485
502
  entry.pendingKillSignal = null;
486
503
  await this.signalProcess(entry, pendingSignal);
504
+ })
505
+ .catch((error) => {
506
+ this.handleBackgroundProcessError(entry, error);
487
507
  });
508
+ if ((signal === 9 || signal === 15) &&
509
+ entry.exitCode === null) {
510
+ this.finishProcess(entry, 128 + signal);
511
+ }
488
512
  },
489
513
  wait: async () => {
490
514
  const exitCode = await this.waitForTrackedProcess(entry);
@@ -506,7 +530,7 @@ export class NativeSidecarKernelProxy {
506
530
  return proc;
507
531
  }
508
532
  openShell(options) {
509
- const stdoutHandlers = new Set();
533
+ const terminalHandlers = new Set();
510
534
  const stderrHandlers = new Set();
511
535
  const command = options?.command ?? "sh";
512
536
  const args = options?.args ??
@@ -565,7 +589,7 @@ export class NativeSidecarKernelProxy {
565
589
  const normalized = normalizeSyntheticTerminalText(text);
566
590
  updateSyntheticCursor(normalized);
567
591
  const chunk = textEncoder.encode(normalized);
568
- for (const handler of stdoutHandlers) {
592
+ for (const handler of terminalHandlers) {
569
593
  handler(chunk);
570
594
  }
571
595
  };
@@ -576,7 +600,7 @@ export class NativeSidecarKernelProxy {
576
600
  const normalized = normalizeSyntheticTerminalText(text);
577
601
  updateSyntheticCursor(normalized);
578
602
  const chunk = textEncoder.encode(normalized);
579
- for (const handler of stdoutHandlers) {
603
+ for (const handler of terminalHandlers) {
580
604
  handler(chunk);
581
605
  }
582
606
  };
@@ -621,7 +645,7 @@ export class NativeSidecarKernelProxy {
621
645
  commandInFlight = false;
622
646
  const promptPrefix = syntheticCursorAtLineStart ? "" : "\r\n";
623
647
  const promptChunk = textEncoder.encode(`${promptPrefix}${promptText}`);
624
- for (const handler of stdoutHandlers) {
648
+ for (const handler of terminalHandlers) {
625
649
  handler(promptChunk);
626
650
  }
627
651
  syntheticCursorAtLineStart = false;
@@ -658,7 +682,7 @@ export class NativeSidecarKernelProxy {
658
682
  }
659
683
  };
660
684
  let onData = null;
661
- stdoutHandlers.add((data) => onData?.(data));
685
+ terminalHandlers.add((data) => onData?.(data));
662
686
  if (options?.onStderr) {
663
687
  stderrHandlers.add(options.onStderr);
664
688
  }
@@ -817,7 +841,7 @@ export class NativeSidecarKernelProxy {
817
841
  cwd: options?.cwd,
818
842
  streamStdin: true,
819
843
  onStdout: (chunk) => {
820
- for (const handler of stdoutHandlers) {
844
+ for (const handler of terminalHandlers) {
821
845
  handler(chunk);
822
846
  }
823
847
  if (commandInFlight) {
@@ -825,6 +849,11 @@ export class NativeSidecarKernelProxy {
825
849
  }
826
850
  },
827
851
  onStderr: (chunk) => {
852
+ // `onData` is the ordered PTY rendering stream. `onStderr` remains an
853
+ // optional channel-specific diagnostic tap and must not also be rendered.
854
+ for (const handler of terminalHandlers) {
855
+ handler(chunk);
856
+ }
828
857
  for (const handler of stderrHandlers) {
829
858
  handler(chunk);
830
859
  }
@@ -878,13 +907,7 @@ export class NativeSidecarKernelProxy {
878
907
  const stdin = process.stdin;
879
908
  const stdout = process.stdout;
880
909
  const { onData, ...shellOptions } = options ?? {};
881
- const shell = this.openShell({
882
- ...shellOptions,
883
- onStderr: shellOptions.onStderr ??
884
- ((data) => {
885
- process.stderr.write(data);
886
- }),
887
- });
910
+ const shell = this.openShell(shellOptions);
888
911
  const outputHandler = onData ??
889
912
  ((data) => {
890
913
  stdout.write(data);
@@ -985,6 +1008,10 @@ export class NativeSidecarKernelProxy {
985
1008
  }
986
1009
  return this.client.rename(this.session, this.vm, oldPath, newPath);
987
1010
  }
1011
+ // Test-runtime only: runtime mounts registered here stay host-side local
1012
+ // compat mounts and are not delivered to the sidecar. The production proxy
1013
+ // in @rivet-dev/agentos-core reconfigures sidecar mounts on every
1014
+ // mountFs/unmountFs.
988
1015
  mountFs(path, driver, options) {
989
1016
  this.localMounts.unshift({
990
1017
  path: posixPath.normalize(path),
@@ -1124,15 +1151,6 @@ export class NativeSidecarKernelProxy {
1124
1151
  if (entry.onStdout.size === 0 && entry.onStderr.size === 0) {
1125
1152
  return;
1126
1153
  }
1127
- if (entry.exitViaEvent) {
1128
- // The sidecar drains process output before it emits `process_exited`,
1129
- // the stdio frame stream is FIFO, and this pump dispatches events in
1130
- // order. Once the exit event reaches this entry, no trailing output can
1131
- // follow it; one zero-delay turn is cheap insurance for same-tick
1132
- // listener scheduling.
1133
- await drainTrailingProcessOutputTurn(0);
1134
- return;
1135
- }
1136
1154
  let observedGeneration = entry.outputGeneration;
1137
1155
  let quietTurns = 0;
1138
1156
  let delayMs = 0;
@@ -1303,9 +1321,27 @@ export class NativeSidecarKernelProxy {
1303
1321
  return entry.waitWithFallbackPromise;
1304
1322
  }
1305
1323
  async signalProcess(entry, signal) {
1306
- await this.signalRefreshes.get(entry.pid);
1324
+ const sidecarSignal = toSidecarSignalName(signal);
1325
+ let timedOut = false;
1326
+ const killPromise = this.client.killProcess(this.session, this.vm, entry.processId, sidecarSignal);
1307
1327
  try {
1308
- await this.client.killProcess(this.session, this.vm, entry.processId, toSidecarSignalName(signal));
1328
+ await Promise.race([
1329
+ killPromise,
1330
+ new Promise((resolve) => setTimeout(() => {
1331
+ timedOut = true;
1332
+ resolve();
1333
+ }, 1000)),
1334
+ ]);
1335
+ if (timedOut) {
1336
+ void killPromise.catch(() => { });
1337
+ }
1338
+ const hasGuestHandler = this.signalStates.get(entry.pid)?.handlers.has(signal) ?? false;
1339
+ if (entry.exitCode === null &&
1340
+ !hasGuestHandler &&
1341
+ LOCALLY_TERMINATING_SIGNALS.has(sidecarSignal) &&
1342
+ (entry.driver === "wasmvm" || timedOut)) {
1343
+ this.finishProcess(entry, 128 + signal);
1344
+ }
1309
1345
  }
1310
1346
  catch (error) {
1311
1347
  if (isNoSuchProcessError(error) || isUnknownVmError(error)) {
@@ -1520,12 +1556,12 @@ export class NativeSidecarKernelProxy {
1520
1556
  return this.client.pread(this.session, this.vm, path, offset, length);
1521
1557
  },
1522
1558
  pwrite: async (path, offset, data) => {
1523
- const bytes = await this.createFilesystemView(includeLocalMounts).readFile(path);
1524
- const nextSize = Math.max(bytes.length, offset + data.length);
1525
- const updated = new Uint8Array(nextSize);
1526
- updated.set(bytes);
1527
- updated.set(data, offset);
1528
- await this.createFilesystemView(includeLocalMounts).writeFile(path, updated);
1559
+ const local = includeLocalMounts ? this.resolveLocalMount(path) : null;
1560
+ if (local) {
1561
+ this.assertLocalWritable(local.mount);
1562
+ return local.mount.fs.pwrite(local.relativePath, offset, data);
1563
+ }
1564
+ return this.client.pwrite(this.session, this.vm, path, offset, data);
1529
1565
  },
1530
1566
  };
1531
1567
  }
@@ -1727,21 +1763,27 @@ function isMissingHostProcessError(error) {
1727
1763
  function errnoError(code, message) {
1728
1764
  return Object.assign(new Error(`${code}: ${message}`), { code });
1729
1765
  }
1766
+ // VirtualStat is a numeric, Node-default-shaped view: u64 fields above
1767
+ // Number.MAX_SAFE_INTEGER lose precision here, same as Node's non-bigint
1768
+ // fs.stat on the host.
1730
1769
  function toVirtualStat(stat) {
1731
1770
  return {
1732
1771
  mode: stat.mode,
1733
- size: stat.size,
1734
- blocks: stat.blocks,
1735
- dev: stat.dev,
1736
- rdev: stat.rdev,
1772
+ size: Number(stat.size),
1773
+ sizeExact: stat.size,
1774
+ blocks: Number(stat.blocks),
1775
+ dev: Number(stat.dev),
1776
+ rdev: Number(stat.rdev),
1737
1777
  isDirectory: stat.is_directory,
1738
1778
  isSymbolicLink: stat.is_symbolic_link,
1739
1779
  atimeMs: stat.atime_ms,
1740
1780
  mtimeMs: stat.mtime_ms,
1741
1781
  ctimeMs: stat.ctime_ms,
1742
1782
  birthtimeMs: stat.birthtime_ms,
1743
- ino: stat.ino,
1744
- nlink: stat.nlink,
1783
+ ino: Number(stat.ino),
1784
+ inoExact: stat.ino,
1785
+ nlink: Number(stat.nlink),
1786
+ nlinkExact: stat.nlink,
1745
1787
  uid: stat.uid,
1746
1788
  gid: stat.gid,
1747
1789
  };
@@ -1774,4 +1816,4 @@ function socketLookupKey(kind, request) {
1774
1816
  path: request.path ?? null,
1775
1817
  });
1776
1818
  }
1777
- export { NATIVE_SIDECAR_FRAME_TIMEOUT_MS, SidecarProcess, SidecarEventBufferOverflow, SidecarProcessError, SidecarProcessExited, } from "./sidecar-process.js";
1819
+ export { SidecarProcess, SidecarEventBufferOverflow, SidecarProcessError, SidecarProcessExited, } from "./sidecar-process.js";
@@ -1,10 +1,9 @@
1
+ import type { LiveSidecarEventSelector } from "./event-buffer.js";
2
+ import type { LiveOwnershipScope } from "./ownership.js";
1
3
  import { StdioSidecarProcess } from "./process.js";
2
- import type { SidecarProcessTransport } from "./sidecar-client.js";
3
4
  import type { LiveEventFrame, LiveResponseFrame, LiveSidecarRequestHandler, ProtocolFramePayloadCodec } from "./protocol-frames.js";
4
- import type { LiveOwnershipScope } from "./ownership.js";
5
5
  import type { LiveRequestPayload } from "./request-payloads.js";
6
- import type { LiveSidecarEventSelector } from "./event-buffer.js";
7
- export declare const DEFAULT_SIDECAR_FRAME_TIMEOUT_MS = 120000;
6
+ import type { SidecarProcessTransport } from "./sidecar-client.js";
8
7
  export declare const DEFAULT_SIDECAR_EVENT_BUFFER_CAPACITY = 4096;
9
8
  export declare const DEFAULT_SIDECAR_GRACEFUL_EXIT_MS = 5000;
10
9
  export declare const DEFAULT_SIDECAR_FORCE_EXIT_MS = 2000;
@@ -12,12 +11,17 @@ export interface StdioSidecarProtocolClientSpawnOptions {
12
11
  cwd?: string;
13
12
  command?: string;
14
13
  args?: string[];
15
- frameTimeoutMs?: number;
16
14
  eventBufferCapacity?: number;
17
15
  gracefulExitMs?: number;
18
16
  forceExitMs?: number;
19
17
  disposedErrorMessage?: string;
20
18
  payloadCodec?: ProtocolFramePayloadCodec;
19
+ /**
20
+ * Override the silence watchdog window (default 30s). Tests only — the
21
+ * window is a fixed protocol constant paired with the sidecar's 10s
22
+ * heartbeat cadence, not an operator tunable.
23
+ */
24
+ silenceTimeoutMs?: number;
21
25
  }
22
26
  export declare class StdioSidecarProtocolClient implements SidecarProcessTransport {
23
27
  readonly child: StdioSidecarProcess["child"];
@@ -1,8 +1,7 @@
1
1
  import { resolvePublishedSidecarBinary } from "./binary.js";
2
- import { SidecarProcessExited, StdioSidecarProcess, } from "./process.js";
2
+ import { SidecarProcessExited, StdioSidecarProcess } from "./process.js";
3
3
  import { SidecarProtocolClient } from "./protocol-client.js";
4
4
  import { registerSidecarProcessSpawnFactory } from "./sidecar-process.js";
5
- export const DEFAULT_SIDECAR_FRAME_TIMEOUT_MS = 120_000;
6
5
  export const DEFAULT_SIDECAR_EVENT_BUFFER_CAPACITY = 4_096;
7
6
  export const DEFAULT_SIDECAR_GRACEFUL_EXIT_MS = 5_000;
8
7
  export const DEFAULT_SIDECAR_FORCE_EXIT_MS = 2_000;
@@ -19,12 +18,33 @@ export class StdioSidecarProtocolClient {
19
18
  this.gracefulExitMs = options.gracefulExitMs;
20
19
  this.forceExitMs = options.forceExitMs;
21
20
  this.disposedErrorMessage = options.disposedErrorMessage;
21
+ const transportOptions = this.sidecarProcess.combinedStdio
22
+ ? {
23
+ stdin: this.child.stdin,
24
+ stdout: this.child.stdout,
25
+ combinedStdio: true,
26
+ }
27
+ : {
28
+ stdin: this.child.stdin,
29
+ stdout: this.child.stdout,
30
+ control: this.sidecarProcess.control,
31
+ };
22
32
  this.protocolClient = new SidecarProtocolClient({
23
- stdin: this.child.stdin,
24
- stdout: this.child.stdout,
25
- frameTimeoutMs: options.frameTimeoutMs,
33
+ ...transportOptions,
26
34
  eventBufferCapacity: options.eventBufferCapacity,
27
35
  payloadCodec: options.payloadCodec,
36
+ silenceTimeoutMs: options.silenceTimeoutMs,
37
+ // A silent sidecar is dead or wedged; reap the process so it cannot
38
+ // linger as a zombie holding VM resources. The watchdog then rejects
39
+ // all in-flight requests with `SidecarSilenceTimeout`.
40
+ onSilenceExpired: () => {
41
+ try {
42
+ this.child.kill("SIGKILL");
43
+ }
44
+ catch {
45
+ // The child may have exited between the check and the kill.
46
+ }
47
+ },
28
48
  stderrText: () => this.sidecarProcess.stderrText(),
29
49
  streamEndedError: () => this.sidecarProcess.currentExitError() ??
30
50
  new SidecarProcessExited({
@@ -42,14 +62,15 @@ export class StdioSidecarProtocolClient {
42
62
  });
43
63
  }
44
64
  static spawn(options = {}) {
65
+ const combinedStdio = typeof globalThis._childProcessSpawnStart !== "undefined";
45
66
  return new StdioSidecarProtocolClient(StdioSidecarProcess.spawn({
46
67
  command: options.command ?? resolvePublishedSidecarBinary(),
47
68
  args: options.args ?? [],
48
69
  cwd: options.cwd,
70
+ combinedStdio,
49
71
  }), {
50
- frameTimeoutMs: options.frameTimeoutMs ?? DEFAULT_SIDECAR_FRAME_TIMEOUT_MS,
51
- eventBufferCapacity: options.eventBufferCapacity ??
52
- DEFAULT_SIDECAR_EVENT_BUFFER_CAPACITY,
72
+ silenceTimeoutMs: options.silenceTimeoutMs,
73
+ eventBufferCapacity: options.eventBufferCapacity ?? DEFAULT_SIDECAR_EVENT_BUFFER_CAPACITY,
53
74
  gracefulExitMs: options.gracefulExitMs ?? DEFAULT_SIDECAR_GRACEFUL_EXIT_MS,
54
75
  forceExitMs: options.forceExitMs ?? DEFAULT_SIDECAR_FORCE_EXIT_MS,
55
76
  disposedErrorMessage: options.disposedErrorMessage ?? "sidecar client disposed",
@@ -69,6 +90,13 @@ export class StdioSidecarProtocolClient {
69
90
  return await this.protocolClient.waitForEvent(matcher, timeoutMs, options);
70
91
  }
71
92
  async dispose() {
93
+ let shutdownError = null;
94
+ try {
95
+ await this.protocolClient.shutdown(this.disposedErrorMessage);
96
+ }
97
+ catch (error) {
98
+ shutdownError = error instanceof Error ? error : new Error(String(error));
99
+ }
72
100
  this.protocolClient.failPermanently(new Error(this.disposedErrorMessage));
73
101
  if (!this.child.stdin.destroyed) {
74
102
  try {
@@ -78,6 +106,14 @@ export class StdioSidecarProtocolClient {
78
106
  // Stdin may already be closing. The child exit watcher will catch up.
79
107
  }
80
108
  }
109
+ if (this.sidecarProcess.control && !this.sidecarProcess.control.destroyed) {
110
+ try {
111
+ this.sidecarProcess.control.end();
112
+ }
113
+ catch {
114
+ // The control socket may already be closing with the child.
115
+ }
116
+ }
81
117
  const exitCode = await this.sidecarProcess.waitForExit(this.gracefulExitMs);
82
118
  if (exitCode === null) {
83
119
  try {
@@ -107,11 +143,20 @@ export class StdioSidecarProtocolClient {
107
143
  catch {
108
144
  // Best effort. The child is gone so the descriptor will close on its own.
109
145
  }
110
- if (exitCode !== null &&
111
- exitCode !== 0 &&
112
- this.child.signalCode === null) {
146
+ if (this.sidecarProcess.control) {
147
+ try {
148
+ this.sidecarProcess.control.destroy();
149
+ }
150
+ catch {
151
+ // Best effort. The child is gone so the descriptor will close on its own.
152
+ }
153
+ }
154
+ if (exitCode !== null && exitCode !== 0 && this.child.signalCode === null) {
113
155
  throw new Error(`sidecar exited with code ${exitCode}\nstderr:\n${this.sidecarProcess.stderrText()}`);
114
156
  }
157
+ if (shutdownError && this.child.exitCode !== 0) {
158
+ throw shutdownError;
159
+ }
115
160
  }
116
161
  failPermanently(error) {
117
162
  this.protocolClient.failPermanently(error, {
@@ -114,4 +114,4 @@ export declare const nodeRuntimePermissionsSchema: z.ZodObject<{
114
114
  * the Rust VM config.
115
115
  */
116
116
  export declare const nodeRuntimeCreateOptionsSchema: z.ZodType<NodeRuntimeCreateOptions>;
117
- export declare function parseNodeRuntimeCreateOptions(options?: NodeRuntimeCreateOptions): NodeRuntimeCreateOptions;
117
+ export declare function parseNodeRuntimeCreateOptions(options: NodeRuntimeCreateOptions): NodeRuntimeCreateOptions;
@@ -1,6 +1,46 @@
1
1
  import { z } from "zod";
2
2
  const permissionModeSchema = z.enum(["allow", "deny"]);
3
3
  const stringArray = z.array(z.string());
4
+ const vmIdSchema = z.number().int().min(0).max(0xffffffff);
5
+ const vmAccountNameSchema = z
6
+ .string()
7
+ .min(1)
8
+ .refine((value) => !/[:\s\0]/u.test(value), "Invalid account name");
9
+ const vmGuestPathSchema = z.string().startsWith("/");
10
+ const vmUserAccountSchema = z
11
+ .object({
12
+ uid: vmIdSchema,
13
+ gid: vmIdSchema,
14
+ username: vmAccountNameSchema,
15
+ homedir: vmGuestPathSchema,
16
+ shell: vmGuestPathSchema,
17
+ gecos: z.string().optional(),
18
+ supplementaryGids: z.array(vmIdSchema).max(64),
19
+ })
20
+ .strict();
21
+ const vmGroupSchema = z
22
+ .object({
23
+ gid: vmIdSchema,
24
+ name: vmAccountNameSchema,
25
+ members: z.array(vmAccountNameSchema),
26
+ })
27
+ .strict();
28
+ const vmUserConfigSchema = z
29
+ .object({
30
+ uid: vmIdSchema.optional(),
31
+ gid: vmIdSchema.optional(),
32
+ euid: vmIdSchema.optional(),
33
+ egid: vmIdSchema.optional(),
34
+ username: z.string().optional(),
35
+ homedir: z.string().optional(),
36
+ shell: z.string().optional(),
37
+ gecos: z.string().optional(),
38
+ groupName: z.string().optional(),
39
+ supplementaryGids: z.array(vmIdSchema).max(64).optional(),
40
+ accounts: z.array(vmUserAccountSchema).max(64).optional(),
41
+ groups: z.array(vmGroupSchema).max(128).optional(),
42
+ })
43
+ .strict();
4
44
  const fsPermissionRuleSchema = z
5
45
  .object({
6
46
  mode: permissionModeSchema,
@@ -91,8 +131,10 @@ const bindingDefinitionSchema = z
91
131
  */
92
132
  export const nodeRuntimeCreateOptionsSchema = z
93
133
  .object({
134
+ filesystem: z.custom((value) => typeof value === "object" && value !== null, { message: "Expected caller-owned VirtualFileSystem object" }),
94
135
  env: z.record(z.string(), z.string()).optional(),
95
136
  cwd: z.string().optional(),
137
+ user: vmUserConfigSchema.optional(),
96
138
  permissions: nodeRuntimePermissionsSchema.optional(),
97
139
  commandsDir: z.string().optional(),
98
140
  wasmCommandDirs: stringArray.optional(),
@@ -116,6 +158,6 @@ export const nodeRuntimeCreateOptionsSchema = z
116
158
  jsRuntime: jsRuntimeSchema.optional(),
117
159
  })
118
160
  .strict();
119
- export function parseNodeRuntimeCreateOptions(options = {}) {
161
+ export function parseNodeRuntimeCreateOptions(options) {
120
162
  return nodeRuntimeCreateOptionsSchema.parse(options);
121
163
  }
@@ -17,10 +17,11 @@
17
17
  * isolation boundary — no host escapes, no real Node.js builtins for guest
18
18
  * work.
19
19
  */
20
- import type { BindingDefinition, KernelBootTiming, Permissions, VirtualDirEntry } from "./test-runtime.js";
20
+ import type { BindingDefinition, KernelBootTiming, Permissions, VirtualDirEntry, VirtualFileSystem } from "./test-runtime.js";
21
21
  import type { JsRuntimeConfig } from "./generated/JsRuntimeConfig.js";
22
+ import type { VmUserConfig } from "./generated/VmUserConfig.js";
22
23
  import type { SidecarProcess } from "./sidecar-process.js";
23
- export type { BindingDefinition, HostToolExample, VirtualDirEntry, } from "./test-runtime.js";
24
+ export type { BindingDefinition, BindingExample, VirtualDirEntry, } from "./test-runtime.js";
24
25
  export { resolveNodeRuntimeSidecarBinary } from "./test-runtime.js";
25
26
  export type NodeRuntimeBootTimingPhase = KernelBootTiming["phase"] | "runtime_mount_wasm" | "runtime_mount_node" | "bindings";
26
27
  export interface NodeRuntimeBootTiming {
@@ -50,10 +51,18 @@ export declare function resolveNodeRuntimeCommandsDir(explicit?: string): string
50
51
  * `crates/vm-config/src/lib.rs::CreateVmConfig`.
51
52
  */
52
53
  export interface NodeRuntimeCreateOptions {
54
+ /**
55
+ * Caller-owned filesystem used only by this low-level compatibility runtime.
56
+ * AgentOS clients do not create a TypeScript filesystem implicitly; normal
57
+ * product code should use the sidecar-owned VFS through `AgentOs`.
58
+ */
59
+ filesystem: VirtualFileSystem;
53
60
  /** Environment variables visible to guest processes. */
54
61
  env?: Record<string, string>;
55
62
  /** Initial working directory for guest processes. Defaults to `/workspace`. */
56
63
  cwd?: string;
64
+ /** Initial virtual Linux credentials and account record. Defaults to `1000:1000` (`agentos`). */
65
+ user?: VmUserConfig;
57
66
  /**
58
67
  * Permission policy for the VM. Merged over a secure default that **denies
59
68
  * network access** (guest code cannot reach the network until you opt in);
@@ -440,7 +449,7 @@ export declare class NodeRuntime {
440
449
  * session, creates the VM with a bootstrapped root filesystem, mounts the
441
450
  * shell and Node runtimes, and waits for the VM to report ready.
442
451
  */
443
- static create(options?: NodeRuntimeCreateOptions): Promise<NodeRuntime>;
452
+ static create(options: NodeRuntimeCreateOptions): Promise<NodeRuntime>;
444
453
  createResidentRunner(_options?: NodeRuntimeResidentRunnerOptions): Promise<NodeRuntimeResidentRunner>;
445
454
  /**
446
455
  * Run `code` as a guest Node program and capture its output.
@@ -20,18 +20,18 @@
20
20
  import { existsSync } from "node:fs";
21
21
  import path from "node:path";
22
22
  import { fileURLToPath } from "node:url";
23
- import { createInMemoryFileSystem, createKernel, createNodeRuntime, createWasmVmRuntime, NodeFileSystem, } from "./test-runtime.js";
23
+ import { createKernel, createNodeRuntime, createWasmVmRuntime, NodeFileSystem, } from "./test-runtime.js";
24
24
  import { parseNodeRuntimeCreateOptions } from "./node-runtime-options-schema.js";
25
25
  export { resolveNodeRuntimeSidecarBinary } from "./test-runtime.js";
26
26
  /** Repository root, used to locate the in-repo WASM command build output. */
27
27
  const REPO_ROOT = fileURLToPath(new URL("../../..", import.meta.url));
28
28
  /**
29
29
  * In-repo build output for the WASM coreutils/shell command binaries, produced
30
- * by the Rust command build (`make -C registry/native wasm`). Only present in a
30
+ * by the Rust command build (`make -C toolchain wasm`). Only present in a
31
31
  * developer checkout; preferred when it exists so local edits are picked up
32
32
  * without re-vendoring.
33
33
  */
34
- const REPO_COMMANDS_DIR = path.join(REPO_ROOT, "registry/native/target/wasm32-wasip1/release/commands");
34
+ const REPO_COMMANDS_DIR = path.join(REPO_ROOT, "toolchain/target/wasm32-wasip1/release/commands");
35
35
  /**
36
36
  * Commands vendored into the published `@rivet-dev/agentos-runtime-core` package by
37
37
  * `scripts/copy-wasm-commands.mjs` (listed in `files` as `commands`). This is
@@ -128,14 +128,14 @@ export class NodeRuntime {
128
128
  * session, creates the VM with a bootstrapped root filesystem, mounts the
129
129
  * shell and Node runtimes, and waits for the VM to report ready.
130
130
  */
131
- static async create(options = {}) {
131
+ static async create(options) {
132
132
  options = parseNodeRuntimeCreateOptions(options);
133
133
  const commandsDir = resolveNodeRuntimeCommandsDir(options.commandsDir);
134
134
  // Seed caller-provided files into the VM's in-memory filesystem before
135
135
  // boot so they are part of the root filesystem snapshot the guest sees
136
136
  // (e.g. projected npm packages or fixtures). The host filesystem is
137
137
  // never exposed; only these bytes are copied in.
138
- const filesystem = createInMemoryFileSystem();
138
+ const filesystem = options.filesystem;
139
139
  if (options.files) {
140
140
  for (const [filePath, content] of Object.entries(options.files)) {
141
141
  await filesystem.writeFile(filePath, content);
@@ -183,6 +183,7 @@ export class NodeRuntime {
183
183
  },
184
184
  env: options.env,
185
185
  cwd: options.cwd,
186
+ user: options.user,
186
187
  sidecar: options.sidecar,
187
188
  onBootTiming: (timing) => options.onBootTiming?.(timing),
188
189
  loopbackExemptPorts: options.loopbackExemptPorts,
@@ -202,7 +203,7 @@ export class NodeRuntime {
202
203
  // installed as guest commands the moment the VM is ready.
203
204
  const bindings = options.bindings;
204
205
  if (bindings && Object.keys(bindings).length > 0) {
205
- await measureBootTiming("bindings", options.onBootTiming, () => kernel.registerHostTools(bindings));
206
+ await measureBootTiming("bindings", options.onBootTiming, () => kernel.registerBindings(bindings));
206
207
  }
207
208
  }
208
209
  catch (error) {
@@ -588,7 +589,7 @@ export class NodeRuntime {
588
589
  * {@link NodeRuntime.create}) so the bindings are invocable.
589
590
  */
590
591
  async registerBindings(bindings) {
591
- await this.kernel.registerHostTools(bindings);
592
+ await this.kernel.registerBindings(bindings);
592
593
  }
593
594
  /**
594
595
  * Write a file into the VM's virtual filesystem, creating parent
package/dist/process.d.ts CHANGED
@@ -1,19 +1,23 @@
1
1
  import { type ChildProcessWithoutNullStreams } from "node:child_process";
2
+ import type { Duplex } from "node:stream";
2
3
  export { SidecarProcessError, SidecarProcessExited, } from "./sidecar-errors.js";
3
4
  import { SidecarProcessError, SidecarProcessExited } from "./sidecar-errors.js";
4
5
  export interface StdioSidecarProcessSpawnOptions {
5
6
  command: string;
6
7
  args?: string[];
7
8
  cwd?: string;
9
+ combinedStdio?: boolean;
8
10
  }
9
11
  export declare class StdioSidecarProcess {
10
12
  readonly child: ChildProcessWithoutNullStreams;
13
+ readonly control: Duplex | null;
14
+ readonly combinedStdio: boolean;
11
15
  private readonly stderrChunks;
12
16
  private readonly exitListeners;
13
17
  private readonly errorListeners;
14
18
  private constructor();
15
19
  static spawn(options: StdioSidecarProcessSpawnOptions): StdioSidecarProcess;
16
- static fromChild(child: ChildProcessWithoutNullStreams): StdioSidecarProcess;
20
+ static fromChild(child: ChildProcessWithoutNullStreams, control?: Duplex | null): StdioSidecarProcess;
17
21
  onExit(handler: (error: SidecarProcessExited) => void): () => void;
18
22
  onError(handler: (error: SidecarProcessError) => void): () => void;
19
23
  stderrText(): string;