@rivet-dev/agentos-core 0.4.0 → 0.4.2

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.
@@ -1,7 +1,7 @@
1
1
  import type { NativeMountPluginDescriptor } from "@rivet-dev/agentos-runtime-core/descriptors";
2
2
  import type { CreateVmConfig, VmUserConfig } from "@rivet-dev/agentos-runtime-core/vm-config";
3
3
  import { type HostFunctionCollections, type HostFunctionSchemas } from "@rivet-dev/agentos-runtime-core/host-functions";
4
- import type { CodeEvaluationResult, CodeExecutionResult, ContextDescriptor, ExecutionSignal, InlineExecutionOptions, JavaScriptEvaluationOptions, JavaScriptExecutionOptions, JsonValue, LanguageExecutionOptions, LanguageSpawnOptions, NpmPackageInstallOptions, NpmProjectInstallOptions, OutputReplay, ProcessDescriptor, ProcessExit, PythonInstallOptions, SpawnOptions, TypeScriptCheckOptions, TypeScriptCheckResult, TypeScriptEvaluationOptions, TypeScriptExecutionOptions, TypeScriptFileExecutionOptions } from "./language-execution.js";
4
+ import type { CodeEvaluationResult, CodeExecutionResult, ContextDescriptor, ExecutionSignal, InlineExecutionOptions, JavaScriptEvaluationOptions, JavaScriptExecutionOptions, JsonValue, LanguageExecutionOptions, LanguageSpawnOptions, NpmPackageInstallOptions, NpmProjectInstallOptions, OutputReplay, ProcessDescriptor, ProcessExit, ProcessSpawnOptions, PythonInstallOptions, TypeScriptCheckOptions, TypeScriptCheckResult, TypeScriptEvaluationOptions, TypeScriptExecutionOptions, TypeScriptFileExecutionOptions } from "./language-execution.js";
5
5
  import type { ConnectTerminalOptions, Kernel, KernelExecOptions, KernelExecResult, OpenShellOptions, Permissions, VirtualFileSystem, VirtualStat } from "./runtime-compat.js";
6
6
  import { type AgentOsSandboxInput } from "./sandbox.js";
7
7
  import { type ReplayReadOptions } from "./output-replay.js";
