@rivet-dev/agentos-core 0.0.0-codex-actor-plugin-contract-coverage.c7d8f87 → 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 (58) hide show
  1. package/README.md +39 -36
  2. package/dist/agent-os.d.ts +197 -232
  3. package/dist/agent-os.js +769 -1162
  4. package/dist/agentos-package.d.ts +4 -2
  5. package/dist/agentos-package.js +4 -1
  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 +134 -31
  32. package/dist/options-schema.js +144 -26
  33. package/dist/runtime-compat.d.ts +11 -37
  34. package/dist/runtime-compat.js +74 -374
  35. package/dist/runtime.d.ts +10 -3
  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 +420 -29
  41. package/dist/sidecar/agentos-protocol.js +683 -93
  42. package/dist/sidecar/native-process-client.d.ts +1 -1
  43. package/dist/sidecar/native-process-client.js +1 -1
  44. package/dist/sidecar/rpc-client.d.ts +39 -6
  45. package/dist/sidecar/rpc-client.js +111 -30
  46. package/dist/test/runtime.d.ts +2 -1
  47. package/dist/test/runtime.js +2 -1
  48. package/dist/test/terminal-harness.js +0 -3
  49. package/dist/types.d.ts +4 -5
  50. package/package.json +19 -17
  51. package/dist/agent-session-types.d.ts +0 -67
  52. package/dist/generated/ToolLimitsConfig.d.ts +0 -10
  53. package/dist/host-tools.d.ts +0 -45
  54. package/dist/host-tools.js +0 -35
  55. package/dist/overlay-filesystem.d.ts +0 -19
  56. package/dist/overlay-filesystem.js +0 -645
  57. /package/dist/generated/{ToolLimitsConfig.js → BindingLimitsConfig.js} +0 -0
  58. /package/dist/{agent-session-types.js → generated/VmUserConfig.js} +0 -0
@@ -7,16 +7,12 @@ import { fileURLToPath } from "node:url";
7
7
  import { resolvePublishedSidecarBinary } from "./sidecar/binary.js";
8
8
  import { findCargoBinary, resolveCargoBinary } from "./sidecar/cargo.js";
9
9
  import { serializePermissionsForSidecar } from "./sidecar/permissions.js";
10
- import { NATIVE_SIDECAR_FRAME_TIMEOUT_MS, NativeSidecarKernelProxy, SidecarProcess, serializeMountConfigForSidecar, } from "./sidecar/rpc-client.js";
10
+ import { NativeSidecarKernelProxy, SidecarProcess, serializeMountConfigForSidecar, } from "./sidecar/rpc-client.js";
11
11
  export const AF_INET = 2;
