@openclaw/openshell-sandbox 2026.7.1-beta.2 → 2026.7.1-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +39 -39
- package/npm-shrinkwrap.json +2 -2
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
2
|
import { buildRemoteWorkdirValidationCommand, buildValidatedExecRemoteCommand, createRemoteShellSandboxFsBridge, createSshSandboxSessionFromConfigText, createWritableRenameTargetResolver, disposeSshSandboxSession, registerSandboxBackend, resolvePreferredOpenClawTmpDir, runPluginCommandWithTimeout, runSshSandboxCommand, sanitizeEnvVars, shellEscape, withTempWorkspace } from "openclaw/plugin-sdk/sandbox";
|
|
3
|
-
import
|
|
3
|
+
import fsPromises from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
6
6
|
import { buildPluginConfigSchema } from "openclaw/plugin-sdk/core";
|
|
@@ -283,7 +283,7 @@ var OpenShellFsBridge = class {
|
|
|
283
283
|
async stat(params) {
|
|
284
284
|
const target = this.resolveTarget(params);
|
|
285
285
|
const hostPath = this.requireHostPath(target);
|
|
286
|
-
const stats = await
|
|
286
|
+
const stats = await fsPromises.lstat(hostPath).catch(() => null);
|
|
287
287
|
if (!stats) return null;
|
|
288
288
|
await assertLocalPathSafety({
|
|
289
289
|
target,
|
|
@@ -400,9 +400,9 @@ async function removeLocalRootPath(params) {
|
|
|
400
400
|
const root$1 = await root(params.target.mountHostRoot);
|
|
401
401
|
const relativePath = relativeToRoot(params.target, params.hostPath);
|
|
402
402
|
try {
|
|
403
|
-
if (params.force === false) await
|
|
403
|
+
if (params.force === false) await fsPromises.lstat(params.hostPath);
|
|
404
404
|
if (params.recursive) {
|
|
405
|
-
if ((await
|
|
405
|
+
if ((await fsPromises.lstat(params.hostPath).catch((err) => {
|
|
406
406
|
if (isNotFoundError(err)) return null;
|
|
407
407
|
throw err;
|
|
408
408
|
}))?.isSymbolicLink()) {
|
|
@@ -444,12 +444,12 @@ function relativeToRoot(target, hostPath) {
|
|
|
444
444
|
return relativePath === "." ? "" : relativePath;
|
|
445
445
|
}
|
|
446
446
|
async function assertRenameSourceSupported(fromHostPath) {
|
|
447
|
-
const stats = await
|
|
447
|
+
const stats = await fsPromises.lstat(fromHostPath);
|
|
448
448
|
if (stats.isSymbolicLink()) throw new Error("Sandbox symlink rename sources are not supported by the local mirror bridge");
|
|
449
449
|
if (stats.isFile() && stats.nlink > 1) throw new Error("Sandbox hardlinked rename sources are not supported by the local mirror bridge");
|
|
450
450
|
}
|
|
451
451
|
async function assertSameDeviceRenameSupported(params) {
|
|
452
|
-
const sourceStats = await
|
|
452
|
+
const sourceStats = await fsPromises.lstat(params.fromHostPath);
|
|
453
453
|
const destinationParentStats = await nearestExistingDirectoryStats({
|
|
454
454
|
root: params.root,
|
|
455
455
|
targetPath: path.dirname(params.toHostPath)
|
|
@@ -460,7 +460,7 @@ async function nearestExistingDirectoryStats(params) {
|
|
|
460
460
|
const rootPath = path.resolve(params.root);
|
|
461
461
|
let cursor = path.resolve(params.targetPath);
|
|
462
462
|
while (isPathInside(rootPath, cursor)) {
|
|
463
|
-
const stats = await
|
|
463
|
+
const stats = await fsPromises.lstat(cursor).catch((err) => {
|
|
464
464
|
if (isNotFoundError(err)) return null;
|
|
465
465
|
throw err;
|
|
466
466
|
});
|
|
@@ -472,7 +472,7 @@ async function nearestExistingDirectoryStats(params) {
|
|
|
472
472
|
if (next === cursor) break;
|
|
473
473
|
cursor = next;
|
|
474
474
|
}
|
|
475
|
-
return await
|
|
475
|
+
return await fsPromises.lstat(rootPath);
|
|
476
476
|
}
|
|
477
477
|
function isNotFoundError(err) {
|
|
478
478
|
return err instanceof FsSafeError && err.code === "not-found" || typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
@@ -511,15 +511,15 @@ function resolveProtectedSkillShadowTarget(params) {
|
|
|
511
511
|
}
|
|
512
512
|
async function assertLocalPathSafety(params) {
|
|
513
513
|
if (!params.target.hostPath) throw new Error(`Missing local host path for ${params.target.containerPath}`);
|
|
514
|
-
const canonicalRoot = await
|
|
515
|
-
const targetStats = await
|
|
514
|
+
const canonicalRoot = await fsPromises.realpath(params.root).catch(() => path.resolve(params.root));
|
|
515
|
+
const targetStats = await fsPromises.lstat(params.target.hostPath).catch(() => null);
|
|
516
516
|
if (!isPathInside(canonicalRoot, params.allowFinalSymlinkForUnlink && targetStats?.isSymbolicLink() ? path.resolve(canonicalRoot, path.relative(params.root, params.target.hostPath)) : await resolveCanonicalCandidate(params.target.hostPath))) throw new Error(`Sandbox path escapes allowed mounts; cannot access: ${params.target.containerPath}`);
|
|
517
517
|
const relative = path.relative(params.root, params.target.hostPath);
|
|
518
518
|
const segments = relative.split(path.sep).filter(Boolean).slice(0, Math.max(0, relative.split(path.sep).filter(Boolean).length));
|
|
519
519
|
let cursor = params.root;
|
|
520
520
|
for (let index = 0; index < segments.length; index += 1) {
|
|
521
521
|
cursor = path.join(cursor, segments[index]);
|
|
522
|
-
const stats = await
|
|
522
|
+
const stats = await fsPromises.lstat(cursor).catch(() => null);
|
|
523
523
|
if (!stats) {
|
|
524
524
|
if (index === segments.length - 1 && params.allowMissingLeaf) return;
|
|
525
525
|
continue;
|
|
@@ -532,8 +532,8 @@ async function resolveCanonicalCandidate(targetPath) {
|
|
|
532
532
|
const missing = [];
|
|
533
533
|
let cursor = path.resolve(targetPath);
|
|
534
534
|
while (true) {
|
|
535
|
-
if (await
|
|
536
|
-
const canonical = await
|
|
535
|
+
if (await fsPromises.lstat(cursor).then(() => true).catch(() => false)) {
|
|
536
|
+
const canonical = await fsPromises.realpath(cursor).catch(() => cursor);
|
|
537
537
|
return path.resolve(canonical, ...missing);
|
|
538
538
|
}
|
|
539
539
|
const parent = path.dirname(cursor);
|
|
@@ -575,16 +575,16 @@ function createConcurrencyLimiter(limit) {
|
|
|
575
575
|
}
|
|
576
576
|
const runLimitedFs = createConcurrencyLimiter(COPY_TREE_FS_CONCURRENCY);
|
|
577
577
|
async function lstatIfExists(targetPath) {
|
|
578
|
-
return await runLimitedFs(async () => await
|
|
578
|
+
return await runLimitedFs(async () => await fsPromises.lstat(targetPath)).catch(() => null);
|
|
579
579
|
}
|
|
580
580
|
async function copyTreeWithoutSymlinks(params) {
|
|
581
|
-
const stats = await runLimitedFs(async () => await
|
|
581
|
+
const stats = await runLimitedFs(async () => await fsPromises.lstat(params.sourcePath));
|
|
582
582
|
if (stats.isSymbolicLink()) return;
|
|
583
583
|
const targetStats = await lstatIfExists(params.targetPath);
|
|
584
584
|
if (params.preserveTargetSymlinks && targetStats?.isSymbolicLink()) return;
|
|
585
585
|
if (stats.isDirectory()) {
|
|
586
|
-
await runLimitedFs(async () => await
|
|
587
|
-
const entries = await runLimitedFs(async () => await
|
|
586
|
+
await runLimitedFs(async () => await fsPromises.mkdir(params.targetPath, { recursive: true }));
|
|
587
|
+
const entries = await runLimitedFs(async () => await fsPromises.readdir(params.sourcePath));
|
|
588
588
|
await Promise.all(entries.map(async (entry) => {
|
|
589
589
|
await copyTreeWithoutSymlinks({
|
|
590
590
|
sourcePath: path.join(params.sourcePath, entry),
|
|
@@ -595,23 +595,23 @@ async function copyTreeWithoutSymlinks(params) {
|
|
|
595
595
|
return;
|
|
596
596
|
}
|
|
597
597
|
if (stats.isFile()) {
|
|
598
|
-
await runLimitedFs(async () => await
|
|
599
|
-
await runLimitedFs(async () => await
|
|
598
|
+
await runLimitedFs(async () => await fsPromises.mkdir(path.dirname(params.targetPath), { recursive: true }));
|
|
599
|
+
await runLimitedFs(async () => await fsPromises.copyFile(params.sourcePath, params.targetPath));
|
|
600
600
|
}
|
|
601
601
|
}
|
|
602
602
|
async function replaceDirectoryContents(params) {
|
|
603
603
|
const isExcluded = createExcludeMatcher(params.excludeDirs);
|
|
604
|
-
await
|
|
605
|
-
const existing = await
|
|
604
|
+
await fsPromises.mkdir(params.targetDir, { recursive: true });
|
|
605
|
+
const existing = await fsPromises.readdir(params.targetDir);
|
|
606
606
|
await Promise.all(existing.filter((entry) => !isExcluded(entry)).map(async (entry) => {
|
|
607
607
|
const targetPath = path.join(params.targetDir, entry);
|
|
608
608
|
if ((await lstatIfExists(targetPath))?.isSymbolicLink()) return;
|
|
609
|
-
await runLimitedFs(async () => await
|
|
609
|
+
await runLimitedFs(async () => await fsPromises.rm(targetPath, {
|
|
610
610
|
recursive: true,
|
|
611
611
|
force: true
|
|
612
612
|
}));
|
|
613
613
|
}));
|
|
614
|
-
const sourceEntries = await
|
|
614
|
+
const sourceEntries = await fsPromises.readdir(params.sourceDir);
|
|
615
615
|
for (const entry of sourceEntries) {
|
|
616
616
|
if (isExcluded(entry)) continue;
|
|
617
617
|
await copyTreeWithoutSymlinks({
|
|
@@ -623,8 +623,8 @@ async function replaceDirectoryContents(params) {
|
|
|
623
623
|
}
|
|
624
624
|
async function stageDirectoryContents(params) {
|
|
625
625
|
const isExcluded = createExcludeMatcher(params.excludeDirs);
|
|
626
|
-
await
|
|
627
|
-
const sourceEntries = await
|
|
626
|
+
await fsPromises.mkdir(params.targetDir, { recursive: true });
|
|
627
|
+
const sourceEntries = await fsPromises.readdir(params.sourceDir);
|
|
628
628
|
for (const entry of sourceEntries) {
|
|
629
629
|
if (isExcluded(entry)) continue;
|
|
630
630
|
await copyTreeWithoutSymlinks({
|
|
@@ -1117,7 +1117,7 @@ var OpenShellSandboxBackendImpl = class {
|
|
|
1117
1117
|
await this.ensureSandboxExists();
|
|
1118
1118
|
await this.maybeSeedRemoteWorkspace();
|
|
1119
1119
|
const target = this.resolveRemoteTarget(remotePath);
|
|
1120
|
-
const stats = await
|
|
1120
|
+
const stats = await fsPromises.lstat(localPath).catch(() => null);
|
|
1121
1121
|
if (!stats) {
|
|
1122
1122
|
await this.runPinnedRemotePathMutation({ args: [
|
|
1123
1123
|
"remove",
|
|
@@ -1245,7 +1245,7 @@ var OpenShellSandboxBackendImpl = class {
|
|
|
1245
1245
|
script: `${ENSURE_OPEN_SHELL_REMOTE_REAL_DIRECTORY_SCRIPT}\nfind "$1" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +`,
|
|
1246
1246
|
args: [remoteSkillsWorkspaceDir, this.params.remoteWorkspaceDir]
|
|
1247
1247
|
});
|
|
1248
|
-
const stats = await
|
|
1248
|
+
const stats = await fsPromises.lstat(this.params.createParams.skillsWorkspaceDir).catch(() => null);
|
|
1249
1249
|
if (!stats?.isDirectory() || stats.isSymbolicLink()) return;
|
|
1250
1250
|
await this.uploadPathToRemote(this.params.createParams.skillsWorkspaceDir, remoteSkillsWorkspaceDir);
|
|
1251
1251
|
}
|
|
@@ -1296,7 +1296,7 @@ var OpenShellSandboxBackendImpl = class {
|
|
|
1296
1296
|
sourceDir: localPath,
|
|
1297
1297
|
targetDir: stagedRoot
|
|
1298
1298
|
});
|
|
1299
|
-
const stagedEntries = (await
|
|
1299
|
+
const stagedEntries = (await fsPromises.readdir(stagedRoot)).toSorted();
|
|
1300
1300
|
for (const entry of stagedEntries) {
|
|
1301
1301
|
const result = await runOpenShellCli({
|
|
1302
1302
|
context: this.params.execContext,
|
|
@@ -1342,17 +1342,17 @@ async function removeMaterializedSkillsFromDownloadedWorkspace(tmpDir) {
|
|
|
1342
1342
|
let cursor = tmpDir;
|
|
1343
1343
|
for (const [index, part] of MATERIALIZED_SKILLS_REMOTE_PARTS.entries()) {
|
|
1344
1344
|
const next = path.join(cursor, part);
|
|
1345
|
-
const stats = await
|
|
1345
|
+
const stats = await fsPromises.lstat(next).catch(() => null);
|
|
1346
1346
|
if (!stats) return;
|
|
1347
1347
|
if (index === MATERIALIZED_SKILLS_REMOTE_PARTS.length - 1) {
|
|
1348
|
-
await
|
|
1348
|
+
await fsPromises.rm(next, {
|
|
1349
1349
|
recursive: true,
|
|
1350
1350
|
force: true
|
|
1351
1351
|
});
|
|
1352
1352
|
return;
|
|
1353
1353
|
}
|
|
1354
1354
|
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
1355
|
-
await
|
|
1355
|
+
await fsPromises.rm(next, {
|
|
1356
1356
|
recursive: true,
|
|
1357
1357
|
force: true
|
|
1358
1358
|
});
|
|
@@ -1363,11 +1363,11 @@ async function removeMaterializedSkillsFromDownloadedWorkspace(tmpDir) {
|
|
|
1363
1363
|
}
|
|
1364
1364
|
async function moveMaterializedSkillsShadowAside(params) {
|
|
1365
1365
|
const shadowPath = path.join(params.workspaceDir, ...MATERIALIZED_SKILLS_REMOTE_PARTS);
|
|
1366
|
-
const parentStats = await
|
|
1366
|
+
const parentStats = await fsPromises.lstat(path.dirname(shadowPath)).catch(() => null);
|
|
1367
1367
|
if (!parentStats?.isDirectory() || parentStats.isSymbolicLink()) return;
|
|
1368
|
-
const shadowStats = await
|
|
1368
|
+
const shadowStats = await fsPromises.lstat(shadowPath).catch(() => null);
|
|
1369
1369
|
if (!shadowStats || shadowStats.isSymbolicLink()) return;
|
|
1370
|
-
const preserveRoot = await
|
|
1370
|
+
const preserveRoot = await fsPromises.mkdtemp(path.join(path.dirname(params.tmpDir), "openclaw-openshell-preserve-"));
|
|
1371
1371
|
const preservedPath = path.join(preserveRoot, "sandbox-skills");
|
|
1372
1372
|
await movePathWithCopyFallback({
|
|
1373
1373
|
from: shadowPath,
|
|
@@ -1384,14 +1384,14 @@ async function restoreMaterializedSkillsShadow(params) {
|
|
|
1384
1384
|
try {
|
|
1385
1385
|
const shadowPath = path.join(params.workspaceDir, ...MATERIALIZED_SKILLS_REMOTE_PARTS);
|
|
1386
1386
|
const parentPath = path.dirname(shadowPath);
|
|
1387
|
-
const parentStats = await
|
|
1387
|
+
const parentStats = await fsPromises.lstat(parentPath).catch(() => null);
|
|
1388
1388
|
if (parentStats?.isSymbolicLink()) throw new Error(`Refusing to restore sandbox skills through symlink parent: ${parentPath}`);
|
|
1389
|
-
if (parentStats && !parentStats.isDirectory()) await
|
|
1389
|
+
if (parentStats && !parentStats.isDirectory()) await fsPromises.rm(parentPath, {
|
|
1390
1390
|
recursive: true,
|
|
1391
1391
|
force: true
|
|
1392
1392
|
});
|
|
1393
|
-
await
|
|
1394
|
-
await
|
|
1393
|
+
await fsPromises.mkdir(parentPath, { recursive: true });
|
|
1394
|
+
await fsPromises.rm(shadowPath, {
|
|
1395
1395
|
recursive: true,
|
|
1396
1396
|
force: true
|
|
1397
1397
|
});
|
|
@@ -1401,7 +1401,7 @@ async function restoreMaterializedSkillsShadow(params) {
|
|
|
1401
1401
|
});
|
|
1402
1402
|
restored = true;
|
|
1403
1403
|
} finally {
|
|
1404
|
-
if (restored) await
|
|
1404
|
+
if (restored) await fsPromises.rm(params.preserved.preserveRoot, {
|
|
1405
1405
|
recursive: true,
|
|
1406
1406
|
force: true
|
|
1407
1407
|
});
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/openshell-sandbox",
|
|
3
|
-
"version": "2026.7.1-beta.
|
|
3
|
+
"version": "2026.7.1-beta.4",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@openclaw/openshell-sandbox",
|
|
9
|
-
"version": "2026.7.1-beta.
|
|
9
|
+
"version": "2026.7.1-beta.4",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"zod": "4.4.3"
|
|
12
12
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/openshell-sandbox",
|
|
3
|
-
"version": "2026.7.1-beta.
|
|
3
|
+
"version": "2026.7.1-beta.4",
|
|
4
4
|
"description": "OpenClaw sandbox backend for the NVIDIA OpenShell CLI with mirrored local workspaces and SSH command execution.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"minHostVersion": ">=2026.5.12-beta.1"
|
|
21
21
|
},
|
|
22
22
|
"compat": {
|
|
23
|
-
"pluginApi": ">=2026.7.1-beta.
|
|
23
|
+
"pluginApi": ">=2026.7.1-beta.4"
|
|
24
24
|
},
|
|
25
25
|
"build": {
|
|
26
|
-
"openclawVersion": "2026.7.1-beta.
|
|
26
|
+
"openclawVersion": "2026.7.1-beta.4",
|
|
27
27
|
"bundledDist": false
|
|
28
28
|
},
|
|
29
29
|
"release": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"README.md"
|
|
42
42
|
],
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"openclaw": ">=2026.7.1-beta.
|
|
44
|
+
"openclaw": ">=2026.7.1-beta.4"
|
|
45
45
|
},
|
|
46
46
|
"peerDependenciesMeta": {
|
|
47
47
|
"openclaw": {
|