@rivet-dev/agentos-core 0.2.5-rc.3 → 0.2.5-rc.4

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 (93) hide show
  1. package/README.md +4 -4
  2. package/dist/agent-os.d.ts +25 -28
  3. package/dist/agent-os.js +129 -403
  4. package/dist/agentos-package.d.ts +3 -3
  5. package/dist/agentos-package.js +7 -1
  6. package/dist/cron/types.d.ts +1 -1
  7. package/dist/default-software.d.ts +6 -46
  8. package/dist/default-software.js +7 -90
  9. package/dist/generated/AcpLimitsConfig.d.ts +4 -0
  10. package/dist/generated/AcpLimitsConfig.js +2 -0
  11. package/dist/generated/CreateVmConfig.d.ts +26 -0
  12. package/dist/generated/CreateVmConfig.js +1 -0
  13. package/dist/generated/FsPermissionRule.d.ts +6 -0
  14. package/dist/generated/FsPermissionRule.js +1 -0
  15. package/dist/generated/FsPermissionRuleSet.d.ts +6 -0
  16. package/dist/generated/FsPermissionRuleSet.js +1 -0
  17. package/dist/generated/FsPermissionScope.d.ts +3 -0
  18. package/dist/generated/FsPermissionScope.js +1 -0
  19. package/dist/generated/HttpLimitsConfig.d.ts +3 -0
  20. package/dist/generated/HttpLimitsConfig.js +2 -0
  21. package/dist/generated/JsModuleResolution.d.ts +1 -0
  22. package/dist/generated/JsModuleResolution.js +2 -0
  23. package/dist/generated/JsRuntimeConfig.d.ts +31 -0
  24. package/dist/generated/JsRuntimeConfig.js +1 -0
  25. package/dist/generated/JsRuntimeLimitsConfig.d.ts +11 -0
  26. package/dist/generated/JsRuntimeLimitsConfig.js +2 -0
  27. package/dist/generated/JsRuntimePlatform.d.ts +1 -0
  28. package/dist/generated/JsRuntimePlatform.js +2 -0
  29. package/dist/generated/MountPluginDescriptor.d.ts +4 -0
  30. package/dist/generated/MountPluginDescriptor.js +2 -0
  31. package/dist/generated/NativeRootFilesystemConfig.d.ts +5 -0
  32. package/dist/generated/NativeRootFilesystemConfig.js +1 -0
  33. package/dist/generated/PatternPermissionRule.d.ts +6 -0
  34. package/dist/generated/PatternPermissionRule.js +1 -0
  35. package/dist/generated/PatternPermissionRuleSet.d.ts +6 -0
  36. package/dist/generated/PatternPermissionRuleSet.js +1 -0
  37. package/dist/generated/PatternPermissionScope.d.ts +3 -0
  38. package/dist/generated/PatternPermissionScope.js +1 -0
  39. package/dist/generated/PermissionMode.d.ts +1 -0
  40. package/dist/generated/PermissionMode.js +2 -0
  41. package/dist/generated/PermissionsPolicy.d.ts +10 -0
  42. package/dist/generated/PermissionsPolicy.js +1 -0
  43. package/dist/generated/PluginLimitsConfig.d.ts +4 -0
  44. package/dist/generated/PluginLimitsConfig.js +2 -0
  45. package/dist/generated/PythonLimitsConfig.d.ts +6 -0
  46. package/dist/generated/PythonLimitsConfig.js +2 -0
  47. package/dist/generated/ResourceLimitsConfig.d.ts +24 -0
  48. package/dist/generated/ResourceLimitsConfig.js +2 -0
  49. package/dist/generated/RootFilesystemConfig.d.ts +9 -0
  50. package/dist/generated/RootFilesystemConfig.js +1 -0
  51. package/dist/generated/RootFilesystemEntry.d.ts +13 -0
  52. package/dist/generated/RootFilesystemEntry.js +1 -0
  53. package/dist/generated/RootFilesystemEntryEncoding.d.ts +1 -0
  54. package/dist/generated/RootFilesystemEntryEncoding.js +2 -0
  55. package/dist/generated/RootFilesystemEntryKind.d.ts +1 -0
  56. package/dist/generated/RootFilesystemEntryKind.js +2 -0
  57. package/dist/generated/RootFilesystemLowerDescriptor.d.ts +7 -0
  58. package/dist/generated/RootFilesystemLowerDescriptor.js +1 -0
  59. package/dist/generated/RootFilesystemMode.d.ts +1 -0
  60. package/dist/generated/RootFilesystemMode.js +2 -0
  61. package/dist/generated/ToolLimitsConfig.d.ts +10 -0
  62. package/dist/generated/ToolLimitsConfig.js +2 -0
  63. package/dist/generated/VmDnsConfig.d.ts +6 -0
  64. package/dist/generated/VmDnsConfig.js +2 -0
  65. package/dist/generated/VmLimitsConfig.d.ts +18 -0
  66. package/dist/generated/VmLimitsConfig.js +1 -0
  67. package/dist/generated/VmListenPolicyConfig.d.ts +5 -0
  68. package/dist/generated/VmListenPolicyConfig.js +2 -0
  69. package/dist/generated/WasmLimitsConfig.d.ts +7 -0
  70. package/dist/generated/WasmLimitsConfig.js +2 -0
  71. package/dist/host-dir-mount.d.ts +1 -1
  72. package/dist/index.d.ts +0 -1
  73. package/dist/index.js +0 -1
  74. package/dist/options-schema.d.ts +14 -1
  75. package/dist/options-schema.js +27 -21
  76. package/dist/packages.d.ts +2 -2
  77. package/dist/packages.js +20 -6
  78. package/dist/runtime-compat.d.ts +17 -4
  79. package/dist/runtime-compat.js +51 -88
  80. package/dist/runtime.d.ts +27 -0
  81. package/dist/sandbox.d.ts +112 -0
  82. package/dist/sandbox.js +264 -0
  83. package/dist/sidecar/agentos-protocol.d.ts +27 -1
  84. package/dist/sidecar/agentos-protocol.js +77 -20
  85. package/dist/sidecar/native-process-client.d.ts +5 -4
  86. package/dist/sidecar/native-process-client.js +4 -4
  87. package/dist/sidecar/permissions.d.ts +1 -1
  88. package/dist/sidecar/rpc-client.d.ts +21 -2
  89. package/dist/sidecar/rpc-client.js +113 -41
  90. package/dist/types.d.ts +7 -1
  91. package/package.json +34 -35
  92. package/dist/agents.d.ts +0 -79
  93. package/dist/agents.js +0 -48
