@rivet-dev/agentos-core 0.2.8-rc.2 → 0.2.8-rc.3

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 (55) hide show
  1. package/README.md +40 -37
  2. package/dist/agent-os.d.ts +184 -230
  3. package/dist/agent-os.js +751 -1158
  4. package/dist/agentos-package.d.ts +5 -3
  5. package/dist/agentos-package.js +5 -2
  6. package/dist/{host-tools-zod.d.ts → bindings-zod.d.ts} +1 -1
  7. package/dist/{host-tools-zod.js → bindings-zod.js} +27 -20
  8. package/dist/bindings.d.ts +43 -0
  9. package/dist/bindings.js +34 -0
  10. package/dist/cron/cron-manager.js +43 -10
  11. package/dist/cron/index.d.ts +1 -1
  12. package/dist/cron/types.d.ts +25 -9
  13. package/dist/filesystem-snapshot.d.ts +1 -2
  14. package/dist/filesystem-snapshot.js +0 -43
  15. package/dist/generated/AcpLimitsConfig.d.ts +16 -0
  16. package/dist/generated/BindingLimitsConfig.d.ts +10 -0
  17. package/dist/generated/CreateVmConfig.d.ts +2 -0
  18. package/dist/generated/ProcessLimitsConfig.d.ts +7 -0
  19. package/dist/generated/ProcessLimitsConfig.js +2 -0
  20. package/dist/generated/VmGroupConfig.d.ts +5 -0
  21. package/dist/generated/VmGroupConfig.js +2 -0
  22. package/dist/generated/VmLimitsConfig.d.ts +4 -2
  23. package/dist/generated/VmUserAccountConfig.d.ts +9 -0
  24. package/dist/generated/VmUserAccountConfig.js +2 -0
  25. package/dist/generated/VmUserConfig.d.ts +19 -0
  26. package/dist/generated/WasmLimitsConfig.d.ts +1 -0
  27. package/dist/index.d.ts +4 -5
  28. package/dist/index.js +4 -5
  29. package/dist/layers.d.ts +5 -2
  30. package/dist/layers.js +0 -185
  31. package/dist/options-schema.d.ts +133 -31
  32. package/dist/options-schema.js +143 -26
  33. package/dist/runtime-compat.d.ts +4 -35
  34. package/dist/runtime-compat.js +84 -372
  35. package/dist/runtime.d.ts +3 -1
  36. package/dist/sandbox.d.ts +4 -4
  37. package/dist/sandbox.js +4 -4
  38. package/dist/session-api.d.ts +180 -0
  39. package/dist/session-api.js +1 -0
  40. package/dist/sidecar/agentos-protocol.d.ts +429 -29
  41. package/dist/sidecar/agentos-protocol.js +691 -93
  42. package/dist/sidecar/rpc-client.d.ts +18 -6
  43. package/dist/sidecar/rpc-client.js +66 -18
  44. package/dist/test/runtime.d.ts +2 -1
  45. package/dist/test/runtime.js +2 -1
  46. package/dist/types.d.ts +4 -5
  47. package/package.json +20 -19
  48. package/dist/agent-session-types.d.ts +0 -67
  49. package/dist/generated/ToolLimitsConfig.d.ts +0 -10
  50. package/dist/host-tools.d.ts +0 -45
  51. package/dist/host-tools.js +0 -35
  52. package/dist/overlay-filesystem.d.ts +0 -19
  53. package/dist/overlay-filesystem.js +0 -645
  54. /package/dist/generated/{ToolLimitsConfig.js → BindingLimitsConfig.js} +0 -0
  55. /package/dist/{agent-session-types.js → generated/VmUserConfig.js} +0 -0
@@ -13,10 +13,6 @@ export const AF_UNIX = 1;
13
13
  export const SOCK_STREAM = 1;
14
14
  export const SOCK_DGRAM = 2;
15
15
  export const SIGTERM = 15;
