@rivet-dev/agentos-runtime-core 0.0.0-codex-actor-plugin-contract-coverage.115ca8a → 0.0.0-fix-main-wasm-ownership.150c49a

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 (238) hide show
  1. package/commands/[ +0 -0
  2. package/commands/_stubs +0 -0
  3. package/commands/acl_tools +0 -0
  4. package/commands/arch +0 -0
  5. package/commands/attr +0 -0
  6. package/commands/awk +0 -0
  7. package/commands/b2sum +0 -0
  8. package/commands/base32 +0 -0
  9. package/commands/base64 +0 -0
  10. package/commands/basename +0 -0
  11. package/commands/basenc +0 -0
  12. package/commands/bash +0 -0
  13. package/commands/bc +0 -0
  14. package/commands/cat +0 -0
  15. package/commands/chacl +0 -0
  16. package/commands/chcon +0 -0
  17. package/commands/chgrp +0 -0
  18. package/commands/chmod +0 -0
  19. package/commands/chown +0 -0
  20. package/commands/chroot +0 -0
  21. package/commands/cksum +0 -0
  22. package/commands/cmp +0 -0
  23. package/commands/column +0 -0
  24. package/commands/comm +0 -0
  25. package/commands/cp +0 -0
  26. package/commands/curl +0 -0
  27. package/commands/cut +0 -0
  28. package/commands/date +0 -0
  29. package/commands/dd +0 -0
  30. package/commands/df +0 -0
  31. package/commands/diff +0 -0
  32. package/commands/dir +0 -0
  33. package/commands/dircolors +0 -0
  34. package/commands/dirname +0 -0
  35. package/commands/du +0 -0
  36. package/commands/echo +0 -0
  37. package/commands/egrep +0 -0
  38. package/commands/env +0 -0
  39. package/commands/envsubst +0 -0
  40. package/commands/expand +0 -0
  41. package/commands/expr +0 -0
  42. package/commands/factor +0 -0
  43. package/commands/false +0 -0
  44. package/commands/fd +0 -0
  45. package/commands/fgrep +0 -0
  46. package/commands/file +0 -0
  47. package/commands/find +0 -0
  48. package/commands/findmnt +0 -0
  49. package/commands/fmt +0 -0
  50. package/commands/fold +0 -0
  51. package/commands/getent +0 -0
  52. package/commands/getfacl +0 -0
  53. package/commands/getfattr +0 -0
  54. package/commands/git +0 -0
  55. package/commands/git-receive-pack +0 -0
  56. package/commands/git-remote-http +0 -0
  57. package/commands/git-remote-https +0 -0
  58. package/commands/git-upload-archive +0 -0
  59. package/commands/git-upload-pack +0 -0
  60. package/commands/grep +0 -0
  61. package/commands/groups +0 -0
  62. package/commands/gunzip +0 -0
  63. package/commands/gzip +0 -0
  64. package/commands/head +0 -0
  65. package/commands/hostid +0 -0
  66. package/commands/hostname +0 -0
  67. package/commands/http-test +0 -0
  68. package/commands/id +0 -0
  69. package/commands/install +0 -0
  70. package/commands/join +0 -0
  71. package/commands/jq +0 -0
  72. package/commands/kill +0 -0
  73. package/commands/link +0 -0
  74. package/commands/ln +0 -0
  75. package/commands/logname +0 -0
  76. package/commands/ls +0 -0
  77. package/commands/md5sum +0 -0
  78. package/commands/mkdir +0 -0
  79. package/commands/mkfifo +0 -0
  80. package/commands/mknod +0 -0
  81. package/commands/mktemp +0 -0
  82. package/commands/more +0 -0
  83. package/commands/mount +0 -0
  84. package/commands/mv +0 -0
  85. package/commands/nice +0 -0
  86. package/commands/nl +0 -0
  87. package/commands/nohup +0 -0
  88. package/commands/nproc +0 -0
  89. package/commands/numfmt +0 -0
  90. package/commands/od +0 -0
  91. package/commands/paste +0 -0
  92. package/commands/pathchk +0 -0
  93. package/commands/pinky +0 -0
  94. package/commands/printenv +0 -0
  95. package/commands/printf +0 -0
  96. package/commands/ptx +0 -0
  97. package/commands/pwd +0 -0
  98. package/commands/readlink +0 -0
  99. package/commands/realpath +0 -0
  100. package/commands/rev +0 -0
  101. package/commands/rg +0 -0
  102. package/commands/rm +0 -0
  103. package/commands/rmdir +0 -0
  104. package/commands/runas +0 -0
  105. package/commands/runcon +0 -0
  106. package/commands/sed +0 -0
  107. package/commands/seq +0 -0
  108. package/commands/setfacl +0 -0
  109. package/commands/setfattr +0 -0
  110. package/commands/sh +0 -0
  111. package/commands/sha1sum +0 -0
  112. package/commands/sha224sum +0 -0
  113. package/commands/sha256sum +0 -0
  114. package/commands/sha384sum +0 -0
  115. package/commands/sha512sum +0 -0
  116. package/commands/shred +0 -0
  117. package/commands/shuf +0 -0
  118. package/commands/sleep +0 -0
  119. package/commands/sort +0 -0
  120. package/commands/spawn-test-host +0 -0
  121. package/commands/split +0 -0
  122. package/commands/sqlite3 +0 -0
  123. package/commands/ssh +0 -0
  124. package/commands/ssh-keysign +0 -0
  125. package/commands/ssh-sk-helper +0 -0
  126. package/commands/stat +0 -0
  127. package/commands/stdbuf +0 -0
  128. package/commands/strings +0 -0
  129. package/commands/stty +0 -0
  130. package/commands/su +0 -0
  131. package/commands/sum +0 -0
  132. package/commands/sync +0 -0
  133. package/commands/tac +0 -0
  134. package/commands/tail +0 -0
  135. package/commands/tar +0 -0
  136. package/commands/tee +0 -0
  137. package/commands/test +0 -0
  138. package/commands/timeout +0 -0
  139. package/commands/touch +0 -0
  140. package/commands/tr +0 -0
  141. package/commands/tree +0 -0
  142. package/commands/true +0 -0
  143. package/commands/truncate +0 -0
  144. package/commands/tsort +0 -0
  145. package/commands/tty +0 -0
  146. package/commands/uname +0 -0
  147. package/commands/unexpand +0 -0
  148. package/commands/uniq +0 -0
  149. package/commands/unlink +0 -0
  150. package/commands/unzip +0 -0
  151. package/commands/uptime +0 -0
  152. package/commands/users +0 -0
  153. package/commands/vdir +0 -0
  154. package/commands/wc +0 -0
  155. package/commands/wget +0 -0
  156. package/commands/which +0 -0
  157. package/commands/who +0 -0
  158. package/commands/whoami +0 -0
  159. package/commands/xargs +0 -0
  160. package/commands/xattr_tools +0 -0
  161. package/commands/xfs_io +0 -0
  162. package/commands/xu +0 -0
  163. package/commands/yes +0 -0
  164. package/commands/yq +0 -0
  165. package/commands/zcat +0 -0
  166. package/commands/zip +0 -0
  167. package/dist/correlation.d.ts +1 -4
  168. package/dist/correlation.js +7 -10
  169. package/dist/descriptors.d.ts +2 -12
  170. package/dist/descriptors.js +10 -6
  171. package/dist/frame-rpc.d.ts +9 -4
  172. package/dist/frame-rpc.js +19 -2
  173. package/dist/frame-stream.d.ts +33 -0
  174. package/dist/frame-stream.js +113 -0
  175. package/dist/generated/AcpLimitsConfig.d.ts +16 -0
  176. package/dist/generated/BindingLimitsConfig.d.ts +10 -0
  177. package/dist/generated/CreateVmConfig.d.ts +4 -0
  178. package/dist/generated/Http2LimitsConfig.d.ts +12 -0
  179. package/dist/generated/Http2LimitsConfig.js +2 -0
  180. package/dist/generated/JsRuntimeLimitsConfig.d.ts +1 -0
  181. package/dist/generated/MountPluginDescriptor.d.ts +1 -1
  182. package/dist/generated/ProcessLimitsConfig.d.ts +7 -0
  183. package/dist/generated/ProcessLimitsConfig.js +2 -0
  184. package/dist/generated/ReactorLimitsConfig.d.ts +23 -0
  185. package/dist/generated/ReactorLimitsConfig.js +2 -0
  186. package/dist/generated/SqliteLimitsConfig.d.ts +3 -0
  187. package/dist/generated/SqliteLimitsConfig.js +2 -0
  188. package/dist/generated/TlsLimitsConfig.d.ts +3 -0
  189. package/dist/generated/TlsLimitsConfig.js +2 -0
  190. package/dist/generated/UdpLimitsConfig.d.ts +4 -0
  191. package/dist/generated/UdpLimitsConfig.js +2 -0
  192. package/dist/generated/VmGroupConfig.d.ts +5 -0
  193. package/dist/generated/VmGroupConfig.js +2 -0
  194. package/dist/generated/VmLimitsConfig.d.ts +14 -2
  195. package/dist/generated/VmSqliteDescriptor.d.ts +11 -0
  196. package/dist/generated/VmSqliteDescriptor.js +2 -0
  197. package/dist/generated/VmUserAccountConfig.d.ts +9 -0
  198. package/dist/generated/VmUserAccountConfig.js +2 -0
  199. package/dist/generated/VmUserConfig.d.ts +19 -0
  200. package/dist/generated/VmUserConfig.js +1 -0
  201. package/dist/generated/WasmLimitsConfig.d.ts +1 -0
  202. package/dist/generated-protocol.d.ts +60 -2
  203. package/dist/generated-protocol.js +166 -31
  204. package/dist/kernel-proxy.d.ts +5 -1
  205. package/dist/kernel-proxy.js +77 -39
  206. package/dist/native-client.d.ts +9 -5
  207. package/dist/native-client.js +41 -9
  208. package/dist/node-runtime-options-schema.d.ts +1 -1
  209. package/dist/node-runtime-options-schema.js +43 -1
  210. package/dist/node-runtime.d.ts +12 -3
  211. package/dist/node-runtime.js +8 -7
  212. package/dist/process.d.ts +3 -1
  213. package/dist/process.js +27 -8
  214. package/dist/protocol-client.d.ts +26 -4
  215. package/dist/protocol-client.js +164 -12
  216. package/dist/protocol-frames.d.ts +14 -3
  217. package/dist/protocol-frames.js +26 -1
  218. package/dist/protocol-schema.d.ts +1 -1
  219. package/dist/protocol-schema.js +1 -1
  220. package/dist/request-payloads.d.ts +4 -1
  221. package/dist/request-payloads.js +7 -2
  222. package/dist/response-payloads.d.ts +23 -2
  223. package/dist/response-payloads.js +34 -2
  224. package/dist/sidecar-errors.d.ts +37 -0
  225. package/dist/sidecar-errors.js +27 -0
  226. package/dist/sidecar-process.d.ts +20 -6
  227. package/dist/sidecar-process.js +79 -15
  228. package/dist/state.d.ts +6 -6
  229. package/dist/state.js +10 -11
  230. package/dist/test-runtime.d.ts +21 -6
  231. package/dist/test-runtime.js +129 -65
  232. package/dist/vm-config.d.ts +5 -1
  233. package/package.json +5 -3
  234. package/commands/codex +0 -0
  235. package/commands/codex-exec +0 -0
  236. package/commands/http_get +0 -0
  237. package/dist/generated/ToolLimitsConfig.d.ts +0 -10
  238. /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
@@ -478,13 +494,17 @@ export class NativeSidecarKernelProxy {
478
494
  }
479
495
  entry.pendingKillSignal = signal;
480
496
  void entry.startPromise.then(async () => {
481
- if (entry.exitCode !== null || entry.pendingKillSignal === null) {
497
+ if (entry.pendingKillSignal === null) {
482
498
  return;
483
499
  }
484
500
  const pendingSignal = entry.pendingKillSignal;
485
501
  entry.pendingKillSignal = null;
486
502
  await this.signalProcess(entry, pendingSignal);
487
503
  });
504
+ if ((signal === 9 || signal === 15) &&
505
+ entry.exitCode === null) {
506
+ this.finishProcess(entry, 128 + signal);
507
+ }
488
508
  },
489
509
  wait: async () => {
490
510
  const exitCode = await this.waitForTrackedProcess(entry);
@@ -506,7 +526,7 @@ export class NativeSidecarKernelProxy {
506
526
  return proc;
507
527
  }
508
528
  openShell(options) {
509
- const stdoutHandlers = new Set();
529
+ const terminalHandlers = new Set();
510
530
  const stderrHandlers = new Set();
511
531
  const command = options?.command ?? "sh";
512
532
  const args = options?.args ??
@@ -565,7 +585,7 @@ export class NativeSidecarKernelProxy {
565
585
  const normalized = normalizeSyntheticTerminalText(text);
566
586
  updateSyntheticCursor(normalized);
567
587
  const chunk = textEncoder.encode(normalized);
568
- for (const handler of stdoutHandlers) {
588
+ for (const handler of terminalHandlers) {
569
589
  handler(chunk);
570
590
  }
571
591
  };
@@ -576,7 +596,7 @@ export class NativeSidecarKernelProxy {
576
596
  const normalized = normalizeSyntheticTerminalText(text);
577
597
  updateSyntheticCursor(normalized);
578
598
  const chunk = textEncoder.encode(normalized);
579
- for (const handler of stdoutHandlers) {
599
+ for (const handler of terminalHandlers) {
580
600
  handler(chunk);
581
601
  }
582
602
  };
@@ -621,7 +641,7 @@ export class NativeSidecarKernelProxy {
621
641
  commandInFlight = false;
622
642
  const promptPrefix = syntheticCursorAtLineStart ? "" : "\r\n";
623
643
  const promptChunk = textEncoder.encode(`${promptPrefix}${promptText}`);
624
- for (const handler of stdoutHandlers) {
644
+ for (const handler of terminalHandlers) {
625
645
  handler(promptChunk);
626
646
  }
627
647
  syntheticCursorAtLineStart = false;
@@ -658,7 +678,7 @@ export class NativeSidecarKernelProxy {
658
678
  }
659
679
  };
660
680
  let onData = null;
661
- stdoutHandlers.add((data) => onData?.(data));
681
+ terminalHandlers.add((data) => onData?.(data));
662
682
  if (options?.onStderr) {
663
683
  stderrHandlers.add(options.onStderr);
664
684
  }
@@ -817,7 +837,7 @@ export class NativeSidecarKernelProxy {
817
837
  cwd: options?.cwd,
818
838
  streamStdin: true,
819
839
  onStdout: (chunk) => {
820
- for (const handler of stdoutHandlers) {
840
+ for (const handler of terminalHandlers) {
821
841
  handler(chunk);
822
842
  }
823
843
  if (commandInFlight) {
@@ -825,6 +845,11 @@ export class NativeSidecarKernelProxy {
825
845
  }
826
846
  },
827
847
  onStderr: (chunk) => {
848
+ // `onData` is the ordered PTY rendering stream. `onStderr` remains an
849
+ // optional channel-specific diagnostic tap and must not also be rendered.
850
+ for (const handler of terminalHandlers) {
851
+ handler(chunk);
852
+ }
828
853
  for (const handler of stderrHandlers) {
829
854
  handler(chunk);
830
855
  }
@@ -878,13 +903,7 @@ export class NativeSidecarKernelProxy {
878
903
  const stdin = process.stdin;
879
904
  const stdout = process.stdout;
880
905
  const { onData, ...shellOptions } = options ?? {};
881
- const shell = this.openShell({
882
- ...shellOptions,
883
- onStderr: shellOptions.onStderr ??
884
- ((data) => {
885
- process.stderr.write(data);
886
- }),
887
- });
906
+ const shell = this.openShell(shellOptions);
888
907
  const outputHandler = onData ??
889
908
  ((data) => {
890
909
  stdout.write(data);
@@ -985,6 +1004,10 @@ export class NativeSidecarKernelProxy {
985
1004
  }
986
1005
  return this.client.rename(this.session, this.vm, oldPath, newPath);
987
1006
  }
1007
+ // Test-runtime only: runtime mounts registered here stay host-side local
1008
+ // compat mounts and are not delivered to the sidecar. The production proxy
1009
+ // in @rivet-dev/agentos-core reconfigures sidecar mounts on every
1010
+ // mountFs/unmountFs.
988
1011
  mountFs(path, driver, options) {
989
1012
  this.localMounts.unshift({
990
1013
  path: posixPath.normalize(path),
@@ -1124,15 +1147,6 @@ export class NativeSidecarKernelProxy {
1124
1147
  if (entry.onStdout.size === 0 && entry.onStderr.size === 0) {
1125
1148
  return;
1126
1149
  }
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
1150
  let observedGeneration = entry.outputGeneration;
1137
1151
  let quietTurns = 0;
1138
1152
  let delayMs = 0;
@@ -1303,9 +1317,27 @@ export class NativeSidecarKernelProxy {
1303
1317
  return entry.waitWithFallbackPromise;
1304
1318
  }
1305
1319
  async signalProcess(entry, signal) {
1306
- await this.signalRefreshes.get(entry.pid);
1320
+ const sidecarSignal = toSidecarSignalName(signal);
1321
+ let timedOut = false;
1322
+ const killPromise = this.client.killProcess(this.session, this.vm, entry.processId, sidecarSignal);
1307
1323
  try {
1308
- await this.client.killProcess(this.session, this.vm, entry.processId, toSidecarSignalName(signal));
1324
+ await Promise.race([
1325
+ killPromise,
1326
+ new Promise((resolve) => setTimeout(() => {
1327
+ timedOut = true;
1328
+ resolve();
1329
+ }, 1000)),
1330
+ ]);
1331
+ if (timedOut) {
1332
+ void killPromise.catch(() => { });
1333
+ }
1334
+ const hasGuestHandler = this.signalStates.get(entry.pid)?.handlers.has(signal) ?? false;
1335
+ if (entry.exitCode === null &&
1336
+ !hasGuestHandler &&
1337
+ LOCALLY_TERMINATING_SIGNALS.has(sidecarSignal) &&
1338
+ (entry.driver === "wasmvm" || timedOut)) {
1339
+ this.finishProcess(entry, 128 + signal);
1340
+ }
1309
1341
  }
1310
1342
  catch (error) {
1311
1343
  if (isNoSuchProcessError(error) || isUnknownVmError(error)) {
@@ -1520,12 +1552,12 @@ export class NativeSidecarKernelProxy {
1520
1552
  return this.client.pread(this.session, this.vm, path, offset, length);
1521
1553
  },
1522
1554
  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);
1555
+ const local = includeLocalMounts ? this.resolveLocalMount(path) : null;
1556
+ if (local) {
1557
+ this.assertLocalWritable(local.mount);
1558
+ return local.mount.fs.pwrite(local.relativePath, offset, data);
1559
+ }
1560
+ return this.client.pwrite(this.session, this.vm, path, offset, data);
1529
1561
  },
1530
1562
  };
1531
1563
  }
@@ -1727,21 +1759,27 @@ function isMissingHostProcessError(error) {
1727
1759
  function errnoError(code, message) {
1728
1760
  return Object.assign(new Error(`${code}: ${message}`), { code });
1729
1761
  }
1762
+ // VirtualStat is a numeric, Node-default-shaped view: u64 fields above
1763
+ // Number.MAX_SAFE_INTEGER lose precision here, same as Node's non-bigint
1764
+ // fs.stat on the host.
1730
1765
  function toVirtualStat(stat) {
1731
1766
  return {
1732
1767
  mode: stat.mode,
1733
- size: stat.size,
1734
- blocks: stat.blocks,
1735
- dev: stat.dev,
1736
- rdev: stat.rdev,
1768
+ size: Number(stat.size),
1769
+ sizeExact: stat.size,
1770
+ blocks: Number(stat.blocks),
1771
+ dev: Number(stat.dev),
1772
+ rdev: Number(stat.rdev),
1737
1773
  isDirectory: stat.is_directory,
1738
1774
  isSymbolicLink: stat.is_symbolic_link,
1739
1775
  atimeMs: stat.atime_ms,
1740
1776
  mtimeMs: stat.mtime_ms,
1741
1777
  ctimeMs: stat.ctime_ms,
1742
1778
  birthtimeMs: stat.birthtime_ms,
1743
- ino: stat.ino,
1744
- nlink: stat.nlink,
1779
+ ino: Number(stat.ino),
1780
+ inoExact: stat.ino,
1781
+ nlink: Number(stat.nlink),
1782
+ nlinkExact: stat.nlink,
1745
1783
  uid: stat.uid,
1746
1784
  gid: stat.gid,
1747
1785
  };
@@ -1774,4 +1812,4 @@ function socketLookupKey(kind, request) {
1774
1812
  path: request.path ?? null,
1775
1813
  });
1776
1814
  }
1777
- export { NATIVE_SIDECAR_FRAME_TIMEOUT_MS, SidecarProcess, SidecarEventBufferOverflow, SidecarProcessError, SidecarProcessExited, } from "./sidecar-process.js";
1815
+ 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;
@@ -22,9 +21,21 @@ export class StdioSidecarProtocolClient {
22
21
  this.protocolClient = new SidecarProtocolClient({
23
22
  stdin: this.child.stdin,
24
23
  stdout: this.child.stdout,
25
- frameTimeoutMs: options.frameTimeoutMs,
24
+ control: this.sidecarProcess.control,
26
25
  eventBufferCapacity: options.eventBufferCapacity,
27
26
  payloadCodec: options.payloadCodec,
27
+ silenceTimeoutMs: options.silenceTimeoutMs,
28
+ // A silent sidecar is dead or wedged; reap the process so it cannot
29
+ // linger as a zombie holding VM resources. The watchdog then rejects
30
+ // all in-flight requests with `SidecarSilenceTimeout`.
31
+ onSilenceExpired: () => {
32
+ try {
33
+ this.child.kill("SIGKILL");
34
+ }
35
+ catch {
36
+ // The child may have exited between the check and the kill.
37
+ }
38
+ },
28
39
  stderrText: () => this.sidecarProcess.stderrText(),
29
40
  streamEndedError: () => this.sidecarProcess.currentExitError() ??
30
41
  new SidecarProcessExited({
@@ -47,9 +58,8 @@ export class StdioSidecarProtocolClient {
47
58
  args: options.args ?? [],
48
59
  cwd: options.cwd,
49
60
  }), {
50
- frameTimeoutMs: options.frameTimeoutMs ?? DEFAULT_SIDECAR_FRAME_TIMEOUT_MS,
51
- eventBufferCapacity: options.eventBufferCapacity ??
52
- DEFAULT_SIDECAR_EVENT_BUFFER_CAPACITY,
61
+ silenceTimeoutMs: options.silenceTimeoutMs,
62
+ eventBufferCapacity: options.eventBufferCapacity ?? DEFAULT_SIDECAR_EVENT_BUFFER_CAPACITY,
53
63
  gracefulExitMs: options.gracefulExitMs ?? DEFAULT_SIDECAR_GRACEFUL_EXIT_MS,
54
64
  forceExitMs: options.forceExitMs ?? DEFAULT_SIDECAR_FORCE_EXIT_MS,
55
65
  disposedErrorMessage: options.disposedErrorMessage ?? "sidecar client disposed",
@@ -69,6 +79,13 @@ export class StdioSidecarProtocolClient {
69
79
  return await this.protocolClient.waitForEvent(matcher, timeoutMs, options);
70
80
  }
71
81
  async dispose() {
82
+ let shutdownError = null;
83
+ try {
84
+ await this.protocolClient.shutdown(this.disposedErrorMessage);
85
+ }
86
+ catch (error) {
87
+ shutdownError = error instanceof Error ? error : new Error(String(error));
88
+ }
72
89
  this.protocolClient.failPermanently(new Error(this.disposedErrorMessage));
73
90
  if (!this.child.stdin.destroyed) {
74
91
  try {
@@ -78,6 +95,14 @@ export class StdioSidecarProtocolClient {
78
95
  // Stdin may already be closing. The child exit watcher will catch up.
79
96
  }
80
97
  }
98
+ if (!this.sidecarProcess.control.destroyed) {
99
+ try {
100
+ this.sidecarProcess.control.end();
101
+ }
102
+ catch {
103
+ // The control socket may already be closing with the child.
104
+ }
105
+ }
81
106
  const exitCode = await this.sidecarProcess.waitForExit(this.gracefulExitMs);
82
107
  if (exitCode === null) {
83
108
  try {
@@ -107,11 +132,18 @@ export class StdioSidecarProtocolClient {
107
132
  catch {
108
133
  // Best effort. The child is gone so the descriptor will close on its own.
109
134
  }
110
- if (exitCode !== null &&
111
- exitCode !== 0 &&
112
- this.child.signalCode === null) {
135
+ try {
136
+ this.sidecarProcess.control.destroy();
137
+ }
138
+ catch {
139
+ // Best effort. The child is gone so the descriptor will close on its own.
140
+ }
141
+ if (exitCode !== null && exitCode !== 0 && this.child.signalCode === null) {
113
142
  throw new Error(`sidecar exited with code ${exitCode}\nstderr:\n${this.sidecarProcess.stderrText()}`);
114
143
  }
144
+ if (shutdownError && this.child.exitCode !== 0) {
145
+ throw shutdownError;
146
+ }
115
147
  }
116
148
  failPermanently(error) {
117
149
  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,4 +1,5 @@
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 {
@@ -8,12 +9,13 @@ export interface StdioSidecarProcessSpawnOptions {
8
9
  }
9
10
  export declare class StdioSidecarProcess {
10
11
  readonly child: ChildProcessWithoutNullStreams;
12
+ readonly control: Duplex;
11
13
  private readonly stderrChunks;
12
14
  private readonly exitListeners;
13
15
  private readonly errorListeners;
14
16
  private constructor();
15
17
  static spawn(options: StdioSidecarProcessSpawnOptions): StdioSidecarProcess;
16
- static fromChild(child: ChildProcessWithoutNullStreams): StdioSidecarProcess;
18
+ static fromChild(child: ChildProcessWithoutNullStreams, control?: Duplex): StdioSidecarProcess;
17
19
  onExit(handler: (error: SidecarProcessExited) => void): () => void;
18
20
  onError(handler: (error: SidecarProcessError) => void): () => void;
19
21
  stderrText(): string;
package/dist/process.js CHANGED
@@ -1,13 +1,15 @@
1
- import { spawn, } from "node:child_process";
1
+ import { spawn } from "node:child_process";
2
2
  export { SidecarProcessError, SidecarProcessExited, } from "./sidecar-errors.js";
3
- import { SidecarProcessError, SidecarProcessExited, } from "./sidecar-errors.js";
3
+ import { SidecarProcessError, SidecarProcessExited } from "./sidecar-errors.js";
4
4
  export class StdioSidecarProcess {
5
5
  child;
6
+ control;
6
7
  stderrChunks = [];
7
8
  exitListeners = new Set();
8
9
  errorListeners = new Set();
9
- constructor(child) {
10
+ constructor(child, control) {
10
11
  this.child = child;
12
+ this.control = control;
11
13
  this.child.stderr.on("data", (chunk) => {
12
14
  this.stderrChunks.push(typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk));
13
15
  });
@@ -30,13 +32,20 @@ export class StdioSidecarProcess {
30
32
  });
31
33
  }
32
34
  static spawn(options) {
33
- return new StdioSidecarProcess(spawn(options.command, options.args ?? [], {
35
+ const child = spawn(options.command, options.args ?? [], {
34
36
  cwd: options.cwd,
35
- stdio: ["pipe", "pipe", "pipe"],
36
- }));
37
+ stdio: ["pipe", "pipe", "pipe", "pipe"],
38
+ });
39
+ try {
40
+ return new StdioSidecarProcess(child, requireControlStream(child));
41
+ }
42
+ catch (error) {
43
+ child.kill("SIGKILL");
44
+ throw error;
45
+ }
37
46
  }
38
- static fromChild(child) {
39
- return new StdioSidecarProcess(child);
47
+ static fromChild(child, control) {
48
+ return new StdioSidecarProcess(child, control ?? requireControlStream(child));
40
49
  }
41
50
  onExit(handler) {
42
51
  this.exitListeners.add(handler);
@@ -95,3 +104,13 @@ export class StdioSidecarProcess {
95
104
  });
96
105
  }
97
106
  }
107
+ function requireControlStream(child) {
108
+ const stream = child.stdio[3];
109
+ if (!stream ||
110
+ typeof stream.write !== "function" ||
111
+ typeof stream.on !== "function" ||
112
+ typeof stream.read !== "function") {
113
+ throw new Error("sidecar process did not expose a full-duplex fd 3");
114
+ }
115
+ return stream;
116
+ }