@rivet-dev/agentos-core 0.0.0-agentos-dylib-plugin.f065be7 → 0.0.0-chdir-fix.3d9feab
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.
- package/README.md +8 -11
- package/dist/agent-os.d.ts +185 -43
- package/dist/agent-os.js +834 -536
- package/dist/agentos-package.d.ts +29 -0
- package/dist/agentos-package.js +88 -0
- package/dist/base-filesystem.d.ts +2 -15
- package/dist/base-filesystem.js +23 -171
- package/dist/cron/cron-manager.js +1 -4
- package/dist/cron/types.d.ts +1 -1
- package/dist/default-software.d.ts +8 -0
- package/dist/default-software.js +10 -0
- package/dist/generated/AcpLimitsConfig.d.ts +4 -0
- package/dist/generated/AcpLimitsConfig.js +2 -0
- package/dist/generated/CreateVmConfig.d.ts +26 -0
- package/dist/generated/CreateVmConfig.js +1 -0
- package/dist/generated/FsPermissionRule.d.ts +6 -0
- package/dist/generated/FsPermissionRule.js +1 -0
- package/dist/generated/FsPermissionRuleSet.d.ts +6 -0
- package/dist/generated/FsPermissionRuleSet.js +1 -0
- package/dist/generated/FsPermissionScope.d.ts +3 -0
- package/dist/generated/FsPermissionScope.js +1 -0
- package/dist/generated/HttpLimitsConfig.d.ts +3 -0
- package/dist/generated/HttpLimitsConfig.js +2 -0
- package/dist/generated/JsModuleResolution.d.ts +1 -0
- package/dist/generated/JsModuleResolution.js +2 -0
- package/dist/generated/JsRuntimeConfig.d.ts +31 -0
- package/dist/generated/JsRuntimeConfig.js +1 -0
- package/dist/generated/JsRuntimeLimitsConfig.d.ts +11 -0
- package/dist/generated/JsRuntimeLimitsConfig.js +2 -0
- package/dist/generated/JsRuntimePlatform.d.ts +1 -0
- package/dist/generated/JsRuntimePlatform.js +2 -0
- package/dist/generated/MountPluginDescriptor.d.ts +4 -0
- package/dist/generated/MountPluginDescriptor.js +2 -0
- package/dist/generated/NativeRootFilesystemConfig.d.ts +5 -0
- package/dist/generated/NativeRootFilesystemConfig.js +1 -0
- package/dist/generated/PatternPermissionRule.d.ts +6 -0
- package/dist/generated/PatternPermissionRule.js +1 -0
- package/dist/generated/PatternPermissionRuleSet.d.ts +6 -0
- package/dist/generated/PatternPermissionRuleSet.js +1 -0
- package/dist/generated/PatternPermissionScope.d.ts +3 -0
- package/dist/generated/PatternPermissionScope.js +1 -0
- package/dist/generated/PermissionMode.d.ts +1 -0
- package/dist/generated/PermissionMode.js +2 -0
- package/dist/generated/PermissionsPolicy.d.ts +10 -0
- package/dist/generated/PermissionsPolicy.js +1 -0
- package/dist/generated/PluginLimitsConfig.d.ts +4 -0
- package/dist/generated/PluginLimitsConfig.js +2 -0
- package/dist/generated/PythonLimitsConfig.d.ts +6 -0
- package/dist/generated/PythonLimitsConfig.js +2 -0
- package/dist/generated/ResourceLimitsConfig.d.ts +24 -0
- package/dist/generated/ResourceLimitsConfig.js +2 -0
- package/dist/generated/RootFilesystemConfig.d.ts +9 -0
- package/dist/generated/RootFilesystemConfig.js +1 -0
- package/dist/generated/RootFilesystemEntry.d.ts +13 -0
- package/dist/generated/RootFilesystemEntry.js +1 -0
- package/dist/generated/RootFilesystemEntryEncoding.d.ts +1 -0
- package/dist/generated/RootFilesystemEntryEncoding.js +2 -0
- package/dist/generated/RootFilesystemEntryKind.d.ts +1 -0
- package/dist/generated/RootFilesystemEntryKind.js +2 -0
- package/dist/generated/RootFilesystemLowerDescriptor.d.ts +7 -0
- package/dist/generated/RootFilesystemLowerDescriptor.js +1 -0
- package/dist/generated/RootFilesystemMode.d.ts +1 -0
- package/dist/generated/RootFilesystemMode.js +2 -0
- package/dist/generated/ToolLimitsConfig.d.ts +10 -0
- package/dist/generated/ToolLimitsConfig.js +2 -0
- package/dist/generated/VmDnsConfig.d.ts +6 -0
- package/dist/generated/VmDnsConfig.js +2 -0
- package/dist/generated/VmLimitsConfig.d.ts +18 -0
- package/dist/generated/VmLimitsConfig.js +1 -0
- package/dist/generated/VmListenPolicyConfig.d.ts +5 -0
- package/dist/generated/VmListenPolicyConfig.js +2 -0
- package/dist/generated/WasmLimitsConfig.d.ts +7 -0
- package/dist/generated/WasmLimitsConfig.js +2 -0
- package/dist/host-dir-mount.d.ts +1 -1
- package/dist/host-tools-zod.js +27 -7
- package/dist/host-tools.js +9 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -2
- package/dist/layers.d.ts +3 -3
- package/dist/layers.js +22 -13
- package/dist/options-schema.d.ts +453 -0
- package/dist/options-schema.js +268 -0
- package/dist/packages.d.ts +17 -116
- package/dist/packages.js +35 -322
- package/dist/runtime-compat.d.ts +27 -7
- package/dist/runtime-compat.js +133 -147
- package/dist/runtime.d.ts +37 -7
- package/dist/sandbox.d.ts +112 -0
- package/dist/sandbox.js +264 -0
- package/dist/sidecar/{agent-os-protocol.d.ts → agentos-protocol.d.ts} +99 -1
- package/dist/sidecar/{agent-os-protocol.js → agentos-protocol.js} +156 -19
- package/dist/sidecar/native-process-client.d.ts +6 -2
- package/dist/sidecar/native-process-client.js +8 -1
- package/dist/sidecar/permissions.d.ts +1 -1
- package/dist/sidecar/permissions.js +4 -4
- package/dist/sidecar/rpc-client.d.ts +87 -6
- package/dist/sidecar/rpc-client.js +433 -93
- package/dist/test/sandbox-agent.js +1 -1
- package/dist/types.d.ts +10 -3
- package/package.json +42 -35
- package/dist/agents.d.ts +0 -63
- package/dist/agents.js +0 -42
- package/fixtures/AGENTOS_SYSTEM_PROMPT.md +0 -21
|
@@ -7,6 +7,30 @@ const PROTECTED_READ_ONLY_GUEST_ROOTS = ["/etc/agentos"];
|
|
|
7
7
|
const TRAILING_OUTPUT_DRAIN_INTERVAL_MS = 10;
|
|
8
8
|
const TRAILING_OUTPUT_DRAIN_MAX_MS = 250;
|
|
9
9
|
const TRAILING_OUTPUT_DRAIN_QUIET_TURNS = 2;
|
|
10
|
+
function shouldLogStructuredSidecarEvent(name) {
|
|
11
|
+
const normalized = name.toLowerCase();
|
|
12
|
+
return (normalized === "limit_warning" ||
|
|
13
|
+
normalized.startsWith("security.") ||
|
|
14
|
+
normalized.includes("warning") ||
|
|
15
|
+
normalized.includes("failed") ||
|
|
16
|
+
normalized.includes("error"));
|
|
17
|
+
}
|
|
18
|
+
function formatStructuredSidecarDetail(detail) {
|
|
19
|
+
const entries = Object.entries(detail);
|
|
20
|
+
if (entries.length === 0) {
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
return entries.map(([key, value]) => `${key}=${JSON.stringify(value)}`).join(" ");
|
|
24
|
+
}
|
|
25
|
+
function logStructuredSidecarEvent(name, detail) {
|
|
26
|
+
if (!shouldLogStructuredSidecarEvent(name)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const formatted = formatStructuredSidecarDetail(detail);
|
|
30
|
+
console.warn(formatted
|
|
31
|
+
? `[agent-os] sidecar ${name}: ${formatted}`
|
|
32
|
+
: `[agent-os] sidecar ${name}`);
|
|
33
|
+
}
|
|
10
34
|
async function drainTrailingProcessOutputTurn(delayMs = 0) {
|
|
11
35
|
// Native-sidecar `process_output` events can lag one macrotask behind the
|
|
12
36
|
// terminal `process_exited` notification for very short-lived processes, and
|
|
@@ -142,7 +166,10 @@ function parseSimpleExecCommand(command) {
|
|
|
142
166
|
return tokens;
|
|
143
167
|
}
|
|
144
168
|
function canUseDirectExec(driver, commandName) {
|
|
145
|
-
return driver === "wasmvm" ||
|
|
169
|
+
return (driver === "wasmvm" ||
|
|
170
|
+
(driver === "node" && commandName === "node") ||
|
|
171
|
+
(driver === "python" &&
|
|
172
|
+
(commandName === "python" || commandName === "python3")));
|
|
146
173
|
}
|
|
147
174
|
function shellSingleQuote(value) {
|
|
148
175
|
if (value.length === 0) {
|
|
@@ -181,7 +208,17 @@ export class NativeSidecarKernelProxy {
|
|
|
181
208
|
client;
|
|
182
209
|
session;
|
|
183
210
|
vm;
|
|
211
|
+
ownsClient;
|
|
184
212
|
localMounts;
|
|
213
|
+
baseSidecarMounts;
|
|
214
|
+
permissions;
|
|
215
|
+
commandPermissions;
|
|
216
|
+
loopbackExemptPorts;
|
|
217
|
+
// Mutable: runtime `linkSoftware` appends via `registerLinkedPackage` so
|
|
218
|
+
// later mount reconfigures resend linked packages too, not just boot ones.
|
|
219
|
+
packages;
|
|
220
|
+
packagesMountAt;
|
|
221
|
+
toolShimCommands;
|
|
185
222
|
commandDrivers;
|
|
186
223
|
onWasmCommandResolved;
|
|
187
224
|
onDispose;
|
|
@@ -199,6 +236,7 @@ export class NativeSidecarKernelProxy {
|
|
|
199
236
|
zombieTimerCountRefresh = null;
|
|
200
237
|
disposed = false;
|
|
201
238
|
pumpError = null;
|
|
239
|
+
mountReconfigurePromise = null;
|
|
202
240
|
nextSyntheticPid = SYNTHETIC_PID_BASE;
|
|
203
241
|
eventPumpAbortController = new AbortController();
|
|
204
242
|
eventPump;
|
|
@@ -206,10 +244,20 @@ export class NativeSidecarKernelProxy {
|
|
|
206
244
|
this.client = options.client;
|
|
207
245
|
this.session = options.session;
|
|
208
246
|
this.vm = options.vm;
|
|
247
|
+
this.ownsClient = options.ownsClient ?? true;
|
|
209
248
|
this.env = { ...options.env };
|
|
210
249
|
this.cwd = options.cwd;
|
|
211
250
|
this.defaultExecCwd = options.defaultExecCwd;
|
|
212
251
|
this.localMounts = [...options.localMounts].sort((left, right) => right.path.length - left.path.length);
|
|
252
|
+
const localMountPaths = new Set(this.localMounts.map((mount) => mount.path));
|
|
253
|
+
this.baseSidecarMounts = options.sidecarMounts.filter((mount) => mount.plugin.id !== "js_bridge" ||
|
|
254
|
+
!localMountPaths.has(posixPath.normalize(mount.guestPath)));
|
|
255
|
+
this.permissions = options.permissions;
|
|
256
|
+
this.commandPermissions = options.commandPermissions;
|
|
257
|
+
this.loopbackExemptPorts = options.loopbackExemptPorts;
|
|
258
|
+
this.packages = options.packages ? [...options.packages] : [];
|
|
259
|
+
this.packagesMountAt = options.packagesMountAt;
|
|
260
|
+
this.toolShimCommands = options.toolShimCommands;
|
|
213
261
|
this.commandDrivers = buildCommandMap(options.commandGuestPaths);
|
|
214
262
|
this.onWasmCommandResolved = options.onWasmCommandResolved;
|
|
215
263
|
this.onDispose = options.onDispose;
|
|
@@ -233,12 +281,24 @@ export class NativeSidecarKernelProxy {
|
|
|
233
281
|
this.commandDrivers.set(name, "wasmvm");
|
|
234
282
|
}
|
|
235
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* Record a runtime-linked package (`linkSoftware`) so mount reconfigures
|
|
286
|
+
* resend it. Rust `configure_vm` rebuilds the whole VM configuration from
|
|
287
|
+
* each payload, so a linked package omitted here would be silently
|
|
288
|
+
* unprojected from `/opt/agentos` by the next `mountFs`/`unmountFs`.
|
|
289
|
+
*/
|
|
290
|
+
registerLinkedPackage(descriptor) {
|
|
291
|
+
if (!this.packages.some((pkg) => pkg.path === descriptor.path)) {
|
|
292
|
+
this.packages.push({ path: descriptor.path });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
236
295
|
async dispose() {
|
|
237
296
|
if (this.disposed) {
|
|
238
297
|
return;
|
|
239
298
|
}
|
|
240
299
|
this.disposed = true;
|
|
241
300
|
this.eventPumpAbortController.abort();
|
|
301
|
+
await this.mountReconfigurePromise?.catch(() => { });
|
|
242
302
|
const liveProcesses = [...this.trackedProcesses.values()].filter((entry) => entry.exitCode === null);
|
|
243
303
|
await Promise.allSettled(liveProcesses.map((entry) => this.signalProcess(entry, 15)));
|
|
244
304
|
await this.client.disposeVm(this.session, this.vm).catch(() => { });
|
|
@@ -251,9 +311,42 @@ export class NativeSidecarKernelProxy {
|
|
|
251
311
|
this.finishProcess(entry, 143);
|
|
252
312
|
}
|
|
253
313
|
}
|
|
254
|
-
|
|
314
|
+
// Only tear down the shared sidecar process when this proxy owns it. VMs
|
|
315
|
+
// leased from an `AgentOsSidecar` handle share one process, which is
|
|
316
|
+
// disposed when the handle is disposed.
|
|
317
|
+
if (this.ownsClient) {
|
|
318
|
+
await this.client.dispose().catch(() => { });
|
|
319
|
+
}
|
|
255
320
|
await this.eventPump.catch(() => { });
|
|
256
321
|
await this.onDispose?.().catch(() => { });
|
|
322
|
+
// Drop all per-VM tracking state so a disposed proxy retains nothing.
|
|
323
|
+
for (const entry of this.trackedProcesses.values()) {
|
|
324
|
+
entry.onStdout.clear();
|
|
325
|
+
entry.onStderr.clear();
|
|
326
|
+
}
|
|
327
|
+
this.trackedProcesses.clear();
|
|
328
|
+
this.trackedProcessesById.clear();
|
|
329
|
+
this.signalStates.clear();
|
|
330
|
+
this.signalRefreshes.clear();
|
|
331
|
+
this.localMounts.length = 0;
|
|
332
|
+
}
|
|
333
|
+
/** Test-only snapshot of the per-VM tracking collection sizes. */
|
|
334
|
+
__trackingSizesForTest() {
|
|
335
|
+
return {
|
|
336
|
+
trackedProcesses: this.trackedProcesses.size,
|
|
337
|
+
trackedProcessesById: this.trackedProcessesById.size,
|
|
338
|
+
signalStates: this.signalStates.size,
|
|
339
|
+
signalRefreshes: this.signalRefreshes.size,
|
|
340
|
+
localMounts: this.localMounts.length,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
/** Test-only handle to a tracked entry so its listener Sets can be inspected. */
|
|
344
|
+
__trackedEntryForTest(pid) {
|
|
345
|
+
return this.trackedProcesses.get(pid);
|
|
346
|
+
}
|
|
347
|
+
/** Test-only join point for in-flight signal-state refreshes. */
|
|
348
|
+
async __awaitSignalRefreshesForTest() {
|
|
349
|
+
await Promise.allSettled([...this.signalRefreshes.values()]);
|
|
257
350
|
}
|
|
258
351
|
async exec(command, options) {
|
|
259
352
|
if (!this.commands.has("sh")) {
|
|
@@ -263,9 +356,6 @@ export class NativeSidecarKernelProxy {
|
|
|
263
356
|
const stderrChunks = [];
|
|
264
357
|
const effectiveCwd = options?.cwd ?? this.defaultExecCwd ?? this.cwd;
|
|
265
358
|
const parsedCommand = parseSimpleExecCommand(command);
|
|
266
|
-
const resolveExecPath = (targetPath) => targetPath.startsWith("/")
|
|
267
|
-
? posixPath.normalize(targetPath)
|
|
268
|
-
: posixPath.normalize(posixPath.join(effectiveCwd, targetPath));
|
|
269
359
|
const runAndCapture = async (proc, stdinOverride, readExitCode) => {
|
|
270
360
|
if (stdinOverride !== undefined) {
|
|
271
361
|
proc.writeStdin(stdinOverride);
|
|
@@ -301,43 +391,6 @@ export class NativeSidecarKernelProxy {
|
|
|
301
391
|
stderr: Buffer.concat(stderrChunks.map((chunk) => Buffer.from(chunk))).toString("utf8"),
|
|
302
392
|
};
|
|
303
393
|
};
|
|
304
|
-
if (parsedCommand &&
|
|
305
|
-
(parsedCommand[0] === "sh" || parsedCommand[0] === "/bin/sh") &&
|
|
306
|
-
parsedCommand[1] === "-c" &&
|
|
307
|
-
parsedCommand.length === 3) {
|
|
308
|
-
const shellScript = parsedCommand[2].trim();
|
|
309
|
-
const exitMatch = shellScript.match(/^exit(?:\s+(-?\d+))?$/);
|
|
310
|
-
if (exitMatch) {
|
|
311
|
-
return {
|
|
312
|
-
exitCode: Number.parseInt(exitMatch[1] ?? "0", 10),
|
|
313
|
-
stdout: "",
|
|
314
|
-
stderr: "",
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
return this.exec(parsedCommand[2], options);
|
|
318
|
-
}
|
|
319
|
-
if (parsedCommand &&
|
|
320
|
-
parsedCommand[0] === "chmod" &&
|
|
321
|
-
parsedCommand.length >= 3 &&
|
|
322
|
-
/^[0-7]{3,4}$/.test(parsedCommand[1] ?? "")) {
|
|
323
|
-
const mode = Number.parseInt(parsedCommand[1], 8);
|
|
324
|
-
for (const target of parsedCommand.slice(2)) {
|
|
325
|
-
await this.client.chmod(this.session, this.vm, resolveExecPath(target), mode);
|
|
326
|
-
}
|
|
327
|
-
return { exitCode: 0, stdout: "", stderr: "" };
|
|
328
|
-
}
|
|
329
|
-
if (parsedCommand &&
|
|
330
|
-
parsedCommand[0] === "stat" &&
|
|
331
|
-
parsedCommand.length === 4 &&
|
|
332
|
-
parsedCommand[1] === "-c" &&
|
|
333
|
-
parsedCommand[2] === "%a") {
|
|
334
|
-
const stat = await this.stat(resolveExecPath(parsedCommand[3]));
|
|
335
|
-
return {
|
|
336
|
-
exitCode: 0,
|
|
337
|
-
stdout: `${(stat.mode & 0o777).toString(8)}\n`,
|
|
338
|
-
stderr: "",
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
394
|
const parsedCommandDriver = parsedCommand
|
|
342
395
|
? this.commands.get(parsedCommand[0])
|
|
343
396
|
: undefined;
|
|
@@ -376,6 +429,51 @@ export class NativeSidecarKernelProxy {
|
|
|
376
429
|
});
|
|
377
430
|
return runAndCapture(proc);
|
|
378
431
|
}
|
|
432
|
+
async execArgv(command, args = [], options) {
|
|
433
|
+
const stdoutChunks = [];
|
|
434
|
+
const stderrChunks = [];
|
|
435
|
+
const effectiveCwd = options?.cwd ?? this.defaultExecCwd ?? this.cwd;
|
|
436
|
+
const runAndCapture = async (proc) => {
|
|
437
|
+
if (options?.stdin !== undefined) {
|
|
438
|
+
proc.writeStdin(options.stdin);
|
|
439
|
+
}
|
|
440
|
+
proc.closeStdin();
|
|
441
|
+
const waitPromise = proc.wait();
|
|
442
|
+
const exitCode = typeof options?.timeout === "number"
|
|
443
|
+
? await new Promise((resolve) => {
|
|
444
|
+
const timer = setTimeout(() => {
|
|
445
|
+
proc.kill(9);
|
|
446
|
+
void proc.wait().then(resolve);
|
|
447
|
+
}, options.timeout);
|
|
448
|
+
void waitPromise.then((code) => {
|
|
449
|
+
clearTimeout(timer);
|
|
450
|
+
resolve(code);
|
|
451
|
+
});
|
|
452
|
+
})
|
|
453
|
+
: await waitPromise;
|
|
454
|
+
await drainTrailingProcessOutputTurn();
|
|
455
|
+
return {
|
|
456
|
+
exitCode,
|
|
457
|
+
stdout: Buffer.concat(stdoutChunks.map((chunk) => Buffer.from(chunk))).toString("utf8"),
|
|
458
|
+
stderr: Buffer.concat(stderrChunks.map((chunk) => Buffer.from(chunk))).toString("utf8"),
|
|
459
|
+
};
|
|
460
|
+
};
|
|
461
|
+
if (this.commands.get(command) === "wasmvm") {
|
|
462
|
+
this.onWasmCommandResolved?.(command);
|
|
463
|
+
}
|
|
464
|
+
return runAndCapture(this.spawn(command, [...args], {
|
|
465
|
+
...options,
|
|
466
|
+
cwd: effectiveCwd,
|
|
467
|
+
onStdout: (chunk) => {
|
|
468
|
+
stdoutChunks.push(chunk);
|
|
469
|
+
options?.onStdout?.(chunk);
|
|
470
|
+
},
|
|
471
|
+
onStderr: (chunk) => {
|
|
472
|
+
stderrChunks.push(chunk);
|
|
473
|
+
options?.onStderr?.(chunk);
|
|
474
|
+
},
|
|
475
|
+
}));
|
|
476
|
+
}
|
|
379
477
|
spawn(command, args, options) {
|
|
380
478
|
let spawnCommand = command;
|
|
381
479
|
let spawnArgs = [...args];
|
|
@@ -402,11 +500,15 @@ export class NativeSidecarKernelProxy {
|
|
|
402
500
|
processId,
|
|
403
501
|
command: spawnCommand,
|
|
404
502
|
args: spawnArgs,
|
|
405
|
-
driver: spawnCommand === "node"
|
|
503
|
+
driver: spawnCommand === "node"
|
|
504
|
+
? "node"
|
|
505
|
+
: spawnCommand === "python" || spawnCommand === "python3"
|
|
506
|
+
? "python"
|
|
507
|
+
: "wasmvm",
|
|
406
508
|
cwd: options?.cwd ?? this.cwd,
|
|
407
509
|
env: {
|
|
408
510
|
...(options?.env ?? {}),
|
|
409
|
-
...(options?.streamStdin ? {
|
|
511
|
+
...(options?.streamStdin ? { AGENTOS_KEEP_STDIN_OPEN: "1" } : {}),
|
|
410
512
|
},
|
|
411
513
|
startTime: Date.now(),
|
|
412
514
|
exitTime: null,
|
|
@@ -434,16 +536,16 @@ export class NativeSidecarKernelProxy {
|
|
|
434
536
|
pid,
|
|
435
537
|
writeStdin: (data) => {
|
|
436
538
|
if (entry.exitCode !== null) {
|
|
437
|
-
return;
|
|
539
|
+
return Promise.resolve();
|
|
438
540
|
}
|
|
439
541
|
entry.pendingStdin.push(data);
|
|
440
|
-
|
|
542
|
+
return this.flushPendingStdin(entry).catch((error) => {
|
|
441
543
|
this.handleBackgroundProcessError(entry, error);
|
|
442
544
|
});
|
|
443
545
|
},
|
|
444
546
|
closeStdin: () => {
|
|
445
547
|
entry.pendingCloseStdin = true;
|
|
446
|
-
|
|
548
|
+
return this.closeTrackedStdin(entry).catch((error) => {
|
|
447
549
|
this.handleBackgroundProcessError(entry, error);
|
|
448
550
|
});
|
|
449
551
|
},
|
|
@@ -487,7 +589,6 @@ export class NativeSidecarKernelProxy {
|
|
|
487
589
|
const args = options?.args ??
|
|
488
590
|
(command === "sh" || command === "/bin/sh" ? ["-i"] : []);
|
|
489
591
|
const synthesizePrompt = !options?.command && !options?.args;
|
|
490
|
-
const autoCloseExplicitCommandStdin = Boolean(options?.command) && !["sh", "/bin/sh", "bash"].includes(command);
|
|
491
592
|
const promptText = "sh-0.4$ ";
|
|
492
593
|
const textEncoder = new TextEncoder();
|
|
493
594
|
const textDecoder = new TextDecoder();
|
|
@@ -497,14 +598,18 @@ export class NativeSidecarKernelProxy {
|
|
|
497
598
|
.replace(/\u001b\[[0-9;]*m/g, "")
|
|
498
599
|
.replace(/^.*WARN could not retrieve pid for child process\n?/gm, "")
|
|
499
600
|
.replace(/^ProcessExitError:.*\n(?:\s+at .*\n)*/gm, "");
|
|
601
|
+
const sanitizeNativeShellOutput = (chunk) => {
|
|
602
|
+
const text = textDecoder.decode(chunk);
|
|
603
|
+
const sanitized = text.replace(/^.*WARN could not retrieve pid for child process\n?/gm, "");
|
|
604
|
+
return sanitized.length > 0 ? textEncoder.encode(sanitized) : null;
|
|
605
|
+
};
|
|
500
606
|
let bufferedInput = "";
|
|
501
607
|
let bufferedCommand = "";
|
|
502
608
|
let activeForegroundProcess = null;
|
|
503
|
-
let shellEnv = { ...(options?.env ?? {}) };
|
|
609
|
+
let shellEnv = { ...(options?.env ?? {}), AGENTOS_EXEC_TTY: "1" };
|
|
504
610
|
let shellCwd = options?.cwd ?? this.cwd;
|
|
505
611
|
let syntheticCommandQueue = Promise.resolve();
|
|
506
612
|
let promptTimer = null;
|
|
507
|
-
let closeStdinTimer = null;
|
|
508
613
|
let commandInFlight = false;
|
|
509
614
|
let syntheticCursorAtLineStart = true;
|
|
510
615
|
const syntheticPid = this.nextSyntheticPid++;
|
|
@@ -519,12 +624,6 @@ export class NativeSidecarKernelProxy {
|
|
|
519
624
|
promptTimer = null;
|
|
520
625
|
}
|
|
521
626
|
};
|
|
522
|
-
const clearCloseStdinTimer = () => {
|
|
523
|
-
if (closeStdinTimer !== null) {
|
|
524
|
-
clearTimeout(closeStdinTimer);
|
|
525
|
-
closeStdinTimer = null;
|
|
526
|
-
}
|
|
527
|
-
};
|
|
528
627
|
const normalizeSyntheticTerminalText = (text) => text.replace(/\r?\n/g, "\r\n");
|
|
529
628
|
const updateSyntheticCursor = (text) => {
|
|
530
629
|
if (!text) {
|
|
@@ -620,15 +719,36 @@ export class NativeSidecarKernelProxy {
|
|
|
620
719
|
}
|
|
621
720
|
return parsed;
|
|
622
721
|
};
|
|
623
|
-
const writeForegroundInput = (proc, data) => {
|
|
722
|
+
const writeForegroundInput = async (proc, data) => {
|
|
624
723
|
if (typeof data === "string") {
|
|
625
724
|
for (const character of data) {
|
|
626
|
-
proc.writeStdin(character);
|
|
725
|
+
await proc.writeStdin(character);
|
|
627
726
|
}
|
|
628
727
|
return;
|
|
629
728
|
}
|
|
630
729
|
for (const byte of data) {
|
|
631
|
-
proc.writeStdin(new Uint8Array([byte]));
|
|
730
|
+
await proc.writeStdin(new Uint8Array([byte]));
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
const appendSyntheticInput = (input) => {
|
|
734
|
+
for (const character of input) {
|
|
735
|
+
if (character === "\u0004") {
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
if (character === "\b" || character === "\u007f") {
|
|
739
|
+
if (bufferedInput.length > 0) {
|
|
740
|
+
bufferedInput = bufferedInput.slice(0, -1);
|
|
741
|
+
emitSyntheticTerminal("\b \b");
|
|
742
|
+
}
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
bufferedInput += character;
|
|
746
|
+
if (character === "\n") {
|
|
747
|
+
emitSyntheticTerminal("\n");
|
|
748
|
+
}
|
|
749
|
+
else if (character >= " ") {
|
|
750
|
+
emitSyntheticTerminal(character);
|
|
751
|
+
}
|
|
632
752
|
}
|
|
633
753
|
};
|
|
634
754
|
let onData = null;
|
|
@@ -640,7 +760,7 @@ export class NativeSidecarKernelProxy {
|
|
|
640
760
|
schedulePrompt(0);
|
|
641
761
|
return {
|
|
642
762
|
pid: syntheticPid,
|
|
643
|
-
write(data) {
|
|
763
|
+
async write(data) {
|
|
644
764
|
if (syntheticExitCode !== null) {
|
|
645
765
|
return;
|
|
646
766
|
}
|
|
@@ -651,13 +771,13 @@ export class NativeSidecarKernelProxy {
|
|
|
651
771
|
if (rawText.includes("\u0003")) {
|
|
652
772
|
const [beforeInterrupt] = rawText.split("\u0003");
|
|
653
773
|
if (beforeInterrupt) {
|
|
654
|
-
writeForegroundInput(activeForegroundProcess, beforeInterrupt);
|
|
774
|
+
await writeForegroundInput(activeForegroundProcess, beforeInterrupt);
|
|
655
775
|
}
|
|
656
776
|
emitSyntheticTerminal("^C\n");
|
|
657
777
|
activeForegroundProcess.kill(2);
|
|
658
778
|
return;
|
|
659
779
|
}
|
|
660
|
-
writeForegroundInput(activeForegroundProcess, data);
|
|
780
|
+
await writeForegroundInput(activeForegroundProcess, data);
|
|
661
781
|
return;
|
|
662
782
|
}
|
|
663
783
|
const rawText = typeof data === "string"
|
|
@@ -680,7 +800,7 @@ export class NativeSidecarKernelProxy {
|
|
|
680
800
|
finishSyntheticShell(0);
|
|
681
801
|
return;
|
|
682
802
|
}
|
|
683
|
-
|
|
803
|
+
appendSyntheticInput(text.replace(/\r\n/g, "\n").replace(/\r/g, "\n"));
|
|
684
804
|
while (true) {
|
|
685
805
|
const newlineIndex = bufferedInput.indexOf("\n");
|
|
686
806
|
if (newlineIndex < 0) {
|
|
@@ -690,7 +810,6 @@ export class NativeSidecarKernelProxy {
|
|
|
690
810
|
.slice(0, newlineIndex)
|
|
691
811
|
.replace(/\r$/, "");
|
|
692
812
|
bufferedInput = bufferedInput.slice(newlineIndex + 1);
|
|
693
|
-
emitSyntheticStdout(`${line}\n`);
|
|
694
813
|
const nextCommand = bufferedCommand
|
|
695
814
|
? `${bufferedCommand}\n${line}`
|
|
696
815
|
: line;
|
|
@@ -789,20 +908,33 @@ export class NativeSidecarKernelProxy {
|
|
|
789
908
|
};
|
|
790
909
|
}
|
|
791
910
|
const proc = this.spawn(command, args, {
|
|
792
|
-
env:
|
|
911
|
+
env: {
|
|
912
|
+
...(options?.env ?? {}),
|
|
913
|
+
...(options?.cols ? { COLUMNS: String(Math.trunc(options.cols)) } : {}),
|
|
914
|
+
...(options?.rows ? { LINES: String(Math.trunc(options.rows)) } : {}),
|
|
915
|
+
AGENTOS_EXEC_TTY: "1",
|
|
916
|
+
},
|
|
793
917
|
cwd: options?.cwd,
|
|
794
918
|
streamStdin: true,
|
|
795
919
|
onStdout: (chunk) => {
|
|
920
|
+
const sanitized = sanitizeNativeShellOutput(chunk);
|
|
921
|
+
if (!sanitized) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
796
924
|
for (const handler of stdoutHandlers) {
|
|
797
|
-
handler(
|
|
925
|
+
handler(sanitized);
|
|
798
926
|
}
|
|
799
927
|
if (commandInFlight) {
|
|
800
928
|
schedulePrompt(120);
|
|
801
929
|
}
|
|
802
930
|
},
|
|
803
931
|
onStderr: (chunk) => {
|
|
932
|
+
const sanitized = sanitizeNativeShellOutput(chunk);
|
|
933
|
+
if (!sanitized) {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
804
936
|
for (const handler of stderrHandlers) {
|
|
805
|
-
handler(
|
|
937
|
+
handler(sanitized);
|
|
806
938
|
}
|
|
807
939
|
if (commandInFlight) {
|
|
808
940
|
schedulePrompt(120);
|
|
@@ -811,18 +943,11 @@ export class NativeSidecarKernelProxy {
|
|
|
811
943
|
});
|
|
812
944
|
return {
|
|
813
945
|
pid: proc.pid,
|
|
814
|
-
write(data) {
|
|
946
|
+
async write(data) {
|
|
815
947
|
if (synthesizePrompt) {
|
|
816
948
|
return;
|
|
817
949
|
}
|
|
818
|
-
proc.writeStdin(data);
|
|
819
|
-
if (autoCloseExplicitCommandStdin) {
|
|
820
|
-
clearCloseStdinTimer();
|
|
821
|
-
closeStdinTimer = setTimeout(() => {
|
|
822
|
-
closeStdinTimer = null;
|
|
823
|
-
proc.closeStdin();
|
|
824
|
-
}, 100);
|
|
825
|
-
}
|
|
950
|
+
await proc.writeStdin(data);
|
|
826
951
|
if (synthesizePrompt &&
|
|
827
952
|
typeof data === "string" &&
|
|
828
953
|
(data.includes("\n") || data.includes("\r"))) {
|
|
@@ -836,11 +961,18 @@ export class NativeSidecarKernelProxy {
|
|
|
836
961
|
set onData(handler) {
|
|
837
962
|
onData = handler;
|
|
838
963
|
},
|
|
839
|
-
resize() {
|
|
840
|
-
|
|
964
|
+
resize: (cols, rows) => {
|
|
965
|
+
const entry = this.trackedProcesses.get(proc.pid);
|
|
966
|
+
if (!entry || entry.exitCode !== null) {
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
void entry.startPromise
|
|
970
|
+
.then(() => this.client.resizePty(this.session, this.vm, entry.processId, Math.trunc(cols), Math.trunc(rows)))
|
|
971
|
+
.catch((error) => {
|
|
972
|
+
this.handleBackgroundProcessError(entry, error);
|
|
973
|
+
});
|
|
841
974
|
},
|
|
842
975
|
kill(signal) {
|
|
843
|
-
clearCloseStdinTimer();
|
|
844
976
|
clearPromptTimer();
|
|
845
977
|
proc.kill(signal);
|
|
846
978
|
},
|
|
@@ -940,12 +1072,41 @@ export class NativeSidecarKernelProxy {
|
|
|
940
1072
|
const entries = await this.client.readdir(this.session, this.vm, path);
|
|
941
1073
|
return [...new Set([...entries, ...this.mountedChildNames(path)])].sort((a, b) => a.localeCompare(b));
|
|
942
1074
|
}
|
|
1075
|
+
async readdirRecursive(path, options) {
|
|
1076
|
+
const local = this.resolveLocalMount(path);
|
|
1077
|
+
if (local) {
|
|
1078
|
+
return this.readdirRecursiveLocal(local.mount.fs, local.relativePath, options?.maxDepth);
|
|
1079
|
+
}
|
|
1080
|
+
return (await this.client.readdirRecursive(this.session, this.vm, path, options)).map((entry) => ({ ...entry, size: Number(entry.size) }));
|
|
1081
|
+
}
|
|
943
1082
|
async removeFile(path) {
|
|
944
1083
|
return this.dispatchWrite(path, (mount, relativePath) => mount.fs.removeFile(relativePath), () => this.client.removeFile(this.session, this.vm, path));
|
|
945
1084
|
}
|
|
946
1085
|
async removeDir(path) {
|
|
947
1086
|
return this.dispatchWrite(path, (mount, relativePath) => mount.fs.removeDir(relativePath), () => this.client.removeDir(this.session, this.vm, path));
|
|
948
1087
|
}
|
|
1088
|
+
async removePath(path, options) {
|
|
1089
|
+
return this.dispatchWrite(path, (mount, relativePath) => this.removePathLocal(mount.fs, relativePath, options?.recursive ?? false), () => this.client.removePath(this.session, this.vm, path, {
|
|
1090
|
+
recursive: options?.recursive ?? false,
|
|
1091
|
+
}));
|
|
1092
|
+
}
|
|
1093
|
+
async copyPath(fromPath, toPath, options) {
|
|
1094
|
+
const from = this.resolveLocalMount(fromPath);
|
|
1095
|
+
const to = this.resolveLocalMount(toPath);
|
|
1096
|
+
if (!!from !== !!to) {
|
|
1097
|
+
throw errnoError("EXDEV", "cross-device link not permitted");
|
|
1098
|
+
}
|
|
1099
|
+
if (from && to) {
|
|
1100
|
+
if (from.mount.path !== to.mount.path) {
|
|
1101
|
+
throw errnoError("EXDEV", "cross-device link not permitted");
|
|
1102
|
+
}
|
|
1103
|
+
this.assertLocalWritable(to.mount);
|
|
1104
|
+
return this.copyPathLocal(from.mount.fs, from.relativePath, to.relativePath, options?.recursive ?? false);
|
|
1105
|
+
}
|
|
1106
|
+
return this.client.copyPath(this.session, this.vm, fromPath, toPath, {
|
|
1107
|
+
recursive: options?.recursive ?? false,
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
949
1110
|
async rename(oldPath, newPath) {
|
|
950
1111
|
const from = this.resolveLocalMount(oldPath);
|
|
951
1112
|
const to = this.resolveLocalMount(newPath);
|
|
@@ -961,19 +1122,93 @@ export class NativeSidecarKernelProxy {
|
|
|
961
1122
|
}
|
|
962
1123
|
return this.client.rename(this.session, this.vm, oldPath, newPath);
|
|
963
1124
|
}
|
|
1125
|
+
async movePath(oldPath, newPath) {
|
|
1126
|
+
const from = this.resolveLocalMount(oldPath);
|
|
1127
|
+
const to = this.resolveLocalMount(newPath);
|
|
1128
|
+
if (!!from !== !!to) {
|
|
1129
|
+
throw errnoError("EXDEV", "cross-device link not permitted");
|
|
1130
|
+
}
|
|
1131
|
+
if (from && to) {
|
|
1132
|
+
if (from.mount.path !== to.mount.path) {
|
|
1133
|
+
throw errnoError("EXDEV", "cross-device link not permitted");
|
|
1134
|
+
}
|
|
1135
|
+
this.assertLocalWritable(from.mount);
|
|
1136
|
+
return from.mount.fs.rename(from.relativePath, to.relativePath);
|
|
1137
|
+
}
|
|
1138
|
+
return this.client.movePath(this.session, this.vm, oldPath, newPath);
|
|
1139
|
+
}
|
|
964
1140
|
mountFs(path, driver, options) {
|
|
965
1141
|
this.localMounts.unshift({
|
|
966
1142
|
path: posixPath.normalize(path),
|
|
967
1143
|
fs: driver,
|
|
968
1144
|
readOnly: options?.readOnly ?? false,
|
|
1145
|
+
sidecarMount: options?.sidecarMount,
|
|
969
1146
|
});
|
|
970
1147
|
this.localMounts.sort((left, right) => right.path.length - left.path.length);
|
|
1148
|
+
// Resolves once the sidecar has the mount; a swallowed rejection here
|
|
1149
|
+
// used to turn reconfigure failures into silently missing guest mounts.
|
|
1150
|
+
// The local catch only guards callers that drop the promise — awaiting
|
|
1151
|
+
// callers still observe the rejection.
|
|
1152
|
+
const applied = this.reconfigureSidecarMounts();
|
|
1153
|
+
applied.catch(() => { });
|
|
1154
|
+
return applied;
|
|
971
1155
|
}
|
|
972
1156
|
unmountFs(path) {
|
|
973
1157
|
const normalized = posixPath.normalize(path);
|
|
974
1158
|
const index = this.localMounts.findIndex((mount) => mount.path === normalized);
|
|
975
|
-
if (index
|
|
976
|
-
|
|
1159
|
+
if (index < 0) {
|
|
1160
|
+
return Promise.resolve();
|
|
1161
|
+
}
|
|
1162
|
+
this.localMounts.splice(index, 1);
|
|
1163
|
+
const applied = this.reconfigureSidecarMounts();
|
|
1164
|
+
applied.catch(() => { });
|
|
1165
|
+
return applied;
|
|
1166
|
+
}
|
|
1167
|
+
desiredSidecarMounts() {
|
|
1168
|
+
return [
|
|
1169
|
+
...this.baseSidecarMounts,
|
|
1170
|
+
...this.localMounts.map((mount) => mount.sidecarMount ?? {
|
|
1171
|
+
guestPath: mount.path,
|
|
1172
|
+
readOnly: mount.readOnly,
|
|
1173
|
+
plugin: {
|
|
1174
|
+
id: "js_bridge",
|
|
1175
|
+
config: {},
|
|
1176
|
+
},
|
|
1177
|
+
}),
|
|
1178
|
+
];
|
|
1179
|
+
}
|
|
1180
|
+
reconfigureSidecarMounts() {
|
|
1181
|
+
const run = async () => {
|
|
1182
|
+
if (this.disposed) {
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
// Rust `configure_vm` rebuilds the whole VM configuration from this
|
|
1186
|
+
// payload, so resend the boot packages / tool shim commands too —
|
|
1187
|
+
// omitting them here strips the `/opt/agentos` projections and tool
|
|
1188
|
+
// shims from the VM as a side effect of a runtime mount change.
|
|
1189
|
+
await this.client.configureVm(this.session, this.vm, {
|
|
1190
|
+
mounts: this.desiredSidecarMounts(),
|
|
1191
|
+
permissions: this.permissions,
|
|
1192
|
+
commandPermissions: this.commandPermissions,
|
|
1193
|
+
loopbackExemptPorts: this.loopbackExemptPorts,
|
|
1194
|
+
packages: this.packages,
|
|
1195
|
+
packagesMountAt: this.packagesMountAt,
|
|
1196
|
+
toolShimCommands: this.toolShimCommands,
|
|
1197
|
+
});
|
|
1198
|
+
};
|
|
1199
|
+
const previous = this.mountReconfigurePromise ?? Promise.resolve();
|
|
1200
|
+
const next = previous.then(run, run);
|
|
1201
|
+
const tracked = next.finally(() => {
|
|
1202
|
+
if (this.mountReconfigurePromise === tracked) {
|
|
1203
|
+
this.mountReconfigurePromise = null;
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
this.mountReconfigurePromise = tracked;
|
|
1207
|
+
return tracked;
|
|
1208
|
+
}
|
|
1209
|
+
async waitForMountReconfigure() {
|
|
1210
|
+
if (this.mountReconfigurePromise) {
|
|
1211
|
+
await this.mountReconfigurePromise;
|
|
977
1212
|
}
|
|
978
1213
|
}
|
|
979
1214
|
snapshotProcesses() {
|
|
@@ -1087,6 +1322,7 @@ export class NativeSidecarKernelProxy {
|
|
|
1087
1322
|
}
|
|
1088
1323
|
}
|
|
1089
1324
|
async startTrackedProcess(entry) {
|
|
1325
|
+
await this.waitForMountReconfigure();
|
|
1090
1326
|
const started = await this.client.execute(this.session, this.vm, {
|
|
1091
1327
|
processId: entry.processId,
|
|
1092
1328
|
command: entry.command,
|
|
@@ -1112,11 +1348,13 @@ export class NativeSidecarKernelProxy {
|
|
|
1112
1348
|
}
|
|
1113
1349
|
}
|
|
1114
1350
|
async runEventPump() {
|
|
1351
|
+
// Scope the pump to THIS VM's ownership so multiple proxies can share one
|
|
1352
|
+
// sidecar process: events for other VMs stay buffered for their own pumps
|
|
1353
|
+
// rather than being consumed (and dropped) here.
|
|
1354
|
+
const vmId = this.vm.vmId;
|
|
1115
1355
|
while (!this.disposed) {
|
|
1116
1356
|
try {
|
|
1117
|
-
const event = await this.client.waitForEvent(
|
|
1118
|
-
signal: this.eventPumpAbortController.signal,
|
|
1119
|
-
});
|
|
1357
|
+
const event = await this.client.waitForEvent((frame) => frame.ownership.scope === "vm" && frame.ownership.vm_id === vmId, undefined, { signal: this.eventPumpAbortController.signal });
|
|
1120
1358
|
if (event.payload.type === "process_output") {
|
|
1121
1359
|
const entry = this.trackedProcessesById.get(event.payload.process_id);
|
|
1122
1360
|
if (!entry) {
|
|
@@ -1143,8 +1381,11 @@ export class NativeSidecarKernelProxy {
|
|
|
1143
1381
|
continue;
|
|
1144
1382
|
}
|
|
1145
1383
|
void this.refreshProcessSnapshot().catch(() => { });
|
|
1146
|
-
this.signalRefreshes.delete(entry.pid);
|
|
1147
1384
|
this.finishProcess(entry, event.payload.exit_code);
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
if (event.payload.type === "structured") {
|
|
1388
|
+
logStructuredSidecarEvent(event.payload.name, event.payload.detail);
|
|
1148
1389
|
}
|
|
1149
1390
|
}
|
|
1150
1391
|
catch (error) {
|
|
@@ -1175,6 +1416,25 @@ export class NativeSidecarKernelProxy {
|
|
|
1175
1416
|
entry.exitTime = Date.now();
|
|
1176
1417
|
this.updateTrackedProcessSnapshot(entry);
|
|
1177
1418
|
entry.resolveWait(exitCode);
|
|
1419
|
+
// Release per-process tracking now that the process has terminated so these
|
|
1420
|
+
// maps/Sets don't grow without bound. Defer the release until trailing
|
|
1421
|
+
// output has drained: `wait()`'s drain and late `process_output` events
|
|
1422
|
+
// still need the entry + its listeners during the drain window. The exited
|
|
1423
|
+
// record lives on in `processes` for listing.
|
|
1424
|
+
void this.releaseProcessTrackingAfterDrain(entry);
|
|
1425
|
+
}
|
|
1426
|
+
async releaseProcessTrackingAfterDrain(entry) {
|
|
1427
|
+
try {
|
|
1428
|
+
await this.drainTrailingProcessOutput(entry);
|
|
1429
|
+
}
|
|
1430
|
+
finally {
|
|
1431
|
+
this.trackedProcesses.delete(entry.pid);
|
|
1432
|
+
this.trackedProcessesById.delete(entry.processId);
|
|
1433
|
+
this.signalRefreshes.delete(entry.pid);
|
|
1434
|
+
this.signalStates.delete(entry.pid);
|
|
1435
|
+
entry.onStdout.clear();
|
|
1436
|
+
entry.onStderr.clear();
|
|
1437
|
+
}
|
|
1178
1438
|
}
|
|
1179
1439
|
waitForTrackedProcess(entry) {
|
|
1180
1440
|
if (entry.exitCode !== null) {
|
|
@@ -1325,6 +1585,74 @@ export class NativeSidecarKernelProxy {
|
|
|
1325
1585
|
handler(stderr);
|
|
1326
1586
|
}
|
|
1327
1587
|
}
|
|
1588
|
+
async readdirRecursiveLocal(fs, path, maxDepth) {
|
|
1589
|
+
const results = [];
|
|
1590
|
+
const queue = [{ path, depth: 0 }];
|
|
1591
|
+
while (queue.length > 0) {
|
|
1592
|
+
const current = queue.shift();
|
|
1593
|
+
if (!current)
|
|
1594
|
+
break;
|
|
1595
|
+
for (const name of await fs.readDir(current.path)) {
|
|
1596
|
+
if (name === "." || name === "..")
|
|
1597
|
+
continue;
|
|
1598
|
+
const child = posixPath.join(current.path, name);
|
|
1599
|
+
const stat = await fs.lstat(child);
|
|
1600
|
+
results.push({
|
|
1601
|
+
name,
|
|
1602
|
+
path: child,
|
|
1603
|
+
isDirectory: stat.isDirectory,
|
|
1604
|
+
isSymbolicLink: stat.isSymbolicLink,
|
|
1605
|
+
size: stat.size,
|
|
1606
|
+
});
|
|
1607
|
+
if (stat.isDirectory &&
|
|
1608
|
+
!stat.isSymbolicLink &&
|
|
1609
|
+
(maxDepth === undefined || current.depth < maxDepth)) {
|
|
1610
|
+
queue.push({ path: child, depth: current.depth + 1 });
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
return results;
|
|
1615
|
+
}
|
|
1616
|
+
async removePathLocal(fs, path, recursive) {
|
|
1617
|
+
const stat = await fs.lstat(path);
|
|
1618
|
+
if (stat.isDirectory && !stat.isSymbolicLink) {
|
|
1619
|
+
if (recursive) {
|
|
1620
|
+
for (const name of await fs.readDir(path)) {
|
|
1621
|
+
if (name === "." || name === "..")
|
|
1622
|
+
continue;
|
|
1623
|
+
await this.removePathLocal(fs, posixPath.join(path, name), true);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return fs.removeDir(path);
|
|
1627
|
+
}
|
|
1628
|
+
return fs.removeFile(path);
|
|
1629
|
+
}
|
|
1630
|
+
async copyPathLocal(fs, fromPath, toPath, recursive) {
|
|
1631
|
+
const stat = await fs.lstat(fromPath);
|
|
1632
|
+
if (stat.isSymbolicLink) {
|
|
1633
|
+
return fs.symlink(await fs.readlink(fromPath), toPath);
|
|
1634
|
+
}
|
|
1635
|
+
if (stat.isDirectory) {
|
|
1636
|
+
if (!recursive) {
|
|
1637
|
+
throw errnoError("EISDIR", "illegal operation on a directory");
|
|
1638
|
+
}
|
|
1639
|
+
await fs.mkdir(posixPath.dirname(toPath), { recursive: true });
|
|
1640
|
+
if (!(await fs.exists(toPath))) {
|
|
1641
|
+
await fs.createDir(toPath);
|
|
1642
|
+
}
|
|
1643
|
+
await fs.chmod(toPath, stat.mode);
|
|
1644
|
+
await fs.chown(toPath, stat.uid, stat.gid);
|
|
1645
|
+
for (const name of await fs.readDir(fromPath)) {
|
|
1646
|
+
if (name === "." || name === "..")
|
|
1647
|
+
continue;
|
|
1648
|
+
await this.copyPathLocal(fs, posixPath.join(fromPath, name), posixPath.join(toPath, name), true);
|
|
1649
|
+
}
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
await fs.writeFile(toPath, await fs.readFile(fromPath));
|
|
1653
|
+
await fs.chmod(toPath, stat.mode);
|
|
1654
|
+
await fs.chown(toPath, stat.uid, stat.gid);
|
|
1655
|
+
}
|
|
1328
1656
|
createFilesystemView(includeLocalMounts) {
|
|
1329
1657
|
return {
|
|
1330
1658
|
readFile: (path) => this.dispatchRead(path, (mount, relativePath) => mount.fs.readFile(relativePath), includeLocalMounts),
|
|
@@ -1535,7 +1863,8 @@ export class NativeSidecarKernelProxy {
|
|
|
1535
1863
|
}
|
|
1536
1864
|
return this.dispatchNativeRead(path);
|
|
1537
1865
|
}
|
|
1538
|
-
dispatchNativeRead(path) {
|
|
1866
|
+
async dispatchNativeRead(path) {
|
|
1867
|
+
await this.waitForMountReconfigure();
|
|
1539
1868
|
return this.client.readFile(this.session, this.vm, path);
|
|
1540
1869
|
}
|
|
1541
1870
|
async dispatchWrite(path, handler, nativeHandler, includeLocalMounts = true) {
|
|
@@ -1546,6 +1875,7 @@ export class NativeSidecarKernelProxy {
|
|
|
1546
1875
|
await handler(local.mount, local.relativePath);
|
|
1547
1876
|
return;
|
|
1548
1877
|
}
|
|
1878
|
+
await this.waitForMountReconfigure();
|
|
1549
1879
|
await nativeHandler();
|
|
1550
1880
|
}
|
|
1551
1881
|
resolveLocalMount(path) {
|
|
@@ -1621,6 +1951,10 @@ function buildCommandMap(commandGuestPaths) {
|
|
|
1621
1951
|
["node", "node"],
|
|
1622
1952
|
["npm", "node"],
|
|
1623
1953
|
["npx", "node"],
|
|
1954
|
+
// `python` / `python3` are served by the embedded Pyodide runtime,
|
|
1955
|
+
// mirroring how `node` is served by the embedded V8 runtime.
|
|
1956
|
+
["python", "python"],
|
|
1957
|
+
["python3", "python"],
|
|
1624
1958
|
]);
|
|
1625
1959
|
for (const name of commandGuestPaths.keys()) {
|
|
1626
1960
|
commands.set(name, "wasmvm");
|
|
@@ -1658,21 +1992,27 @@ function isMissingHostProcessError(error) {
|
|
|
1658
1992
|
function errnoError(code, message) {
|
|
1659
1993
|
return Object.assign(new Error(`${code}: ${message}`), { code });
|
|
1660
1994
|
}
|
|
1995
|
+
// VirtualStat is a numeric, Node-default-shaped view: u64 fields above
|
|
1996
|
+
// Number.MAX_SAFE_INTEGER lose precision here, same as Node's non-bigint
|
|
1997
|
+
// fs.stat on the host.
|
|
1661
1998
|
function toVirtualStat(stat) {
|
|
1662
1999
|
return {
|
|
1663
2000
|
mode: stat.mode,
|
|
1664
|
-
size: stat.size,
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
2001
|
+
size: Number(stat.size),
|
|
2002
|
+
sizeExact: stat.size,
|
|
2003
|
+
blocks: Number(stat.blocks),
|
|
2004
|
+
dev: Number(stat.dev),
|
|
2005
|
+
rdev: Number(stat.rdev),
|
|
1668
2006
|
isDirectory: stat.is_directory,
|
|
1669
2007
|
isSymbolicLink: stat.is_symbolic_link,
|
|
1670
2008
|
atimeMs: stat.atime_ms,
|
|
1671
2009
|
mtimeMs: stat.mtime_ms,
|
|
1672
2010
|
ctimeMs: stat.ctime_ms,
|
|
1673
2011
|
birthtimeMs: stat.birthtime_ms,
|
|
1674
|
-
ino: stat.ino,
|
|
1675
|
-
|
|
2012
|
+
ino: Number(stat.ino),
|
|
2013
|
+
inoExact: stat.ino,
|
|
2014
|
+
nlink: Number(stat.nlink),
|
|
2015
|
+
nlinkExact: stat.nlink,
|
|
1676
2016
|
uid: stat.uid,
|
|
1677
2017
|
gid: stat.gid,
|
|
1678
2018
|
};
|
|
@@ -1705,7 +2045,7 @@ function socketLookupKey(kind, request) {
|
|
|
1705
2045
|
path: request.path ?? null,
|
|
1706
2046
|
});
|
|
1707
2047
|
}
|
|
1708
|
-
export {
|
|
2048
|
+
export { NativeSidecarProcessClient, SidecarEventBufferOverflow, SidecarProcess, SidecarProcessError, SidecarProcessExited, } from "./native-process-client.js";
|
|
1709
2049
|
export class AgentOsSidecarVmHandle {
|
|
1710
2050
|
client;
|
|
1711
2051
|
sessionId;
|