package/dist/agent-os.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { randomUUID } from "node:crypto";
3
- import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, } from "node:fs";
4
- import { tmpdir } from "node:os";
3
+ import { existsSync, readdirSync, statSync } from "node:fs";
5
4
  import { join, posix as posixPath, resolve as resolveHostPath, } from "node:path";
6
5
  import { fileURLToPath } from "node:url";
7
6
  import { validateToolkits } from "./host-tools.js";
@@ -13,14 +12,6 @@ export { isAcpTimeoutErrorData } from "./json-rpc.js";
13
12
  const ACP_PROTOCOL_VERSION = 1;
14
13
  const ACP_EXTENSION_NAMESPACE = "dev.rivet.agent-os.acp";
15
14
  const SHELL_DISPOSE_TIMEOUT_MS = 5_000;
16
- /**
17
- * Reserved `env` key on `AcpResumeSessionRequest` carrying the resolved adapter
18
- * bin entrypoint. The resume wire request omits a dedicated `adapterEntrypoint`
19
- * field; the sidecar reads the entrypoint from this key and strips it before
20
- * launching the adapter. Must stay in sync with the sidecar constant of the same
21
- * name in `crates/agentos-sidecar/src/acp_extension.rs`.
22
- */
23
- const RESUME_ADAPTER_ENTRYPOINT_ENV = "AGENT_OS_RESUME_ADAPTER_ENTRYPOINT";
24
15
  function defaultAcpClientCapabilities() {
25
16
  return {
26
17
  fs: {
@@ -45,12 +36,10 @@ import { getBaseEnvironment } from "./base-filesystem.js";
45
36
  import { CronManager } from "./cron/cron-manager.js";
46
37
  import { TimerScheduleDriver } from "./cron/timer-driver.js";
47
38
  import { snapshotVirtualFilesystem, sortFilesystemEntries, } from "./filesystem-snapshot.js";
48
- import { createHostDirBackend } from "./host-dir-mount.js";
49
39
  import { serializeMountConfigForSidecar, } from "./js-bridge.js";
50
40
  import { createSnapshotExport, } from "./layers.js";
51
- import { resolveAgentSnapshotBundle, } from "./packages.js";
52
- import { OPT_AGENTOS_BIN, OPT_AGENTOS_ROOT, tryReadAgentosPackageManifest, } from "./agentos-package.js";
53
- import { resolveDefaultSoftware, resolveDependencyAgents, } from "./default-software.js";
41
+ import { OPT_AGENTOS_ROOT, } from "./agentos-package.js";
42
+ import { resolveDefaultSoftware } from "./default-software.js";
54
43
  import { allowAll } from "./runtime-compat.js";
55
44
  import { AcpRuntimeKind, decodeAcpCallback, decodeAcpEvent, decodeAcpResponse, encodeAcpCallbackResponse, encodeAcpRequest, } from "./sidecar/agentos-protocol.js";
56
45
  import { serializePermissionsForSidecar } from "./sidecar/permissions.js";
@@ -114,52 +103,27 @@ function toRecord(value) {
114
103
  : {};
115
104
  }
116
105
  function normalizePackageRef(value) {
106
+ // The single package reference is `packagePath`: the packed `.aospkg` file
107
+ // (registry-built packages export `{ packagePath }`), or a package dir for
108
+ // local transition fixtures. A raw string is shorthand for the same path.
117
109
  if (typeof value === "string") {
118
- return { dir: value };
110
+ return { path: value };
119
111
  }
120
112
  const record = toRecord(value);
121
- if (typeof record.packageDir === "string") {
122
- return {
123
- dir: record.packageDir,
124
- legacyManifest: legacyPackageManifest(record),
125
- };
113
+ if (typeof record.packagePath === "string") {
114
+ return { path: record.packagePath };
126
115
  }
127
- if (typeof record.dir === "string") {
128
- return {
129
- dir: record.dir,
130
- legacyManifest: legacyPackageManifest(record),
131
- };
116
+ // Recognizably-legacy shapes fail loudly: silently dropping a software
117
+ // entry boots a VM with missing packages and no diagnostic.
118
+ for (const legacy of ["packageTar", "packageDir", "dir"]) {
119
+ if (typeof record[legacy] === "string") {
120
+ throw new Error(`agentOS package ref uses removed field "${legacy}" (value: ${JSON.stringify(record[legacy])}); ` +
121
+ "packages are referenced by a single `packagePath` — update the package " +
122
+ "(rebuild @agentos-software/* dependencies) or pass { packagePath }");
123
+ }
132
124
  }
133
125
  return undefined;
134
126
  }
135
- function legacyPackageManifest(record) {
136
- if (typeof record.name !== "string") {
137
- return undefined;
138
- }
139
- const manifest = { name: record.name };
140
- const agent = toRecord(record.agent);
141
- if (typeof agent.acpEntrypoint === "string") {
142
- manifest.agent = {
143
- acpEntrypoint: agent.acpEntrypoint,
144
- ...(isStringRecord(agent.env) ? { env: agent.env } : {}),
145
- ...(Array.isArray(agent.launchArgs) &&
146
- agent.launchArgs.every((arg) => typeof arg === "string")
147
- ? { launchArgs: agent.launchArgs }
148
- : {}),
149
- ...(typeof agent.snapshot === "boolean" ? { snapshot: agent.snapshot } : {}),
150
- };
151
- }
152
- return manifest;
153
- }
154
- function readPackageManifestForClient(ref) {
155
- return tryReadAgentosPackageManifest(ref.dir) ?? ref.legacyManifest;
156
- }
157
- function isStringRecord(value) {
158
- return (value !== null &&
159
- typeof value === "object" &&
160
- !Array.isArray(value) &&
161
- Object.values(value).every((entry) => typeof entry === "string"));
162
- }
163
127
  function parseAcpJson(value, context) {
164
128
  if (value === null) {
165
129
  return undefined;
@@ -475,7 +439,6 @@ const RUNTIME_BOOTSTRAP_COMMANDS = [
475
439
  "python",
476
440
  "python3",
477
441
  ];
478
- const KERNEL_COMMAND_STUB = "#!/bin/sh\n# kernel command stub\n";
479
442
  const REPO_ROOT = fileURLToPath(new URL("../../..", import.meta.url));
480
443
  const SIDECAR_BINARY = join(REPO_ROOT, "target/debug/agentos-sidecar");
481
444
  const SIDECAR_BUILD_INPUTS = [
@@ -515,7 +478,7 @@ function findBootstrapSeedEntry(config, path) {
515
478
  const meta = KERNEL_POSIX_BOOTSTRAP_DIR_METADATA[path];
516
479
  return meta ? { path, type: "directory", ...meta } : undefined;
517
480
  }
518
- function createKernelBootstrapLower(config, commandNames, extraEntries = []) {
481
+ function createKernelBootstrapLower(config, extraEntries = []) {
519
482
  const includesBundledBaseLayer = !(config?.disableDefaultBaseLayer ?? false);
520
483
  const existingPaths = collectConfiguredLowerPaths(config);
521
484
  const entries = [
@@ -557,22 +520,6 @@ function createKernelBootstrapLower(config, commandNames, extraEntries = []) {
557
520
  encoding: "base64",
558
521
  });
559
522
  }
560
- const uniqueCommands = [...new Set(commandNames)].sort((a, b) => a.localeCompare(b));
561
- for (const command of uniqueCommands) {
562
- const stubPath = `/bin/${command}`;
563
- if (existingPaths.has(stubPath)) {
564
- continue;
565
- }
566
- entries.push({
567
- path: stubPath,
568
- type: "file",
569
- mode: "755",
570
- uid: 0,
571
- gid: 0,
572
- content: KERNEL_COMMAND_STUB,
573
- encoding: "utf8",
574
- });
575
- }
576
523
  for (const entry of sortFilesystemEntries(extraEntries)) {
577
524
  if (existingPaths.has(entry.path)) {
578
525
  continue;
@@ -769,28 +716,10 @@ function collectSidecarMountPlan(options) {
769
716
  }
770
717
  pushMount(mount);
771
718
  }
772
- if (options.shimDir) {
773
- pushMount({
774
- path: "/usr/local/bin",
775
- plugin: createHostDirBackend({
776
- hostPath: options.shimDir,
777
- readOnly: true,
778
- }),
779
- readOnly: true,
780
- });
781
- }
782
719
  hostMounts.sort((left, right) => right.vmPath.length - left.vmPath.length);
783
720
  hostPathMappings.sort((left, right) => right.vmPath.length - left.vmPath.length);
784
721
  return { sidecarMounts, hostMounts, hostPathMappings };
785
722
  }
786
- function materializeToolShimDir(toolKits) {
787
- const shimDir = mkdtempSync(join(tmpdir(), "agentos-host-tools-shims-"));
788
- writeFileSync(join(shimDir, "agentos"), KERNEL_COMMAND_STUB, { mode: 0o755 });
789
- for (const toolKit of toolKits) {
790
- writeFileSync(join(shimDir, `agentos-${toolKit.name}`), KERNEL_COMMAND_STUB, { mode: 0o755 });
791
- }
792
- return shimDir;
793
- }
794
723
  function collectToolkitBootstrapCommands(toolKits) {
795
724
  if (toolKits.length === 0) {
796
725
  return [];
@@ -1465,12 +1394,9 @@ export class AgentOs {
1465
1394
  _closedShellIds = new BoundedSet(CLOSED_SHELL_ID_RETENTION_LIMIT);
1466
1395
  _pendingShellExitPromises = new Set();
1467
1396
  _shellCounter = 0;
1468
- /** Command names linked into `/opt/agentos/bin` at runtime (via the sidecar). */
1469
- _linkedCommands = new Set();
1470
1397
  _acpTerminals = new Map();
1471
1398
  _acpTerminalCounter = 0;
1472
1399
  _softwareRoots;
1473
- _softwareAgentConfigs;
1474
1400
  _cronManager;
1475
1401
  _toolKits = [];
1476
1402
  _toolReference = "";
@@ -1487,11 +1413,10 @@ export class AgentOs {
1487
1413
  _agentStderrHandler;
1488
1414
  _agentExitHandler;
1489
1415
  _limitWarningHandler;
1490
- constructor(kernel, sidecar, softwareRoots, softwareAgentConfigs, hostMounts, env, rootFilesystem, sidecarClient, sidecarSession, sidecarVm, additionalInstructions, agentStderrHandler, agentExitHandler, limitWarningHandler) {
1416
+ constructor(kernel, sidecar, softwareRoots, hostMounts, env, rootFilesystem, sidecarClient, sidecarSession, sidecarVm, additionalInstructions, agentStderrHandler, agentExitHandler, limitWarningHandler) {
1491
1417
  this.#kernel = kernel;
1492
1418
  this.sidecar = sidecar;
1493
1419
  this._softwareRoots = softwareRoots;
1494
- this._softwareAgentConfigs = softwareAgentConfigs;
1495
1420
  this._hostMounts = hostMounts;
1496
1421
  this._env = env;
1497
1422
  this._rootFilesystem = rootFilesystem;
@@ -1527,38 +1452,32 @@ export class AgentOs {
1527
1452
  // VM on first use, so agent closures (and pi's V8 snapshot bundle) only
1528
1453
  // enter VMs that run them. Unbuilt packages throw with build
1529
1454
  // instructions; opt out via defaultSoftware: false.
1530
- const defaultSoftware = options?.defaultSoftware === false ? [] : await resolveDefaultSoftware();
1455
+ const defaultSoftware = options?.defaultSoftware === false ? [] : resolveDefaultSoftware();
1531
1456
  const software = options?.defaultSoftware === false
1532
1457
  ? (options.software ?? [])
1533
1458
  : [...defaultSoftware, ...(options?.software ?? [])];
1534
- // Package dirs are projected by the SIDECAR: the client forwards only
1535
- // `{dir}` over `configureVm` and the sidecar reads package metadata from
1536
- // `<dir>/agentos-package.json`.
1459
+ // Packages are projected by the SIDECAR: the client forwards only the
1460
+ // package `path` over `configureVm` and the sidecar reads metadata from
1461
+ // the packed vbare manifest (chunk1 of the `.aospkg`).
1537
1462
  const flatSoftware = software.flat();
1538
- const packageRefs = flatSoftware.flatMap((entry) => {
1463
+ // Honor the AgentOsOptions.defaultSoftware contract ("entries already present
1464
+ // in `software` are not duplicated"): the default bundle and an explicitly
1465
+ // passed one resolve to the same package paths, so dedup by path. Without
1466
+ // this the sidecar rejects the second projection with a duplicate-command
1467
+ // error (e.g. coreutils' `[`).
1468
+ const seenPackagePaths = new Set();
1469
+ const sidecarPackages = flatSoftware.flatMap((entry) => {
1539
1470
  const ref = normalizePackageRef(entry);
1540
- return ref ? [ref] : [];
1471
+ if (!ref || seenPackagePaths.has(ref.path)) {
1472
+ return [];
1473
+ }
1474
+ seenPackagePaths.add(ref.path);
1475
+ return [{ path: ref.path }];
1541
1476
  });
1542
- const sidecarPackages = packageRefs.map((ref) => ({ dir: ref.dir }));
1543
1477
  // All package software is projected into `/opt/agentos` by the sidecar. The
1544
- // client stages nothing host-side; it only derives the agent configs.
1545
- const agentConfigs = new Map();
1546
- // Register `/opt/agentos` agent packages so `createSession(<name>)`
1547
- // launches via `/opt/agentos/bin/<acpEntrypoint>`. The wire no longer
1548
- // carries agent metadata; read it from the package manifest.
1549
- for (const ref of packageRefs) {
1550
- const manifest = readPackageManifestForClient(ref);
1551
- if (!manifest?.agent)
1552
- continue;
1553
- agentConfigs.set(manifest.name, {
1554
- adapterEntrypoint: `${OPT_AGENTOS_BIN}/${manifest.agent.acpEntrypoint}`,
1555
- launchArgs: manifest.agent.launchArgs,
1556
- defaultEnv: manifest.agent.env,
1557
- });
1558
- }
1559
- // Agent-SDK snapshot bundle (loaded once per sidecar into the V8 startup
1560
- // snapshot, reused across sessions) for any snapshot-enabled agent.
1561
- const snapshotUserlandCode = resolveAgentSnapshotBundle(packageRefs.map((ref) => ({ packageDir: ref.dir })));
1478
+ // client stages nothing host-side and parses NO package manifests: the
1479
+ // sidecar owns agent resolution, agent enumeration, and agent snapshot
1480
+ // bundle loading from the projected package dirs.
1562
1481
  const localMounts = await resolveCompatLocalMounts(options?.mounts);
1563
1482
  const toolKits = options?.toolKits;
1564
1483
  if (toolKits && toolKits.length > 0) {
@@ -1573,81 +1492,32 @@ export class AgentOs {
1573
1492
  // runtime `linkSoftware` appends to that live dir). The client no longer
1574
1493
  // stages packages host-side.
1575
1494
  const toolBootstrapCommands = collectToolkitBootstrapCommands(toolKits ?? []);
1576
- const bootstrapLower = createKernelBootstrapLower(options?.rootFilesystem, [...RUNTIME_BOOTSTRAP_COMMANDS, ...toolBootstrapCommands]);
1495
+ const bootstrapCommands = [
1496
+ ...RUNTIME_BOOTSTRAP_COMMANDS,
1497
+ ...toolBootstrapCommands,
1498
+ ];
1499
+ const bootstrapLower = createKernelBootstrapLower(options?.rootFilesystem);
1577
1500
  let toolReference = "";
1578
1501
  let rootBridge = null;
1579
1502
  let kernel = null;
1580
1503
  let client = null;
1581
1504
  let createdNativeVm = null;
1582
1505
  let nativeSession = null;
1583
- let toolShimDir = null;
1584
1506
  let cleanedUp = false;
1585
1507
  const cleanup = async () => {
1586
1508
  if (cleanedUp) {
1587
1509
  return;
1588
1510
  }
1589
1511
  cleanedUp = true;
1590
- if (toolShimDir) {
1591
- rmSync(toolShimDir, { recursive: true, force: true });
1592
- toolShimDir = null;
1593
- }
1594
1512
  };
1595
1513
  try {
1596
1514
  const env = getBaseEnvironment();
1597
- if (toolKits && toolKits.length > 0) {
1598
- toolShimDir = materializeToolShimDir(toolKits);
1599
- }
1600
- // Guest command paths. The sidecar owns the `/opt/agentos` projection,
1601
- // but the client's command map (rpc-client) still needs to KNOW the
1602
- // projected command names so its shell-exec guard (`this.commands.has("sh")`)
1603
- // and wasmvm routing work. Seed each projected package's commands from its
1604
- // `bin` map (mirroring the sidecar projection's command derivation), mapped
1605
- // to their projected `/opt/agentos/bin/<cmd>` path. Tool-shim commands are
1606
- // added below.
1515
+ // Guest command paths. The sidecar owns the `/opt/agentos` projection and
1516
+ // reports the exact projected package commands after `configureVm`.
1517
+ // Tool-shim commands are added below.
1607
1518
  const commandGuestPaths = new Map();
1608
- const deriveProjectedCommandNames = (dir) => {
1609
- try {
1610
- const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
1611
- if (pkg.bin && typeof pkg.bin === "object") {
1612
- return Object.keys(pkg.bin);
1613
- }
1614
- if (typeof pkg.bin === "string") {
1615
- const base = pkg.name?.split("/").pop();
1616
- if (base)
1617
- return [base];
1618
- }
1619
- }
1620
- catch {
1621
- // no/invalid package.json — fall through to the bin/ scan
1622
- }
1623
- try {
1624
- return readdirSync(join(dir, "bin"));
1625
- }
1626
- catch {
1627
- return [];
1628
- }
1629
- };
1630
- for (const ref of packageRefs) {
1631
- for (const cmd of deriveProjectedCommandNames(ref.dir)) {
1632
- commandGuestPaths.set(cmd, `/opt/agentos/bin/${cmd}`);
1633
- }
1634
- }
1635
- const requestedMounts = options?.moduleAccessCwd
1636
- ? [
1637
- ...(options.mounts ?? []),
1638
- {
1639
- path: "/root/node_modules",
1640
- plugin: createHostDirBackend({
1641
- hostPath: join(options.moduleAccessCwd, "node_modules"),
1642
- readOnly: true,
1643
- }),
1644
- readOnly: true,
1645
- },
1646
- ]
1647
- : options?.mounts;
1648
1519
  const { sidecarMounts, hostMounts, hostPathMappings } = collectSidecarMountPlan({
1649
- mounts: requestedMounts,
1650
- shimDir: toolShimDir,
1520
+ mounts: options?.mounts,
1651
1521
  });
1652
1522
  // Reuse the sidecar handle's single shared native process; this VM
1653
1523
  // becomes another tenant of it rather than spawning its own process.
@@ -1666,6 +1536,7 @@ export class AgentOs {
1666
1536
  permissions: sidecarPermissions,
1667
1537
  limits: options?.limits,
1668
1538
  loopbackExemptPorts: options?.loopbackExemptPorts ?? [],
1539
+ bootstrapCommands,
1669
1540
  // 0.3: the Node builtin allow-list moved from configureVm to
1670
1541
  // VM creation. `undefined` => engine default allow-list;
1671
1542
  // `[]` => deny all; `[..]` => exactly those. Platform and
@@ -1673,8 +1544,7 @@ export class AgentOs {
1673
1544
  // emulation), matching the prior behavior where Agent OS only
1674
1545
  // constrained the builtin allow-list.
1675
1546
  ...(options?.allowedNodeBuiltins !== undefined ||
1676
- options?.highResolutionTime !== undefined ||
1677
- snapshotUserlandCode !== undefined
1547
+ options?.highResolutionTime !== undefined
1678
1548
  ? {
1679
1549
  jsRuntime: {
1680
1550
  platform: "node",
@@ -1685,9 +1555,6 @@ export class AgentOs {
1685
1555
  ...(options?.highResolutionTime !== undefined
1686
1556
  ? { highResolutionTime: options.highResolutionTime }
1687
1557
  : {}),
1688
- ...(snapshotUserlandCode !== undefined
1689
- ? { snapshotUserlandCode }
1690
- : {}),
1691
1558
  },
1692
1559
  }
1693
1560
  : {}),
@@ -1703,14 +1570,18 @@ export class AgentOs {
1703
1570
  event.payload.state === "ready" &&
1704
1571
  event.ownership.scope === "vm" &&
1705
1572
  event.ownership.vm_id === nativeVm.vmId, 10_000);
1706
- await client.configureVm(session, nativeVm, {
1573
+ const configuredVm = await client.configureVm(session, nativeVm, {
1707
1574
  mounts: sidecarMounts,
1708
1575
  permissions: sidecarPermissions,
1709
1576
  commandPermissions: {},
1710
1577
  loopbackExemptPorts: options?.loopbackExemptPorts,
1711
1578
  packages: sidecarPackages,
1712
1579
  packagesMountAt: OPT_AGENTOS_ROOT,
1580
+ toolShimCommands: toolBootstrapCommands,
1713
1581
  });
1582
+ for (const command of configuredVm.projectedCommands) {
1583
+ commandGuestPaths.set(command.name, command.guestPath);
1584
+ }
1714
1585
  if (toolKits && toolKits.length > 0) {
1715
1586
  toolReference = await registerToolkitsOnSidecar(client, session, nativeVm, toolKits);
1716
1587
  commandGuestPaths.set("agentos", "/bin/agentos");
@@ -1796,7 +1667,7 @@ export class AgentOs {
1796
1667
  createVm: async () => createVmAdmin(),
1797
1668
  });
1798
1669
  const vmAdmin = sidecarLease.admin;
1799
- const vm = new AgentOs(vmAdmin.kernel, sidecar, [], agentConfigs, vmAdmin.hostMounts, vmAdmin.env, vmAdmin.rootView, vmAdmin.sidecarClient, vmAdmin.sidecarSession, vmAdmin.sidecarVm, options?.additionalInstructions, options?.onAgentStderr ?? defaultAgentStderrHandler, options?.onAgentExit ?? defaultAgentExitHandler, options?.onLimitWarning);
1670
+ const vm = new AgentOs(vmAdmin.kernel, sidecar, [], vmAdmin.hostMounts, vmAdmin.env, vmAdmin.rootView, vmAdmin.sidecarClient, vmAdmin.sidecarSession, vmAdmin.sidecarVm, options?.additionalInstructions, options?.onAgentStderr ?? defaultAgentStderrHandler, options?.onAgentExit ?? defaultAgentExitHandler, options?.onLimitWarning);
1800
1671
  vm._sidecarLease = sidecarLease;
1801
1672
  vm._toolKits = vmAdmin.toolKits;
1802
1673
  vm._toolReference = vmAdmin.toolReference;
@@ -1933,35 +1804,6 @@ export class AgentOs {
1933
1804
  _vfs() {
1934
1805
  return this.#kernel.vfs;
1935
1806
  }
1936
- async _copyPath(from, to) {
1937
- const stat = await this._vfs().lstat(from);
1938
- if (stat.isSymbolicLink) {
1939
- const target = await this._vfs().readlink(from);
1940
- await this._vfs().symlink(target, to);
1941
- return;
1942
- }
1943
- if (stat.isDirectory) {
1944
- await this._mkdirp(posixPath.dirname(to));
1945
- if (!(await this.#kernel.exists(to))) {
1946
- await this.#kernel.mkdir(to);
1947
- }
1948
- await this._vfs().chmod(to, stat.mode);
1949
- await this._vfs().chown(to, stat.uid, stat.gid);
1950
- const entries = await this.#kernel.readdir(from);
1951
- for (const entry of entries) {
1952
- if (entry === "." || entry === "..")
1953
- continue;
1954
- const fromPath = from === "/" ? `/${entry}` : `${from}/${entry}`;
1955
- const toPath = to === "/" ? `/${entry}` : `${to}/${entry}`;
1956
- await this._copyPath(fromPath, toPath);
1957
- }
1958
- return;
1959
- }
1960
- const content = await this.#kernel.readFile(from);
1961
- await this.writeFile(to, content);
1962
- await this._vfs().chmod(to, stat.mode);
1963
- await this._vfs().chown(to, stat.uid, stat.gid);
1964
- }
1965
1807
  async readFile(path) {
1966
1808
  this._assertSafeAbsolutePath(path);
1967
1809
  return this.#kernel.readFile(path);
@@ -2035,49 +1877,31 @@ export class AgentOs {
2035
1877
  }
2036
1878
  async readdirRecursive(path, options) {
2037
1879
  this._assertSafeAbsolutePath(path);
2038
- const maxDepth = options?.maxDepth;
2039
1880
  const exclude = options?.exclude ? new Set(options.exclude) : undefined;
1881
+ const entries = await this.#kernel.readdirRecursive(path, {
1882
+ maxDepth: options?.maxDepth,
1883
+ });
1884
+ const excludedPrefixes = [];
2040
1885
  const results = [];
2041
- // BFS queue: [dirPath, currentDepth]
2042
- const queue = [[path, 0]];
2043
- while (queue.length > 0) {
2044
- const item = queue.shift();
2045
- if (!item)
2046
- break;
2047
- const [dirPath, depth] = item;
2048
- const entries = await this.#kernel.readdir(dirPath);
2049
- for (const name of entries) {
2050
- if (name === "." || name === "..")
2051
- continue;
2052
- if (exclude?.has(name))
2053
- continue;
2054
- const fullPath = dirPath === "/" ? `/${name}` : `${dirPath}/${name}`;
2055
- const s = await this.#kernel.stat(fullPath);
2056
- if (s.isSymbolicLink) {
2057
- results.push({
2058
- path: fullPath,
2059
- type: "symlink",
2060
- size: s.size,
2061
- });
2062
- }
2063
- else if (s.isDirectory) {
2064
- results.push({
2065
- path: fullPath,
2066
- type: "directory",
2067
- size: s.size,
2068
- });
2069
- if (maxDepth === undefined || depth < maxDepth) {
2070
- queue.push([fullPath, depth + 1]);
2071
- }
2072
- }
2073
- else {
2074
- results.push({
2075
- path: fullPath,
2076
- type: "file",
2077
- size: s.size,
2078
- });
1886
+ for (const entry of entries) {
1887
+ if (excludedPrefixes.some((prefix) => entry.path === prefix || entry.path.startsWith(`${prefix}/`))) {
1888
+ continue;
1889
+ }
1890
+ if (exclude?.has(entry.name)) {
1891
+ if (entry.isDirectory && !entry.isSymbolicLink) {
1892
+ excludedPrefixes.push(entry.path);
2079
1893
  }
1894
+ continue;
2080
1895
  }
1896
+ results.push({
1897
+ path: entry.path,
1898
+ type: entry.isSymbolicLink
1899
+ ? "symlink"
1900
+ : entry.isDirectory
1901
+ ? "directory"
1902
+ : "file",
1903
+ size: entry.size,
1904
+ });
2081
1905
  }
2082
1906
  return results;
2083
1907
  }
@@ -2105,30 +1929,15 @@ export class AgentOs {
2105
1929
  this.#kernel.unmountFs(path);
2106
1930
  }
2107
1931
  async move(from, to) {
2108
- this._assertSafeAbsolutePath(from);
2109
- this._assertSafeAbsolutePath(to);
2110
- const sourceStat = await this._vfs().lstat(from);
2111
- if (!sourceStat.isDirectory || sourceStat.isSymbolicLink) {
2112
- return this.#kernel.rename(from, to);
2113
- }
2114
- await this._copyPath(from, to);
2115
- await this.delete(from, { recursive: true });
1932
+ this._assertWritableAbsolutePath(from);
1933
+ this._assertWritableAbsolutePath(to);
1934
+ await this.#kernel.movePath(from, to);
2116
1935
  }
2117
1936
  async delete(path, options) {
2118
- this._assertSafeAbsolutePath(path);
2119
- const s = await this._vfs().lstat(path);
2120
- if (s.isDirectory) {
2121
- if (options?.recursive) {
2122
- const entries = await this.#kernel.readdir(path);
2123
- for (const entry of entries) {
2124
- if (entry === "." || entry === "..")
2125
- continue;
2126
- await this.delete(`${path}/${entry}`, { recursive: true });
2127
- }
2128
- }
2129
- return this.#kernel.removeDir(path);
2130
- }
2131
- return this.#kernel.removeFile(path);
1937
+ this._assertWritableAbsolutePath(path);
1938
+ await this.#kernel.removePath(path, {
1939
+ recursive: options?.recursive ?? false,
1940
+ });
2132
1941
  }
2133
1942
  async fetch(port, request) {
2134
1943
  const url = new URL(request.url);
@@ -2359,78 +2168,39 @@ export class AgentOs {
2359
2168
  // appends the package to its live host-backed staging dir; the commands
2360
2169
  // appear under `/opt/agentos/bin` immediately. The sidecar rejects a
2361
2170
  // duplicate command, surfaced here as a thrown error.
2362
- const commands = await this._sidecarClient.linkPackage(this._sidecarSession, this._sidecarVm, { dir: ref.dir });
2363
- for (const command of commands) {
2364
- this._linkedCommands.add(command);
2365
- }
2366
- const manifest = readPackageManifestForClient(ref);
2367
- if (manifest?.agent) {
2368
- this._softwareAgentConfigs.set(manifest.name, {
2369
- adapterEntrypoint: `${OPT_AGENTOS_BIN}/${manifest.agent.acpEntrypoint}`,
2370
- launchArgs: manifest.agent.launchArgs,
2371
- defaultEnv: manifest.agent.env,
2372
- });
2171
+ const commands = await this._sidecarClient.linkPackage(this._sidecarSession, this._sidecarVm, { path: ref.path });
2172
+ if (this.#kernel instanceof NativeSidecarKernelProxy) {
2173
+ this.#kernel.registerCommandGuestPaths(new Map(commands.projectedCommands.map((command) => [
2174
+ command.name,
2175
+ command.guestPath,
2176
+ ])));
2373
2177
  }
2178
+ // The client parses no manifests: an `agent` block in the linked package is
2179
+ // picked up by the sidecar (it owns the projected `/opt/agentos` and answers
2180
+ // createSession/listAgents from it). Nothing to record client-side.
2374
2181
  }
2375
- /** Returns all registered agents with their installation status. */
2376
- listAgents() {
2377
- // Collect agent IDs from package configs, the hardcoded configs, and the
2378
- // @agentos-software/* agent dependencies (linked lazily on first
2379
- // createSession — see createSession).
2380
- const dependencyAgents = resolveDependencyAgents();
2381
- const allIds = new Set([
2382
- ...this._softwareAgentConfigs.keys(),
2383
- ...dependencyAgents.keys(),
2384
- ]);
2385
- return [...allIds]
2386
- .map((id) => {
2387
- let config = this._resolveAgentConfig(id);
2388
- if (!config) {
2389
- // Dependency agent not linked yet — report it from its manifest.
2390
- const dependencyAgent = dependencyAgents.get(id);
2391
- if (!dependencyAgent)
2392
- return null;
2393
- config = {
2394
- adapterEntrypoint: `${OPT_AGENTOS_BIN}/${dependencyAgent.acpEntrypoint}`,
2395
- };
2396
- }
2397
- // An `/opt/agentos` agent package is materialized into the VM at
2398
- // boot, so it is always "installed" — its adapter is a real command.
2399
- if (config.adapterEntrypoint || !config.acpAdapter) {
2400
- return {
2401
- id,
2402
- adapterEntrypoint: config.adapterEntrypoint,
2403
- installed: true,
2404
- };
2405
- }
2406
- let installed = false;
2407
- try {
2408
- // Check the software roots that provide this adapter package.
2409
- const vmPrefix = `/root/node_modules/${config.acpAdapter}`;
2410
- let hostPkgJsonPath = null;
2411
- for (const root of this._softwareRoots) {
2412
- if (root.vmPath === vmPrefix) {
2413
- hostPkgJsonPath = join(root.hostPath, "package.json");
2414
- break;
2415
- }
2416
- }
2417
- if (!hostPkgJsonPath) {
2418
- throw new Error("no package source");
2419
- }
2420
- readFileSync(hostPkgJsonPath);
2421
- installed = true;
2422
- }
2423
- catch {
2424
- // Package not installed
2425
- }
2426
- return {
2427
- id,
2428
- acpAdapter: config.acpAdapter,
2429
- agentPackage: config.agentPackage,
2430
- installed,
2431
- };
2432
- })
2433
- .filter((entry) => entry !== null);
2182
+ async providedCommands() {
2183
+ return this._sidecarClient.providedCommands(this._sidecarSession, this._sidecarVm);
2184
+ }
2185
+ /**
2186
+ * Returns all registered agents with their installation status. Thin forwarder:
2187
+ * sends `AcpListAgentsRequest` and maps the response. The sidecar enumerates the
2188
+ * projected `/opt/agentos` packages (the client parses no manifests). Every such
2189
+ * agent is a package materialized into the VM, so `installed` is always `true`.
2190
+ */
2191
+ async listAgents() {
2192
+ const response = await this._sendAcpRequest({
2193
+ tag: "AcpListAgentsRequest",
2194
+ val: { reserved: false },
2195
+ });
2196
+ if (response.tag !== "AcpListAgentsResponse") {
2197
+ throw new Error(`unexpected list_agents response: ${response.tag}`);
2198
+ }
2199
+ return response.val.agents.map((agent) => ({
2200
+ id: agent.id,
2201
+ installed: agent.installed,
2202
+ adapterEntrypoint: agent.adapterEntrypoint,
2203
+ }));
2434
2204
  }
2435
2205
  _syncSessionState(session, state) {
2436
2206
  session.processId = state.processId;
@@ -2913,42 +2683,21 @@ export class AgentOs {
2913
2683
  this._syncSessionState(session, state);
2914
2684
  }
2915
2685
  async createSession(agentType, options) {
2916
- let config = this._resolveAgentConfig(agentType);
2917
- if (!config) {
2918
- // Lazily link an agent dependency on first use: agent packages are not
2919
- // projected by default (each carries a full node closure), so resolve
2920
- // the @agentos-software/* dep whose packed manifest name matches and
2921
- // link it into the running VM now. linkSoftware registers its
2922
- // entrypoint/env from the package's own agentos-package.json.
2923
- const dependencyAgent = resolveDependencyAgents().get(String(agentType));
2924
- if (dependencyAgent) {
2925
- await this.linkSoftware({ packageDir: dependencyAgent.packageDir });
2926
- config = this._resolveAgentConfig(agentType);
2927
- }
2928
- }
2929
- if (!config) {
2930
- throw new Error(`Unknown agent type: ${agentType}`);
2931
- }
2932
- // System-prompt assembly and injection (launch args / OPENCODE_CONTEXTPATHS) are owned by
2933
- // the sidecar at AcpCreateSessionRequest. The host only forwards additionalInstructions /
2934
- // skipOsInstructions plus the agent's static launch args and env.
2935
- const launchArgs = [...(config.launchArgs ?? [])];
2936
- const launchEnv = { ...config.defaultEnv, ...options?.env };
2686
+ // The client is npm-agnostic: it sends only the agent NAME. The sidecar
2687
+ // resolves the name -> package -> entrypoint/env/launchArgs from the
2688
+ // projected `/opt/agentos/<name>/current/agentos-package.json` and spawns
2689
+ // (including the agent's static launch args and manifest env defaults).
2690
+ // System-prompt assembly/injection (launch args / OPENCODE_CONTEXTPATHS) is
2691
+ // owned by the sidecar; the host only forwards additionalInstructions /
2692
+ // skipOsInstructions plus the caller's env.
2693
+ const launchEnv = { ...options?.env };
2937
2694
  const sessionCwd = options?.cwd ?? "/workspace";
2938
- // Every agent is an `/opt/agentos` package now: the config carries a
2939
- // pre-resolved guest command path (`adapterEntrypoint`) that the sidecar
2940
- // spawns directly. There is no npm adapter resolution.
2941
- const adapterEntrypoint = config.adapterEntrypoint;
2942
- if (!adapterEntrypoint) {
2943
- throw new Error(`agent "${String(agentType)}" config has no adapterEntrypoint`);
2944
- }
2945
2695
  const response = await this._sendAcpRequest({
2946
2696
  tag: "AcpCreateSessionRequest",
2947
2697
  val: {
2948
2698
  agentType: String(agentType),
2949
2699
  runtime: AcpRuntimeKind.JavaScript,
2950
- adapterEntrypoint,
2951
- args: launchArgs,
2700
+ args: [],
2952
2701
  env: new Map(Object.entries(launchEnv)),
2953
2702
  cwd: sessionCwd,
2954
2703
  mcpServers: JSON.stringify(options?.mcpServers ?? []),
@@ -3008,26 +2757,11 @@ export class AgentOs {
3008
2757
  * there is no surviving store and the fallback tier always runs.
3009
2758
  */
3010
2759
  async resumeSession(sessionId, agentType, options) {
3011
- const config = this._resolveAgentConfig(agentType);
3012
- if (!config) {
3013
- throw new Error(`Unknown agent type: ${agentType}`);
3014
- }
3015
- // Every agent is an `/opt/agentos` package now: the config carries a
3016
- // pre-resolved guest command path (`adapterEntrypoint`). There is no npm
3017
- // adapter resolution.
3018
- const adapterEntrypoint = config.adapterEntrypoint;
3019
- if (!adapterEntrypoint) {
3020
- throw new Error(`agent "${String(agentType)}" config has no adapterEntrypoint`);
3021
- }
2760
+ // The client is npm-agnostic: it sends only the agent NAME. The sidecar
2761
+ // resolves the name -> package -> entrypoint/env/launchArgs from the
2762
+ // projected manifest, exactly as createSession does.
3022
2763
  const sessionCwd = options?.cwd ?? "/workspace";
3023
- // The resume wire request has no dedicated `adapterEntrypoint` field; carry
3024
- // the resolved entrypoint through env under the sidecar's reserved key. The
3025
- // sidecar reads it and strips it before launching the adapter.
3026
- const launchEnv = {
3027
- ...config.defaultEnv,
3028
- ...options?.env,
3029
- [RESUME_ADAPTER_ENTRYPOINT_ENV]: adapterEntrypoint,
3030
- };
2764
+ const launchEnv = { ...options?.env };
3031
2765
  const response = await this._sendAcpRequest({
3032
2766
  tag: "AcpResumeSessionRequest",
3033
2767
  val: {
@@ -3578,14 +3312,6 @@ export class AgentOs {
3578
3312
  return "reject";
3579
3313
  }
3580
3314
  }
3581
- /**
3582
- * Resolve an agent config by ID. Agents are /opt/agentos packages
3583
- * registered from their manifests (explicit software at create, or lazily
3584
- * linked dependency agents) — there is no hardcoded fallback config.
3585
- */
3586
- _resolveAgentConfig(agentType) {
3587
- return this._softwareAgentConfigs.get(agentType);
3588
- }
3589
3315
  /**
3590
3316
  * Gracefully destroy a session: cancel any pending work, close the client,
3591
3317
  * and remove from tracking. Unlike close() which is abrupt, this attempts
@@ -3902,13 +3628,13 @@ function ensureSharedSidecarNativeProcess(sidecar) {
3902
3628
  // the spawned child is untracked, unreapable, and pins the loop).
3903
3629
  state.sharedChild = sidecarChildHandle(client);
3904
3630
  if (!state.sharedChild) {
3905
- // We reached into @secure-exec/core internals to get the child for
3631
+ // We reached into @rivet-dev/agentos-runtime-core internals to get the child for
3906
3632
  // idle-unref. If that shape ever changes this returns undefined and
3907
3633
  // the optimization silently stops working (one-shot scripts would
3908
3634
  // hang again). Make it loud rather than a silent regression.
3909
3635
  console.warn("[agentos] could not resolve the shared sidecar child handle; " +
3910
3636
  "standalone scripts may not exit cleanly after dispose(). " +
3911
- "This usually means @secure-exec/core internals changed.");
3637
+ "This usually means @rivet-dev/agentos-runtime-core internals changed.");
3912
3638
  }
3913
3639
  // Apply the current hold state to the just-spawned child.
3914
3640
  applySharedSidecarHold(state);