@@ -461,7 +461,7 @@ export declare class AgentOs {
461
461
  readonly process: {
462
462
  exec: (command: string, options?: LanguageExecutionOptions) => Promise<CodeExecutionResult>;
463
463
  execFile: (command: string, args?: readonly string[], options?: Omit<LanguageExecutionOptions, "args">) => Promise<CodeExecutionResult>;
464
- spawn: (command: string, args?: string[], options?: SpawnOptions) => Promise<ProcessDescriptor>;
464
+ spawn: (command: string, args?: string[], options?: ProcessSpawnOptions) => Promise<ProcessDescriptor>;
465
465
  get: (pid: number) => Promise<ProcessDescriptor>;
466
466
  list: () => Promise<ProcessDescriptor[]>;
467
467
  tree: () => Promise<ProcessTreeNode[]>;
@@ -630,7 +630,7 @@ export declare class AgentOs {
630
630
  private _reserveProcessRegistrySlot;
631
631
  private _pruneExitedProcesses;
632
632
  private _spawnProcess;
633
- spawn(command: string, args?: string[], options?: SpawnOptions): Promise<ProcessDescriptor>;
633
+ spawn(command: string, args?: string[], options?: ProcessSpawnOptions): Promise<ProcessDescriptor>;
634
634
  /** Write data to a process's stdin. */
635
635
  private _writeProcessStdin;
636
636
  /** Close a process's stdin stream. */
package/dist/agent-os.js CHANGED
@@ -2648,7 +2648,7 @@ export class AgentOs {
2648
2648
  env: options.env,
2649
2649
  stdin: options.stdin,
2650
2650
  timeout: options.timeoutMs,
2651
- streamStdin: true,
2651
+ streamStdin: options.streamStdin ?? true,
2652
2652
  retainOutput: options.output?.retainEvents ?? false,
2653
2653
  onStdout: (data, metadata) => {
2654
2654
  options?.onStdout?.(data);
@@ -3926,6 +3926,17 @@ function ensureSharedSidecarNativeProcess(sidecar) {
3926
3926
  applySharedSidecarHold(state);
3927
3927
  try {
3928
3928
  const session = await client.authenticateAndOpenSession();
3929
+ // A transport that fails for good (process exit, silence watchdog,
3930
+ // event buffer overflow, undecodable frame) leaves the VMs out of
3931
+ // reach. Stop the process, as the Rust client does, and report the
3932
+ // handle disposed so a shared pool spawns a fresh sidecar.
3933
+ const child = state.sharedChild;
3934
+ client.onClose(() => {
3935
+ child?.kill?.("SIGKILL");
3936
+ if (state.description.state === "ready") {
3937
+ state.description.state = "disposed";
3938
+ }
3939
+ });
3929
3940
  return { client, session };
3930
3941
  }
3931
3942
  catch (error) {
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/dist/default-software/coreutils.aospkg
3
- size: 67400857 bytes
4
- sha256: e269c2e6a04b2d0027e66d0efb49a2937304decc7963dcb66f394cdd5dad4186
3
+ size: 67548825 bytes
4
+ sha256: be05ed39037fb797efb65701f766f9a9bac8b11f80596226fcfaad4d52488656
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -110,6 +110,13 @@ export interface SpawnOptions {
110
110
  };
111
111
  }
112
112
  export type LanguageSpawnOptions = SpawnOptions;
113
+ export interface ProcessSpawnOptions extends SpawnOptions {
114
+ /**
115
+ * Keep stdin open for `writeStdin`. Defaults to `true`. With `false` the
116
+ * process reads EOF as soon as it starts, as with `</dev/null`.
117
+ */
118
+ streamStdin?: boolean;
119
+ }
113
120
  export interface ExecutionErrorData {
114
121
  code: string;
115
122
  name: string;
@@ -2,7 +2,7 @@ import type { RootFilesystemConfig as VmConfigRootFilesystemConfig, RootFilesyst
2
2
  import type { NativeMountConfig, PlainMountConfig, RootFilesystemConfig } from "../agent-os.js";
3
3
  import type { RootSnapshotExport } from "../layers.js";
4
4
  import type { ProcessForestRecord } from "../process-forest.js";
5
- import type { ConnectTerminalOptions, KernelExecOptions, KernelExecResult, KernelSpawnOptions, ManagedProcess, OpenShellOptions, ProcessInfo, ShellHandle, VirtualFileSystem, VirtualStat } from "../runtime-compat.js";
5
+ import { type ConnectTerminalOptions, type KernelExecOptions, type KernelExecResult, type KernelSpawnOptions, type ManagedProcess, type OpenShellOptions, type ProcessInfo, type ShellHandle, type VirtualFileSystem, type VirtualStat } from "../runtime-compat.js";
6
6
  import type { AuthenticatedSession, CreatedVm, SidecarPermissionsPolicy, SidecarProcess, SidecarSignalHandlerRegistration } from "./native-process-client.js";
7
7
  export declare function toSidecarSignalName(signal: number): string;
8
8
  export interface LocalCompatMount {
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { constants as osConstants } from "node:os";
3
3
  import { posix as posixPath } from "node:path";
4
4
  import { SidecarRejectedError } from "@rivet-dev/agentos-runtime-core/sidecar-errors";
5
+ import { KernelError, } from "../runtime-compat.js";
5
6
  const SYNTHETIC_PID_BASE = 1_000_000;
6
7
  const MISSING_EXIT_EVENT_GRACE_MS = 500;
7
8
  const PROTECTED_READ_ONLY_GUEST_ROOTS = ["/etc/agentos"];
@@ -458,11 +459,13 @@ export class NativeSidecarKernelProxy {
458
459
  const parsedCommand = parseSimpleExecCommand(command);
459
460
  const runAndCapture = async (proc, stdinOverride, readExitCode) => {
460
461
  const shellExitCode = await runManagedProcessToCompletion(proc, async () => {
462
+ // A command may exit without reading its input, as `true` does in
463
+ // `echo x | true`; its exit status is the result.
461
464
  if (stdinOverride !== undefined) {
462
- await proc.writeStdin(stdinOverride);
465
+ await proc.writeStdin(stdinOverride).catch(ignoreBrokenPipe);
463
466
  }
464
467
  else if (options?.stdin !== undefined) {
465
- await proc.writeStdin(options.stdin);
468
+ await proc.writeStdin(options.stdin).catch(ignoreBrokenPipe);
466
469
  }
467
470
  // Non-interactive runs must observe EOF after any provided input.
468
471
  await proc.closeStdin();
@@ -527,7 +530,7 @@ export class NativeSidecarKernelProxy {
527
530
  const runAndCapture = async (proc) => {
528
531
  const exitCode = await runManagedProcessToCompletion(proc, async () => {
529
532
  if (options?.stdin !== undefined) {
530
- await proc.writeStdin(options.stdin);
533
+ await proc.writeStdin(options.stdin).catch(ignoreBrokenPipe);
531
534
  }
532
535
  await proc.closeStdin();
533
536
  }, deadlineMs);
@@ -620,7 +623,7 @@ export class NativeSidecarKernelProxy {
620
623
  processId,
621
624
  writeStdin: (data) => {
622
625
  if (entry.exitCode !== null) {
623
- return Promise.resolve();
626
+ return Promise.reject(stdinHasNoReader(entry.pid));
624
627
  }
625
628
  if (entry.waitError)
626
629
  return Promise.reject(entry.waitError);
@@ -1712,7 +1715,10 @@ export class NativeSidecarKernelProxy {
1712
1715
  if (entry.waitError)
1713
1716
  throw entry.waitError;
1714
1717
  if (entry.exitCode !== null) {
1715
- return;
1718
+ if (entry.pendingStdin.length === 0)
1719
+ return;
1720
+ entry.pendingStdin.length = 0;
1721
+ throw stdinHasNoReader(entry.pid);
1716
1722
  }
1717
1723
  while (entry.pendingStdin.length > 0) {
1718
1724
  const chunk = entry.pendingStdin.shift();
@@ -1725,9 +1731,9 @@ export class NativeSidecarKernelProxy {
1725
1731
  .catch((error) => {
1726
1732
  if (entry.exitCode !== null &&
1727
1733
  (isNoSuchProcessError(error) || isUnknownVmError(error))) {
1728
- return;
1734
+ throw stdinHasNoReader(entry.pid);
1729
1735
  }
1730
- throw error;
1736
+ throw guestStdinError(error) ?? error;
1731
1737
  })
1732
1738
  .finally(() => {
1733
1739
  entry.stdinFlushPromise = null;
@@ -1745,7 +1751,8 @@ export class NativeSidecarKernelProxy {
1745
1751
  await entry.startPromise;
1746
1752
  if (entry.waitError)
1747
1753
  throw entry.waitError;
1748
- await this.flushPendingStdin(entry);
1754
+ // Closing stdin succeeds after exit even though queued writes had no reader.
1755
+ await this.flushPendingStdin(entry).catch(ignoreBrokenPipe);
1749
1756
  if (entry.exitCode !== null || !entry.pendingCloseStdin) {
1750
1757
  return;
1751
1758
  }
@@ -1758,10 +1765,14 @@ export class NativeSidecarKernelProxy {
1758
1765
  (isNoSuchProcessError(error) || isUnknownVmError(error))) {
1759
1766
  return;
1760
1767
  }
1761
- throw error;
1768
+ throw guestStdinError(error) ?? error;
1762
1769
  }
1763
1770
  }
1764
1771
  handleBackgroundProcessError(entry, error) {
1772
+ // The writer already received the errno; a stdin write the guest
1773
+ // refused is not a process failure.
1774
+ if (isRefusedStdinWrite(error))
1775
+ return;
1765
1776
  if (entry.exitCode !== null) {
1766
1777
  // An I/O failure after exit must never rewrite the guest's real status.
1767
1778
  if (!isNoSuchProcessError(error) && !isUnknownVmError(error)) {
@@ -2194,6 +2205,32 @@ function buildCommandMap(commandGuestPaths) {
2194
2205
  }
2195
2206
  return commands;
2196
2207
  }
2208
+ /** Stdin written after its process exited, as a pipe write with no reader on Linux. */
2209
+ function stdinHasNoReader(pid) {
2210
+ return new KernelError("EPIPE", `process ${pid} has exited, so its stdin has no reader`);
2211
+ }
2212
+ /**
2213
+ * A stdin rejection that carries a guest errno, such as EBADF for a write
2214
+ * after closeStdin, as the KernelError the Rust client also returns.
2215
+ */
2216
+ function guestStdinError(error) {
2217
+ if (!(error instanceof SidecarRejectedError) || !error.detail.errno)
2218
+ return null;
2219
+ return new KernelError(error.detail.errno, error.detail.message);
2220
+ }
2221
+ /** EPIPE (no reader) or EBADF (stdin closed): the guest refused the write. */
2222
+ function isRefusedStdinWrite(error) {
2223
+ return (error instanceof KernelError &&
2224
+ (error.code === "EPIPE" || error.code === "EBADF"));
2225
+ }
2226
+ function isBrokenPipe(error) {
2227
+ return error instanceof KernelError && error.code === "EPIPE";
2228
+ }
2229
+ function ignoreBrokenPipe(error) {
2230
+ if (isBrokenPipe(error))
2231
+ return;
2232
+ throw error;
2233
+ }
2197
2234
  function isNoSuchProcessError(error) {
2198
2235
  if (!(error instanceof Error)) {
2199
2236
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rivet-dev/agentos-core",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "main": "./dist/index.js",
@@ -60,8 +60,8 @@
60
60
  },
61
61
  "dependencies": {
62
62
  "@aws-sdk/client-s3": "^3.1019.0",
63
- "@rivet-dev/agentos-runtime-sidecar": "0.4.0",
64
- "@rivet-dev/agentos-runtime-core": "0.4.0",
63
+ "@rivet-dev/agentos-runtime-sidecar": "0.4.2",
64
+ "@rivet-dev/agentos-runtime-core": "0.4.2",
65
65
  "@xterm/headless": "^6.0.0",
66
66
  "better-sqlite3": "^12.8.0",
67
67
  "croner": "^10.0.1",
@@ -73,7 +73,7 @@
73
73
  },
74
74
  "devDependencies": {
75
75
  "@bare-ts/lib": "^0.4.0",
76
- "@rivet-dev/agentos-test-harness": "0.4.0",
76
+ "@rivet-dev/agentos-test-harness": "0.4.2",
77
77
  "@browserbasehq/browse-cli": "0.5.0",
78
78
  "@browserbasehq/cli": "0.5.4",
79
79
  "@browserbasehq/sdk": "2.10.0",