12
12
  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,7 +870,7 @@ function createBootstrapEntries() {
1194
870
  ...KERNEL_POSIX_BOOTSTRAP_DIRS.map((entryPath) => ({
1195
871
  path: entryPath,
1196
872
  kind: "directory",
1197
- mode: 0o755,
873
+ mode: entryPath === "/tmp" || entryPath === "/var/tmp" ? 0o1777 : 0o755,
1198
874
  uid: 0,
1199
875
  gid: 0,
1200
876
  executable: false,
@@ -1457,7 +1133,10 @@ const VIRTUAL_FILESYSTEM_METHOD_NAMES = [
1457
1133
  "pread",
1458
1134
  "pwrite",
1459
1135
  ];
1460
- const LIVE_FILESYSTEM_SYNC_CHUNK_SIZE = 512 * 1024;
1136
+ const DEFAULT_LIVE_FILESYSTEM_SYNC_MAX_BYTES = 64 * 1024 * 1024;
1137
+ class LiveFilesystemSyncLimitError extends Error {
1138
+ code = "ERR_AGENTOS_LIVE_FILESYSTEM_SYNC_LIMIT";
1139
+ }
1461
1140
  function topLevelSyncRoot(targetPath) {
1462
1141
  const normalized = normalizePath(targetPath);
1463
1142
  const [first] = normalized.split("/").filter(Boolean);
@@ -1489,50 +1168,71 @@ async function ensureBoundParentDirectory(methods, targetPath) {
1489
1168
  recursive: true,
1490
1169
  });
1491
1170
  }
1492
- async function syncLiveFilesystemToBoundMethods(live, methods, paths) {
1171
+ async function syncLiveFilesystemToBoundMethods(live, methods, paths, maxBytes) {
1172
+ const snapshot = [];
1173
+ const usage = { bytes: 0 };
1493
1174
  for (const targetPath of [...new Set(paths.map(normalizePath))].sort((left, right) => left.localeCompare(right))) {
1494
1175
  if (!(await live.exists(targetPath).catch(() => false))) {
1495
1176
  continue;
1496
1177
  }
1497
- await syncLiveFilesystemPathToBoundMethods(live, methods, targetPath);
1178
+ await snapshotLiveFilesystemPath(live, targetPath, snapshot, usage, maxBytes);
1179
+ }
1180
+ if (usage.bytes >= maxBytes * 0.8) {
1181
+ process.emitWarning(`live filesystem sync retained ${usage.bytes} of ${maxBytes} bytes; ` +
1182
+ "raise createKernel({ maxFilesystemBytes }) before increasing VM filesystem limits", { code: "AGENTOS_LIVE_FILESYSTEM_SYNC_NEAR_LIMIT" });
1183
+ }
1184
+ for (const entry of snapshot) {
1185
+ await applyLiveFilesystemSnapshotEntry(methods, entry);
1498
1186
  }
1499
1187
  }
1500
- async function syncLiveFilesystemPathToBoundMethods(live, methods, targetPath) {
1501
- const stat = targetPath === "/"
1502
- ? await live.stat(targetPath)
1503
- : await live.lstat(targetPath);
1188
+ async function snapshotLiveFilesystemPath(live, targetPath, snapshot, usage, maxBytes, knownEntry) {
1189
+ const stat = knownEntry ??
1190
+ (targetPath === "/" ? await live.stat(targetPath) : await live.lstat(targetPath));
1504
1191
  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);
1192
+ snapshot.push({ kind: "symlink", path: targetPath, target: await live.readlink(targetPath) });
1508
1193
  return;
1509
1194
  }
1510
1195
  if (stat.isDirectory) {
1511
- await callBoundFilesystemMethod(methods, "mkdir", targetPath, {
1512
- recursive: true,
1513
- });
1196
+ snapshot.push({ kind: "directory", path: targetPath });
1514
1197
  const children = (await live.readDirWithTypes(targetPath))
1515
- .map((entry) => entry.name)
1516
- .filter((name) => name !== "." && name !== "..")
1517
- .sort((left, right) => left.localeCompare(right));
1198
+ .filter((entry) => entry.name !== "." && entry.name !== "..")
1199
+ .sort((left, right) => left.name.localeCompare(right.name));
1518
1200
  for (const child of children) {
1519
- await syncLiveFilesystemPathToBoundMethods(live, methods, targetPath === "/"
1520
- ? posixPath.join("/", child)
1521
- : posixPath.join(targetPath, child));
1201
+ await snapshotLiveFilesystemPath(live, targetPath === "/"
1202
+ ? posixPath.join("/", child.name)
1203
+ : posixPath.join(targetPath, child.name), snapshot, usage, maxBytes, child);
1522
1204
  }
1523
1205
  return;
1524
1206
  }
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);
1207
+ const fileSize = "size" in stat && typeof stat.size === "number"
1208
+ ? stat.size
1209
+ : (await live.lstat(targetPath)).size;
1210
+ if (fileSize > maxBytes - usage.bytes) {
1211
+ throw new LiveFilesystemSyncLimitError(`live filesystem sync for ${targetPath} exceeds ${maxBytes} bytes ` +
1212
+ "(createKernel maxFilesystemBytes); raise createKernel({ maxFilesystemBytes })");
1213
+ }
1214
+ const content = await live.readFile(targetPath);
1215
+ if (content.byteLength > maxBytes - usage.bytes) {
1216
+ throw new LiveFilesystemSyncLimitError(`live filesystem sync for ${targetPath} exceeds ${maxBytes} bytes ` +
1217
+ "(createKernel maxFilesystemBytes); raise createKernel({ maxFilesystemBytes })");
1218
+ }
1219
+ usage.bytes += content.byteLength;
1220
+ snapshot.push({ kind: "file", path: targetPath, content });
1221
+ }
1222
+ async function applyLiveFilesystemSnapshotEntry(methods, entry) {
1223
+ if (entry.kind === "directory") {
1224
+ await callBoundFilesystemMethod(methods, "mkdir", entry.path, { recursive: true });
1225
+ return;
1226
+ }
1227
+ await ensureBoundParentDirectory(methods, entry.path);
1228
+ if (entry.kind === "symlink") {
1229
+ await callBoundFilesystemMethod(methods, "removeFile", entry.path).catch(() => { });
1230
+ await callBoundFilesystemMethod(methods, "symlink", entry.target, entry.path);
1231
+ return;
1533
1232
  }
1233
+ await callBoundFilesystemMethod(methods, "writeFile", entry.path, entry.content);
1534
1234
  }
1535
- function bindLiveFilesystem(target, getFilesystem) {
1235
+ function bindLiveFilesystem(target, getFilesystem, maxBytes) {
1536
1236
  const fallback = {};
1537
1237
  for (const method of VIRTUAL_FILESYSTEM_METHOD_NAMES) {
1538
1238
  const candidate = target[method];
@@ -1558,7 +1258,7 @@ function bindLiveFilesystem(target, getFilesystem) {
1558
1258
  if (!filesystem) {
1559
1259
  return;
1560
1260
  }
1561
- await syncLiveFilesystemToBoundMethods(filesystem, fallback, paths);
1261
+ await syncLiveFilesystemToBoundMethods(filesystem, fallback, paths, maxBytes);
1562
1262
  },
1563
1263
  restore() {
1564
1264
  for (const [method, delegate] of Object.entries(fallback)) {
@@ -1641,7 +1341,7 @@ class NativeKernel {
1641
1341
  }));
1642
1342
  }
1643
1343
  this.vfs = new DeferredFileSystem(() => this.rootFilesystem);
1644
- this.liveFilesystemBinding = bindLiveFilesystem(this.options.filesystem, () => this.rootFilesystem);
1344
+ this.liveFilesystemBinding = bindLiveFilesystem(this.options.filesystem, () => this.rootFilesystem, options.maxFilesystemBytes ?? DEFAULT_LIVE_FILESYSTEM_SYNC_MAX_BYTES);
1645
1345
  }
1646
1346
  get zombieTimerCount() {
1647
1347
  return this.proxy?.zombieTimerCount ?? 0;
@@ -1804,9 +1504,10 @@ class NativeKernel {
1804
1504
  this.pendingLocalMounts.unshift(localMount);
1805
1505
  this.pendingLocalMounts.sort((left, right) => right.path.length - left.path.length);
1806
1506
  if (!this.proxy) {
1507
+ // Pre-boot mounts apply during kernel initialization.
1807
1508
  return;
1808
1509
  }
1809
- this.proxy.mountFs(mountPath, filesystem, {
1510
+ return this.proxy.mountFs(mountPath, filesystem, {
1810
1511
  readOnly: localMount.readOnly,
1811
1512
  sidecarMount: localMount.sidecarMount,
1812
1513
  });
@@ -1817,7 +1518,7 @@ class NativeKernel {
1817
1518
  if (pendingIndex >= 0) {
1818
1519
  this.pendingLocalMounts.splice(pendingIndex, 1);
1819
1520
  }
1820
- this.proxy?.unmountFs(mountPath);
1521
+ return this.proxy?.unmountFs(mountPath);
1821
1522
  }
1822
1523
  async readFile(targetPath) {
1823
1524
  await this.ensureReady();
@@ -1917,7 +1618,6 @@ class NativeKernel {
1917
1618
  cwd: REPO_ROOT,
1918
1619
  command: ensureNativeSidecarBinary(),
1919
1620
  args: [],
1920
- frameTimeoutMs: NATIVE_SIDECAR_FRAME_TIMEOUT_MS,
1921
1621
  });
1922
1622
  const session = await client.authenticateAndOpenSession();
1923
1623
  const createVmConfig = {
package/dist/runtime.d.ts CHANGED
@@ -10,6 +10,7 @@ export interface VirtualDirEntry {
10
10
  export interface VirtualStat {
11
11
  mode: number;
12
12
  size: number;
13
+ sizeExact?: bigint;
13
14
  blocks: number;
14
15
  dev: number;
15
16
  rdev: number;
@@ -20,7 +21,9 @@ export interface VirtualStat {
20
21
  ctimeMs: number;
21
22
  birthtimeMs: number;
22
23
  ino: number;
24
+ inoExact?: bigint;
23
25
  nlink: number;
26
+ nlinkExact?: bigint;
24
27
  uid: number;
25
28
  gid: number;
26
29
  }
@@ -45,7 +48,9 @@ export interface VirtualFileSystem {
45
48
  lstat(path: string): Promise<VirtualStat>;
46
49
  link(oldPath: string, newPath: string): Promise<void>;
47
50
  chmod(path: string, mode: number): Promise<void>;
48
- chown(path: string, uid: number, gid: number): Promise<void>;
51
+ chown(path: string, uid: number, gid: number, options?: {
52
+ followSymlinks?: boolean;
53
+ }): Promise<void>;
49
54
  utimes(path: string, atime: number, mtime: number): Promise<void>;
50
55
  truncate(path: string, length: number): Promise<void>;
51
56
  pread(path: string, offset: number, length: number): Promise<Uint8Array>;
@@ -102,6 +107,7 @@ export interface ManagedProcess {
102
107
  export interface ShellHandle {
103
108
  pid: number;
104
109
  write(data: Uint8Array | string): Promise<void>;
110
+ /** Ordered PTY output containing stdout and stderr exactly once. */
105
111
  onData: ((data: Uint8Array) => void) | null;
106
112
  resize(cols: number, rows: number): void;
107
113
  kill(signal?: number): void;
@@ -114,6 +120,7 @@ export interface OpenShellOptions {
114
120
  cwd?: string;
115
121
  cols?: number;
116
122
  rows?: number;
123
+ /** Optional stderr-only diagnostic tap; do not render it alongside `onData`. */
117
124
  onStderr?: (data: Uint8Array) => void;
118
125
  }
119
126
  export interface ConnectTerminalOptions extends OpenShellOptions {
@@ -275,8 +282,8 @@ export interface Kernel extends KernelInterface {
275
282
  connectTerminal(options?: ConnectTerminalOptions): Promise<number>;
276
283
  mountFs(path: string, fs: VirtualFileSystem, options?: {
277
284
  readOnly?: boolean;
278
- }): void;
279
- unmountFs(path: string): void;
285
+ }): void | Promise<void>;
286
+ unmountFs(path: string): void | Promise<void>;
280
287
  readFile(path: string): Promise<Uint8Array>;
281
288
  writeFile(path: string, content: string | Uint8Array): Promise<void>;
282
289
  mkdir(path: string): Promise<void>;
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
  }