16
- const S_IFREG = 0o100000;
17
- const S_IFDIR = 0o040000;
18
- const S_IFLNK = 0o120000;
19
- const MAX_SYMLINK_DEPTH = 40;
20
16
  const NODE_RUNTIME_BOOTSTRAP_COMMANDS = [
21
17
  "node",
22
18
  "npm",
@@ -71,9 +67,23 @@ const SIDECAR_BUILD_INPUTS = [
71
67
  path.join(REPO_ROOT, "Cargo.toml"),
72
68
  path.join(REPO_ROOT, "Cargo.lock"),
73
69
  path.join(REPO_ROOT, "crates/bridge"),
70
+ path.join(REPO_ROOT, "crates/build-support"),
74
71
  path.join(REPO_ROOT, "crates/execution"),
75
72
  path.join(REPO_ROOT, "crates/kernel"),
76
- path.join(REPO_ROOT, "crates/sidecar"),
73
+ path.join(REPO_ROOT, "crates/agentos-protocol"),
74
+ path.join(REPO_ROOT, "crates/agentos-sidecar"),
75
+ path.join(REPO_ROOT, "crates/native-sidecar"),
76
+ path.join(REPO_ROOT, "crates/native-sidecar-core"),
77
+ path.join(REPO_ROOT, "crates/sidecar-protocol"),
78
+ path.join(REPO_ROOT, "crates/v8-runtime"),
79
+ path.join(REPO_ROOT, "crates/vfs"),
80
+ path.join(REPO_ROOT, "crates/vm-config"),
81
+ path.join(REPO_ROOT, "packages/build-tools/bridge-src"),
82
+ path.join(REPO_ROOT, "packages/build-tools/package.json"),
83
+ path.join(REPO_ROOT, "packages/build-tools/scripts/build-v8-bridge.mjs"),
84
+ path.join(REPO_ROOT, "packages/core/fixtures/base-filesystem.json"),
85
+ path.join(REPO_ROOT, "packages/runtime-core/fixtures/base-filesystem.json"),
86
+ path.join(REPO_ROOT, "pnpm-lock.yaml"),
77
87
  ];
78
88
  let ensuredSidecarBinary = null;
79
89
  export class KernelError extends Error {
@@ -112,338 +122,6 @@ function dirnameVirtual(inputPath) {
112
122
  const parts = normalized.split("/").filter(Boolean);
113
123
  return parts.length <= 1 ? "/" : `/${parts.slice(0, -1).join("/")}`;
114
124
  }
115
- let nextInode = 1;
116
- export class InMemoryFileSystem {
117
- entries = new Map();
118
- constructor() {
119
- this.entries.set("/", this.newDirectory());
120
- }
121
- async readFile(targetPath) {
122
- const entry = this.resolveEntry(targetPath);
123
- if (!entry || entry.type !== "file") {
124
- throw errnoError("ENOENT", `open '${targetPath}'`);
125
- }
126
- entry.atimeMs = Date.now();
127
- return new Uint8Array(entry.data);
128
- }
129
- async readTextFile(targetPath) {
130
- return new TextDecoder().decode(await this.readFile(targetPath));
131
- }
132
- async readDir(targetPath) {
133
- return (await this.readDirWithTypes(targetPath)).map((entry) => entry.name);
134
- }
135
- async readDirWithTypes(targetPath) {
136
- const resolved = this.resolvePath(targetPath);
137
- const entry = this.entries.get(resolved);
138
- if (!entry || entry.type !== "dir") {
139
- throw errnoError("ENOENT", `scandir '${targetPath}'`);
140
- }
141
- const prefix = resolved === "/" ? "/" : `${resolved}/`;
142
- const output = new Map();
143
- for (const [entryPath, candidate] of this.entries) {
144
- if (!entryPath.startsWith(prefix))
145
- continue;
146
- const rest = entryPath.slice(prefix.length);
147
- if (!rest || rest.includes("/"))
148
- continue;
149
- output.set(rest, {
150
- name: rest,
151
- isDirectory: candidate.type === "dir",
152
- isSymbolicLink: candidate.type === "symlink",
153
- });
154
- }
155
- return [...output.values()];
156
- }
157
- async writeFile(targetPath, content) {
158
- const normalized = normalizePath(targetPath);
159
- await this.mkdir(dirnameVirtual(normalized), { recursive: true });
160
- const data = typeof content === "string"
161
- ? new TextEncoder().encode(content)
162
- : new Uint8Array(content);
163
- const existing = this.entries.get(normalized);
164
- if (existing?.type === "file") {
165
- existing.data = data;
166
- existing.mtimeMs = Date.now();
167
- existing.ctimeMs = Date.now();
168
- return;
169
- }
170
- const now = Date.now();
171
- this.entries.set(normalized, {
172
- type: "file",
173
- data,
174
- mode: S_IFREG | 0o644,
175
- uid: 0,
176
- gid: 0,
177
- nlink: 1,
178
- ino: nextInode++,
179
- atimeMs: now,
180
- mtimeMs: now,
181
- ctimeMs: now,
182
- birthtimeMs: now,
183
- });
184
- }
185
- async createDir(targetPath) {
186
- const normalized = normalizePath(targetPath);
187
- if (!this.entries.has(dirnameVirtual(normalized))) {
188
- throw errnoError("ENOENT", `mkdir '${targetPath}'`);
189
- }
190
- if (!this.entries.has(normalized)) {
191
- this.entries.set(normalized, this.newDirectory());
192
- }
193
- }
194
- async mkdir(targetPath, options) {
195
- const normalized = normalizePath(targetPath);
196
- if (options?.recursive === false) {
197
- return this.createDir(normalized);
198
- }
199
- let current = "";
200
- for (const part of normalized.split("/").filter(Boolean)) {
201
- current += `/${part}`;
202
- if (!this.entries.has(current)) {
203
- this.entries.set(current, this.newDirectory());
204
- }
205
- }
206
- }
207
- async exists(targetPath) {
208
- try {
209
- return this.entries.has(this.resolvePath(targetPath));
210
- }
211
- catch {
212
- return false;
213
- }
214
- }
215
- async stat(targetPath) {
216
- const entry = this.resolveEntry(targetPath);
217
- if (!entry)
218
- throw errnoError("ENOENT", `stat '${targetPath}'`);
219
- return this.toStat(entry);
220
- }
221
- async removeFile(targetPath) {
222
- const resolved = normalizePath(targetPath);
223
- const entry = this.entries.get(resolved);
224
- if (!entry || entry.type === "dir") {
225
- throw errnoError("ENOENT", `unlink '${targetPath}'`);
226
- }
227
- this.entries.delete(resolved);
228
- }
229
- async removeDir(targetPath) {
230
- const resolved = this.resolvePath(targetPath);
231
- if (resolved === "/") {
232
- throw errnoError("EPERM", "operation not permitted");
233
- }
234
- const entry = this.entries.get(resolved);
235
- if (!entry || entry.type !== "dir") {
236
- throw errnoError("ENOENT", `rmdir '${targetPath}'`);
237
- }
238
- const prefix = `${resolved}/`;
239
- for (const key of this.entries.keys()) {
240
- if (key.startsWith(prefix)) {
241
- throw errnoError("ENOTEMPTY", `directory not empty '${targetPath}'`);
242
- }
243
- }
244
- this.entries.delete(resolved);
245
- }
246
- async rename(oldPath, newPath) {
247
- const oldResolved = this.resolvePath(oldPath);
248
- const newResolved = normalizePath(newPath);
249
- const entry = this.entries.get(oldResolved);
250
- if (!entry)
251
- throw errnoError("ENOENT", `rename '${oldPath}'`);
252
- if (!this.entries.has(dirnameVirtual(newResolved))) {
253
- throw errnoError("ENOENT", `rename '${newPath}'`);
254
- }
255
- if (entry.type !== "dir") {
256
- this.entries.set(newResolved, entry);
257
- this.entries.delete(oldResolved);
258
- return;
259
- }
260
- const prefix = `${oldResolved}/`;
261
- const moved = [];
262
- for (const candidate of this.entries) {
263
- if (candidate[0] === oldResolved || candidate[0].startsWith(prefix)) {
264
- moved.push(candidate);
265
- }
266
- }
267
- for (const [candidatePath] of moved) {
268
- this.entries.delete(candidatePath);
269
- }
270
- for (const [candidatePath, candidate] of moved) {
271
- const nextPath = candidatePath === oldResolved
272
- ? newResolved
273
- : `${newResolved}${candidatePath.slice(oldResolved.length)}`;
274
- this.entries.set(nextPath, candidate);
275
- }
276
- }
277
- async realpath(targetPath) {
278
- return this.resolvePath(targetPath);
279
- }
280
- async symlink(target, linkPath) {
281
- const normalized = normalizePath(linkPath);
282
- if (this.entries.has(normalized)) {
283
- throw errnoError("EEXIST", `symlink '${linkPath}'`);
284
- }
285
- const now = Date.now();
286
- this.entries.set(normalized, {
287
- type: "symlink",
288
- target,
289
- mode: S_IFLNK | 0o777,
290
- uid: 0,
291
- gid: 0,
292
- nlink: 1,
293
- ino: nextInode++,
294
- atimeMs: now,
295
- mtimeMs: now,
296
- ctimeMs: now,
297
- birthtimeMs: now,
298
- });
299
- }
300
- async readlink(targetPath) {
301
- const normalized = normalizePath(targetPath);
302
- const entry = this.entries.get(normalized);
303
- if (!entry || entry.type !== "symlink") {
304
- throw errnoError("ENOENT", `readlink '${targetPath}'`);
305
- }
306
- return entry.target;
307
- }
308
- async lstat(targetPath) {
309
- const entry = this.entries.get(normalizePath(targetPath));
310
- if (!entry)
311
- throw errnoError("ENOENT", `lstat '${targetPath}'`);
312
- return this.toStat(entry);
313
- }
314
- async link(oldPath, newPath) {
315
- const entry = this.resolveEntry(oldPath);
316
- if (!entry || entry.type !== "file") {
317
- throw errnoError("ENOENT", `link '${oldPath}'`);
318
- }
319
- const normalized = normalizePath(newPath);
320
- if (this.entries.has(normalized)) {
321
- throw errnoError("EEXIST", `link '${newPath}'`);
322
- }
323
- entry.nlink += 1;
324
- this.entries.set(normalized, entry);
325
- }
326
- async chmod(targetPath, mode) {
327
- const entry = this.resolveEntry(targetPath);
328
- if (!entry)
329
- throw errnoError("ENOENT", `chmod '${targetPath}'`);
330
- const typeBits = mode & 0o170000;
331
- entry.mode =
332
- typeBits === 0 ? (entry.mode & 0o170000) | (mode & 0o7777) : mode;
333
- entry.ctimeMs = Date.now();
334
- }
335
- async chown(targetPath, uid, gid) {
336
- const entry = this.resolveEntry(targetPath);
337
- if (!entry)
338
- throw errnoError("ENOENT", `chown '${targetPath}'`);
339
- entry.uid = uid;
340
- entry.gid = gid;
341
- entry.ctimeMs = Date.now();
342
- }
343
- async utimes(targetPath, atime, mtime) {
344
- const entry = this.resolveEntry(targetPath);
345
- if (!entry)
346
- throw errnoError("ENOENT", `utimes '${targetPath}'`);
347
- entry.atimeMs = atime;
348
- entry.mtimeMs = mtime;
349
- entry.ctimeMs = Date.now();
350
- }
351
- async truncate(targetPath, length) {
352
- const entry = this.resolveEntry(targetPath);
353
- if (!entry || entry.type !== "file") {
354
- throw errnoError("ENOENT", `truncate '${targetPath}'`);
355
- }
356
- if (length < entry.data.length) {
357
- entry.data = entry.data.slice(0, length);
358
- }
359
- else if (length > entry.data.length) {
360
- const expanded = new Uint8Array(length);
361
- expanded.set(entry.data);
362
- entry.data = expanded;
363
- }
364
- entry.mtimeMs = Date.now();
365
- entry.ctimeMs = Date.now();
366
- }
367
- async pread(targetPath, offset, length) {
368
- const entry = this.resolveEntry(targetPath);
369
- if (!entry || entry.type !== "file") {
370
- throw errnoError("ENOENT", `open '${targetPath}'`);
371
- }
372
- if (offset >= entry.data.length)
373
- return new Uint8Array(0);
374
- return entry.data.slice(offset, Math.min(offset + length, entry.data.length));
375
- }
376
- async pwrite(targetPath, offset, data) {
377
- const entry = this.resolveEntry(targetPath);
378
- if (!entry || entry.type !== "file") {
379
- throw errnoError("ENOENT", `open '${targetPath}'`);
380
- }
381
- const nextSize = Math.max(entry.data.length, offset + data.length);
382
- const updated = new Uint8Array(nextSize);
383
- updated.set(entry.data);
384
- updated.set(new Uint8Array(data), offset);
385
- entry.data = updated;
386
- entry.mtimeMs = Date.now();
387
- entry.ctimeMs = Date.now();
388
- }
389
- resolvePath(targetPath, depth = 0) {
390
- if (depth > MAX_SYMLINK_DEPTH) {
391
- throw errnoError("ELOOP", `too many symbolic links '${targetPath}'`);
392
- }
393
- const normalized = normalizePath(targetPath);
394
- const entry = this.entries.get(normalized);
395
- if (!entry)
396
- return normalized;
397
- if (entry.type === "symlink") {
398
- const target = entry.target.startsWith("/")
399
- ? entry.target
400
- : `${dirnameVirtual(normalized)}/${entry.target}`;
401
- return this.resolvePath(target, depth + 1);
402
- }
403
- return normalized;
404
- }
405
- resolveEntry(targetPath) {
406
- return this.entries.get(this.resolvePath(targetPath));
407
- }
408
- newDirectory() {
409
- const now = Date.now();
410
- return {
411
- type: "dir",
412
- mode: S_IFDIR | 0o755,
413
- uid: 0,
414
- gid: 0,
415
- nlink: 2,
416
- ino: nextInode++,
417
- atimeMs: now,
418
- mtimeMs: now,
419
- ctimeMs: now,
420
- birthtimeMs: now,
421
- };
422
- }
423
- toStat(entry) {
424
- const size = entry.type === "file" ? entry.data.length : 4096;
425
- return {
426
- mode: entry.mode,
427
- size,
428
- blocks: size === 0 ? 0 : Math.ceil(size / 512),
429
- dev: 1,
430
- rdev: 0,
431
- isDirectory: entry.type === "dir",
432
- isSymbolicLink: entry.type === "symlink",
433
- atimeMs: entry.atimeMs,
434
- mtimeMs: entry.mtimeMs,
435
- ctimeMs: entry.ctimeMs,
436
- birthtimeMs: entry.birthtimeMs,
437
- ino: entry.ino,
438
- nlink: entry.nlink,
439
- uid: entry.uid,
440
- gid: entry.gid,
441
- };
442
- }
443
- }
444
- export function createInMemoryFileSystem() {
445
- return new InMemoryFileSystem();
446
- }
447
125
  export class NodeFileSystem {
448
126
  rootPath;
449
127
  constructor(options) {
@@ -824,7 +502,6 @@ export const WASMVM_COMMANDS = Object.freeze([
824
502
  "unzip",
825
503
  "sqlite3",
826
504
  "curl",
827
- "wget",
828
505
  "git",
829
506
  "git-remote-http",
830
507
  "git-remote-https",
@@ -988,7 +665,6 @@ export const DEFAULT_FIRST_PARTY_TIERS = Object.freeze({
988
665
  tac: "read-only",
989
666
  tsort: "read-only",
990
667
  curl: "full",
991
- wget: "full",
992
668
  sqlite3: "read-write",
993
669
  });
994
670
  class NativeRuntimeDescriptor {
@@ -1194,9 +870,13 @@ function createBootstrapEntries() {
1194
870
  ...KERNEL_POSIX_BOOTSTRAP_DIRS.map((entryPath) => ({
1195
871
  path: entryPath,
1196
872
  kind: "directory",
1197
- mode: 0o755,
1198
- uid: 0,
1199
- gid: 0,
873
+ mode: entryPath === "/tmp" || entryPath === "/var/tmp"
874
+ ? 0o1777
875
+ : entryPath === "/home/agentos"
876
+ ? 0o2755
877
+ : 0o755,
878
+ uid: entryPath === "/home/agentos" || entryPath === "/workspace" ? 1000 : 0,
879
+ gid: entryPath === "/home/agentos" || entryPath === "/workspace" ? 1000 : 0,
1200
880
  executable: false,
1201
881
  })),
1202
882
  {
@@ -1457,7 +1137,10 @@ const VIRTUAL_FILESYSTEM_METHOD_NAMES = [
1457
1137
  "pread",
1458
1138
  "pwrite",
1459
1139
  ];
1460
- const LIVE_FILESYSTEM_SYNC_CHUNK_SIZE = 512 * 1024;
1140
+ const DEFAULT_LIVE_FILESYSTEM_SYNC_MAX_BYTES = 64 * 1024 * 1024;
1141
+ class LiveFilesystemSyncLimitError extends Error {
1142
+ code = "ERR_AGENTOS_LIVE_FILESYSTEM_SYNC_LIMIT";
1143
+ }
1461
1144
  function topLevelSyncRoot(targetPath) {
1462
1145
  const normalized = normalizePath(targetPath);
1463
1146
  const [first] = normalized.split("/").filter(Boolean);
@@ -1489,50 +1172,79 @@ async function ensureBoundParentDirectory(methods, targetPath) {
1489
1172
  recursive: true,
1490
1173
  });
1491
1174
  }
1492
- async function syncLiveFilesystemToBoundMethods(live, methods, paths) {
1175
+ async function syncLiveFilesystemToBoundMethods(live, methods, paths, maxBytes) {
1176
+ const snapshot = [];
1177
+ const usage = { bytes: 0 };
1493
1178
  for (const targetPath of [...new Set(paths.map(normalizePath))].sort((left, right) => left.localeCompare(right))) {
1494
1179
  if (!(await live.exists(targetPath).catch(() => false))) {
1495
1180
  continue;
1496
1181
  }
1497
- await syncLiveFilesystemPathToBoundMethods(live, methods, targetPath);
1182
+ await snapshotLiveFilesystemPath(live, targetPath, snapshot, usage, maxBytes);
1183
+ }
1184
+ if (usage.bytes >= maxBytes * 0.8) {
1185
+ process.emitWarning(`live filesystem sync retained ${usage.bytes} of ${maxBytes} bytes; ` +
1186
+ "raise createKernel({ maxFilesystemBytes }) before increasing VM filesystem limits", { code: "AGENTOS_LIVE_FILESYSTEM_SYNC_NEAR_LIMIT" });
1187
+ }
1188
+ for (const entry of snapshot) {
1189
+ await applyLiveFilesystemSnapshotEntry(methods, entry);
1498
1190
  }
1499
1191
  }
1500
- async function syncLiveFilesystemPathToBoundMethods(live, methods, targetPath) {
1501
- const stat = targetPath === "/"
1502
- ? await live.stat(targetPath)
1503
- : await live.lstat(targetPath);
1192
+ async function snapshotLiveFilesystemPath(live, targetPath, snapshot, usage, maxBytes, knownEntry) {
1193
+ const stat = knownEntry ??
1194
+ (targetPath === "/"
1195
+ ? await live.stat(targetPath)
1196
+ : await live.lstat(targetPath));
1504
1197
  if (stat.isSymbolicLink) {
1505
- await ensureBoundParentDirectory(methods, targetPath);
1506
- await callBoundFilesystemMethod(methods, "removeFile", targetPath).catch(() => { });
1507
- await callBoundFilesystemMethod(methods, "symlink", await live.readlink(targetPath), targetPath);
1198
+ snapshot.push({
1199
+ kind: "symlink",
1200
+ path: targetPath,
1201
+ target: await live.readlink(targetPath),
1202
+ });
1508
1203
  return;
1509
1204
  }
1510
1205
  if (stat.isDirectory) {
1511
- await callBoundFilesystemMethod(methods, "mkdir", targetPath, {
1512
- recursive: true,
1513
- });
1206
+ snapshot.push({ kind: "directory", path: targetPath });
1514
1207
  const children = (await live.readDirWithTypes(targetPath))
1515
- .map((entry) => entry.name)
1516
- .filter((name) => name !== "." && name !== "..")
1517
- .sort((left, right) => left.localeCompare(right));
1208
+ .filter((entry) => entry.name !== "." && entry.name !== "..")
1209
+ .sort((left, right) => left.name.localeCompare(right.name));
1518
1210
  for (const child of children) {
1519
- await syncLiveFilesystemPathToBoundMethods(live, methods, targetPath === "/"
1520
- ? posixPath.join("/", child)
1521
- : posixPath.join(targetPath, child));
1211
+ await snapshotLiveFilesystemPath(live, targetPath === "/"
1212
+ ? posixPath.join("/", child.name)
1213
+ : posixPath.join(targetPath, child.name), snapshot, usage, maxBytes, child);
1522
1214
  }
1523
1215
  return;
1524
1216
  }
1525
- await ensureBoundParentDirectory(methods, targetPath);
1526
- await callBoundFilesystemMethod(methods, "writeFile", targetPath, new Uint8Array(0));
1527
- for (let offset = 0; offset < stat.size; offset += LIVE_FILESYSTEM_SYNC_CHUNK_SIZE) {
1528
- const chunk = await live.pread(targetPath, offset, Math.min(LIVE_FILESYSTEM_SYNC_CHUNK_SIZE, stat.size - offset));
1529
- if (chunk.length === 0) {
1530
- break;
1531
- }
1532
- await callBoundFilesystemMethod(methods, "pwrite", targetPath, offset, chunk);
1217
+ const fileSize = "size" in stat && typeof stat.size === "number"
1218
+ ? stat.size
1219
+ : (await live.lstat(targetPath)).size;
1220
+ if (fileSize > maxBytes - usage.bytes) {
1221
+ throw new LiveFilesystemSyncLimitError(`live filesystem sync for ${targetPath} exceeds ${maxBytes} bytes ` +
1222
+ "(createKernel maxFilesystemBytes); raise createKernel({ maxFilesystemBytes })");
1223
+ }
1224
+ const content = await live.readFile(targetPath);
1225
+ if (content.byteLength > maxBytes - usage.bytes) {
1226
+ throw new LiveFilesystemSyncLimitError(`live filesystem sync for ${targetPath} exceeds ${maxBytes} bytes ` +
1227
+ "(createKernel maxFilesystemBytes); raise createKernel({ maxFilesystemBytes })");
1228
+ }
1229
+ usage.bytes += content.byteLength;
1230
+ snapshot.push({ kind: "file", path: targetPath, content });
1231
+ }
1232
+ async function applyLiveFilesystemSnapshotEntry(methods, entry) {
1233
+ if (entry.kind === "directory") {
1234
+ await callBoundFilesystemMethod(methods, "mkdir", entry.path, {
1235
+ recursive: true,
1236
+ });
1237
+ return;
1238
+ }
1239
+ await ensureBoundParentDirectory(methods, entry.path);
1240
+ if (entry.kind === "symlink") {
1241
+ await callBoundFilesystemMethod(methods, "removeFile", entry.path).catch(() => { });
1242
+ await callBoundFilesystemMethod(methods, "symlink", entry.target, entry.path);
1243
+ return;
1533
1244
  }
1245
+ await callBoundFilesystemMethod(methods, "writeFile", entry.path, entry.content);
1534
1246
  }
1535
- function bindLiveFilesystem(target, getFilesystem) {
1247
+ function bindLiveFilesystem(target, getFilesystem, maxBytes) {
1536
1248
  const fallback = {};
1537
1249
  for (const method of VIRTUAL_FILESYSTEM_METHOD_NAMES) {
1538
1250
  const candidate = target[method];
@@ -1558,7 +1270,7 @@ function bindLiveFilesystem(target, getFilesystem) {
1558
1270
  if (!filesystem) {
1559
1271
  return;
1560
1272
  }
1561
- await syncLiveFilesystemToBoundMethods(filesystem, fallback, paths);
1273
+ await syncLiveFilesystemToBoundMethods(filesystem, fallback, paths, maxBytes);
1562
1274
  },
1563
1275
  restore() {
1564
1276
  for (const [method, delegate] of Object.entries(fallback)) {
@@ -1641,7 +1353,7 @@ class NativeKernel {
1641
1353
  }));
1642
1354
  }
1643
1355
  this.vfs = new DeferredFileSystem(() => this.rootFilesystem);
1644
- this.liveFilesystemBinding = bindLiveFilesystem(this.options.filesystem, () => this.rootFilesystem);
1356
+ this.liveFilesystemBinding = bindLiveFilesystem(this.options.filesystem, () => this.rootFilesystem, options.maxFilesystemBytes ?? DEFAULT_LIVE_FILESYSTEM_SYNC_MAX_BYTES);
1645
1357
  }
1646
1358
  get zombieTimerCount() {
1647
1359
  return this.proxy?.zombieTimerCount ?? 0;
package/dist/runtime.d.ts CHANGED
@@ -48,7 +48,9 @@ export interface VirtualFileSystem {
48
48
  lstat(path: string): Promise<VirtualStat>;
49
49
  link(oldPath: string, newPath: string): Promise<void>;
50
50
  chmod(path: string, mode: number): Promise<void>;
51
- chown(path: string, uid: number, gid: number): Promise<void>;
51
+ chown(path: string, uid: number, gid: number, options?: {
52
+ followSymlinks?: boolean;
53
+ }): Promise<void>;
52
54
  utimes(path: string, atime: number, mtime: number): Promise<void>;
53
55
  truncate(path: string, length: number): Promise<void>;
54
56
  pread(path: string, offset: number, length: number): Promise<Uint8Array>;
package/dist/sandbox.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { MountConfig, MountConfigJsonObject, NativeMountPluginDescriptor } from "./agent-os.js";
2
- import type { ToolKit } from "./host-tools.js";
2
+ import type { Bindings } from "./bindings.js";
3
3
  export interface AgentOsSandboxProcessResult {
4
4
  stdout?: string;
5
5
  stderr?: string;
@@ -86,7 +86,7 @@ declare const sandboxDisposeHooks: unique symbol;
86
86
  type SandboxDisposeHook = () => void | Promise<void>;
87
87
  export type AgentOsSandboxExpandedOptions = {
88
88
  mounts?: MountConfig[];
89
- toolKits?: ToolKit[];
89
+ bindings?: Bindings[];
90
90
  [sandboxDisposeHooks]?: SandboxDisposeHook[];
91
91
  };
92
92
  type ResolvedSandboxOptions = AgentOsSandboxCommonOptions & {
@@ -101,12 +101,12 @@ export type SandboxMountPluginConfig = MountConfigJsonObject & {
101
101
  maxFullReadBytes?: number;
102
102
  };
103
103
  export declare function createSandboxFs(input: ResolvedSandboxOptions | AgentOsSandboxClientOptions): NativeMountPluginDescriptor<SandboxMountPluginConfig>;
104
- export declare function createSandboxBindings(input: ResolvedSandboxOptions | AgentOsSandboxClientOptions): ToolKit;
104
+ export declare function createSandboxBindings(input: ResolvedSandboxOptions | AgentOsSandboxClientOptions): Bindings;
105
105
  export declare function getSandboxDisposeHooks(options: object | undefined): SandboxDisposeHook[];
106
106
  export declare function resolveSandboxOptions<T extends {
107
107
  sandbox?: AgentOsSandboxInput;
108
108
  }>(options: T): Promise<Omit<T, "sandbox"> & {
109
109
  mounts?: MountConfig[];
110
- toolKits?: ToolKit[];
110
+ bindings?: Bindings[];
111
111
  }>;
112
112
  export {};
package/dist/sandbox.js CHANGED
@@ -49,7 +49,7 @@ export function createSandboxBindings(input) {
49
49
  return {
50
50
  name: "sandbox",
51
51
  description: "Execute commands and manage processes in a remote sandbox environment.",
52
- tools: {
52
+ bindings: {
53
53
  "run-command": binding({
54
54
  description: "Run a command synchronously in the sandbox and return its stdout, stderr, and exit code.",
55
55
  inputSchema: z.object({
@@ -252,13 +252,13 @@ export async function resolveSandboxOptions(options) {
252
252
  readOnly: sandboxOptions.readOnly,
253
253
  },
254
254
  ];
255
- const toolKits = [
256
- ...(expanded.toolKits ?? []),
255
+ const bindings = [
256
+ ...(expanded.bindings ?? []),
257
257
  createSandboxBindings(sandboxOptions),
258
258
  ];
259
259
  return attachSandboxDisposeHooks({
260
260
  ...expanded,
261
261
  mounts,
262
- toolKits,
262
+ bindings,
263
263
  }, normalizedSandbox.dispose ? [normalizedSandbox.dispose] : []);
264
264
  }