mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-24 05:51:12 +00:00
refactor(openshell): trim internal exports (#107291)
This commit is contained in:
committed by
GitHub
parent
b58e0875ed
commit
2444cc2f37
@@ -96,12 +96,17 @@ describe("openshell backend exec workdir validation", () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
vi.unstubAllEnvs();
|
||||
await Promise.all(
|
||||
tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })),
|
||||
);
|
||||
});
|
||||
|
||||
it("reuses validation-time workspace preparation for the following exec", async () => {
|
||||
vi.stubEnv("OPENAI_API_KEY", "fixture");
|
||||
vi.stubEnv("ANTHROPIC_API_KEY", "fixture");
|
||||
vi.stubEnv("LANG", "en_US.UTF-8");
|
||||
vi.stubEnv("NODE_ENV", "test");
|
||||
const workspaceDir = await makeTempDir("openclaw-openshell-workspace-");
|
||||
await fs.writeFile(path.join(workspaceDir, "seed.txt"), "seed", "utf8");
|
||||
const backendFactory = createOpenShellSandboxBackendFactory({
|
||||
@@ -112,7 +117,7 @@ describe("openshell backend exec workdir validation", () => {
|
||||
});
|
||||
const backend = await backendFactory({
|
||||
sessionKey: "agent:main:turn",
|
||||
scopeKey: "agent:main",
|
||||
scopeKey: "agent:somalley_alice:dashboard-8",
|
||||
workspaceDir,
|
||||
agentWorkspaceDir: workspaceDir,
|
||||
cfg: createOpenShellBackendSandboxConfig(),
|
||||
@@ -130,6 +135,25 @@ describe("openshell backend exec workdir validation", () => {
|
||||
([params]) => params.args[0] === "sandbox" && params.args[1] === "upload",
|
||||
);
|
||||
expect(uploadCalls).toHaveLength(1);
|
||||
expect(uploadCalls[0]?.[0]).toMatchObject({
|
||||
args: [
|
||||
"sandbox",
|
||||
"upload",
|
||||
"--no-git-ignore",
|
||||
backend.runtimeId,
|
||||
expect.stringMatching(/\/seed\.txt$/),
|
||||
"/sandbox",
|
||||
],
|
||||
cwd: workspaceDir,
|
||||
});
|
||||
expect(backend.runtimeId).toMatch(/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/);
|
||||
expect(backend.runtimeId).toContain("somalley-alice");
|
||||
expect(backend.runtimeId).not.toContain("_");
|
||||
expect(backend.runtimeId.length).toBeLessThanOrEqual(63);
|
||||
expect(execSpec.env.OPENAI_API_KEY).toBeUndefined();
|
||||
expect(execSpec.env.ANTHROPIC_API_KEY).toBeUndefined();
|
||||
expect(execSpec.env.LANG).toBe("en_US.UTF-8");
|
||||
expect(execSpec.env.NODE_ENV).toBe("test");
|
||||
expect(execSpec.argv).toContain("openshell-test");
|
||||
});
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
// Openshell tests cover backend plugin behavior.
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { buildOpenShellSandboxName, buildOpenShellSshExecEnv } from "./backend.js";
|
||||
|
||||
describe("openshell backend env", () => {
|
||||
const originalEnv = { ...process.env };
|
||||
|
||||
afterEach(() => {
|
||||
for (const key of Object.keys(process.env)) {
|
||||
if (!(key in originalEnv)) {
|
||||
delete process.env[key];
|
||||
}
|
||||
}
|
||||
Object.assign(process.env, originalEnv);
|
||||
});
|
||||
|
||||
it("filters blocked secrets from ssh exec env", () => {
|
||||
process.env.OPENAI_API_KEY = "sk-test-secret";
|
||||
process.env.ANTHROPIC_API_KEY = "sk-ant-test-secret";
|
||||
process.env.LANG = "en_US.UTF-8";
|
||||
process.env.NODE_ENV = "test";
|
||||
|
||||
const env = buildOpenShellSshExecEnv();
|
||||
|
||||
expect(env.OPENAI_API_KEY).toBeUndefined();
|
||||
expect(env.ANTHROPIC_API_KEY).toBeUndefined();
|
||||
expect(env.LANG).toBe("en_US.UTF-8");
|
||||
expect(env.NODE_ENV).toBe("test");
|
||||
});
|
||||
});
|
||||
|
||||
describe("openshell sandbox names", () => {
|
||||
it("generates Kubernetes-safe names from OpenClaw session scope keys", () => {
|
||||
const name = buildOpenShellSandboxName("agent:somalley_alice:dashboard-8");
|
||||
|
||||
expect(name).toMatch(/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/);
|
||||
expect(name).toContain("somalley-alice");
|
||||
expect(name).not.toContain("_");
|
||||
expect(name.length).toBeLessThanOrEqual(63);
|
||||
});
|
||||
});
|
||||
@@ -46,7 +46,7 @@ type PendingExec = {
|
||||
};
|
||||
|
||||
const MATERIALIZED_SKILLS_REMOTE_PARTS = [".openclaw", "sandbox-skills"] as const;
|
||||
export function buildOpenShellDirectoryUploadArgs(params: {
|
||||
function buildOpenShellDirectoryUploadArgs(params: {
|
||||
sandboxName: string;
|
||||
localPath: string;
|
||||
remotePath: string;
|
||||
@@ -203,12 +203,10 @@ export const ENSURE_OPEN_SHELL_REMOTE_REAL_DIRECTORY_SCRIPT = [
|
||||
"done",
|
||||
].join("\n");
|
||||
|
||||
export function buildOpenShellSshExecEnv(): NodeJS.ProcessEnv {
|
||||
function buildOpenShellSshExecEnv(): NodeJS.ProcessEnv {
|
||||
return sanitizeEnvVars(process.env).allowed;
|
||||
}
|
||||
|
||||
export type { OpenShellSandboxBackend } from "./backend.types.js";
|
||||
|
||||
export function createOpenShellSandboxBackendFactory(
|
||||
params: CreateOpenShellSandboxBackendFactoryParams,
|
||||
): SandboxBackendFactory {
|
||||
@@ -893,7 +891,7 @@ function resolveOpenShellPluginConfigFromConfig(
|
||||
return resolveOpenShellPluginConfig(pluginConfig);
|
||||
}
|
||||
|
||||
export function buildOpenShellSandboxName(scopeKey: string): string {
|
||||
function buildOpenShellSandboxName(scopeKey: string): string {
|
||||
const trimmed = scopeKey.trim() || "session";
|
||||
const safe = normalizeLowercaseStringOrEmpty(trimmed)
|
||||
.replace(/[^a-z0-9-]+/g, "-")
|
||||
|
||||
@@ -8,10 +8,8 @@ import {
|
||||
import type { ResolvedOpenShellPluginConfig } from "./config.js";
|
||||
|
||||
export {
|
||||
buildExecRemoteCommand,
|
||||
buildRemoteWorkdirValidationCommand,
|
||||
buildValidatedExecRemoteCommand,
|
||||
shellEscape,
|
||||
} from "openclaw/plugin-sdk/sandbox";
|
||||
|
||||
export type OpenShellExecContext = {
|
||||
@@ -20,12 +18,8 @@ export type OpenShellExecContext = {
|
||||
timeoutMs?: number;
|
||||
};
|
||||
|
||||
export function resolveOpenShellCommand(command: string): string {
|
||||
return command;
|
||||
}
|
||||
|
||||
export function buildOpenShellBaseArgv(config: ResolvedOpenShellPluginConfig): string[] {
|
||||
const argv = [resolveOpenShellCommand(config.command)];
|
||||
function buildOpenShellBaseArgv(config: ResolvedOpenShellPluginConfig): string[] {
|
||||
const argv = [config.command];
|
||||
if (config.gateway) {
|
||||
argv.push("--gateway", config.gateway);
|
||||
}
|
||||
@@ -39,7 +33,7 @@ export function buildRemoteCommand(argv: string[]): string {
|
||||
return argv.map((entry) => shellEscape(entry)).join(" ");
|
||||
}
|
||||
|
||||
export function applyGatewayEndpointToSshConfig(params: {
|
||||
function applyGatewayEndpointToSshConfig(params: {
|
||||
configText: string;
|
||||
gatewayEndpoint?: string;
|
||||
}): string {
|
||||
|
||||
@@ -4,7 +4,12 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import type { CreateSandboxBackendParams } from "openclaw/plugin-sdk/sandbox";
|
||||
import {
|
||||
buildExecRemoteCommand,
|
||||
disposeSshSandboxSession,
|
||||
shellEscape,
|
||||
type CreateSandboxBackendParams,
|
||||
} from "openclaw/plugin-sdk/sandbox";
|
||||
import {
|
||||
createSandboxBrowserConfig,
|
||||
createSandboxPruneConfig,
|
||||
@@ -12,15 +17,11 @@ import {
|
||||
createSandboxTestContext,
|
||||
} from "openclaw/plugin-sdk/test-fixtures";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenShellSandboxBackend } from "./backend.js";
|
||||
import type { OpenShellSandboxBackend } from "./backend.types.js";
|
||||
import {
|
||||
applyGatewayEndpointToSshConfig,
|
||||
buildExecRemoteCommand,
|
||||
buildValidatedExecRemoteCommand,
|
||||
buildOpenShellBaseArgv,
|
||||
resolveOpenShellCommand,
|
||||
createOpenShellSshSession,
|
||||
runOpenShellCli,
|
||||
shellEscape,
|
||||
} from "./cli.js";
|
||||
import { resolveOpenShellPluginConfig } from "./config.js";
|
||||
|
||||
@@ -30,7 +31,6 @@ const cliMocks = vi.hoisted(() => ({
|
||||
|
||||
let createOpenShellSandboxBackendManager: typeof import("./backend.js").createOpenShellSandboxBackendManager;
|
||||
let createOpenShellSandboxBackendFactory: typeof import("./backend.js").createOpenShellSandboxBackendFactory;
|
||||
let buildOpenShellDirectoryUploadArgs: typeof import("./backend.js").buildOpenShellDirectoryUploadArgs;
|
||||
let ensureOpenShellRemoteRealDirectoryScript: typeof import("./backend.js").ENSURE_OPEN_SHELL_REMOTE_REAL_DIRECTORY_SCRIPT;
|
||||
|
||||
describe("openshell cli helpers", () => {
|
||||
@@ -45,34 +45,6 @@ describe("openshell cli helpers", () => {
|
||||
Object.assign(process.env, originalEnv);
|
||||
});
|
||||
|
||||
it("builds base argv with gateway overrides", () => {
|
||||
const config = resolveOpenShellPluginConfig({
|
||||
command: "/usr/local/bin/openshell",
|
||||
gateway: "lab",
|
||||
gatewayEndpoint: "https://lab.example",
|
||||
});
|
||||
expect(buildOpenShellBaseArgv(config)).toEqual([
|
||||
"/usr/local/bin/openshell",
|
||||
"--gateway",
|
||||
"lab",
|
||||
"--gateway-endpoint",
|
||||
"https://lab.example",
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses the configured NVIDIA OpenShell CLI command directly", () => {
|
||||
const config = resolveOpenShellPluginConfig(undefined);
|
||||
|
||||
expect(resolveOpenShellCommand("openshell")).toBe("openshell");
|
||||
expect(buildOpenShellBaseArgv(config)).toEqual(["openshell"]);
|
||||
});
|
||||
|
||||
it("preserves an explicit NVIDIA OpenShell CLI path", () => {
|
||||
expect(resolveOpenShellCommand("/opt/openshell/bin/openshell")).toBe(
|
||||
"/opt/openshell/bin/openshell",
|
||||
);
|
||||
});
|
||||
|
||||
it("shell escapes single quotes", () => {
|
||||
expect(shellEscape(`a'b`)).toBe(`'a'"'"'b'`);
|
||||
});
|
||||
@@ -133,35 +105,43 @@ describe("openshell cli helpers", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("adds direct gateway endpoints to generated ssh proxy configs", () => {
|
||||
const configText = [
|
||||
"Host openshell-demo",
|
||||
" User sandbox",
|
||||
" ProxyCommand /usr/local/bin/openshell ssh-proxy --gateway-name alice --name demo",
|
||||
"",
|
||||
].join("\n");
|
||||
it.runIf(process.platform !== "win32")(
|
||||
"adds direct gateway endpoints to generated ssh proxy configs",
|
||||
async () => {
|
||||
const configText = [
|
||||
"Host openshell-demo",
|
||||
" User sandbox",
|
||||
" ProxyCommand /usr/local/bin/openshell ssh-proxy --gateway-name alice --name demo",
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
expect(
|
||||
applyGatewayEndpointToSshConfig({
|
||||
configText,
|
||||
gatewayEndpoint: "http://openshell.openshell-alice.svc.cluster.local:8080",
|
||||
}),
|
||||
).toContain(
|
||||
"ProxyCommand /usr/local/bin/openshell ssh-proxy --gateway-name alice --name demo --server 'http://openshell.openshell-alice.svc.cluster.local:8080'",
|
||||
);
|
||||
});
|
||||
await expect(
|
||||
readOpenShellSshConfig({
|
||||
configText,
|
||||
gatewayEndpoint: "http://openshell.openshell-alice.svc.cluster.local:8080",
|
||||
}),
|
||||
).resolves.toContain(
|
||||
"ProxyCommand /usr/local/bin/openshell ssh-proxy --gateway-name alice --name demo --server 'http://openshell.openshell-alice.svc.cluster.local:8080'",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("leaves ssh proxy configs with an explicit endpoint unchanged", () => {
|
||||
const configText =
|
||||
"Host openshell-demo\n ProxyCommand openshell ssh-proxy --gateway-name alice --name demo --server 'http://existing'\n";
|
||||
it.runIf(process.platform !== "win32")(
|
||||
"leaves ssh proxy configs with an explicit endpoint unchanged",
|
||||
async () => {
|
||||
const configText =
|
||||
"Host openshell-demo\n ProxyCommand openshell ssh-proxy --gateway-name alice --name demo --server 'http://existing'\n";
|
||||
|
||||
expect(
|
||||
applyGatewayEndpointToSshConfig({
|
||||
configText,
|
||||
gatewayEndpoint: "http://replacement",
|
||||
}),
|
||||
).toBe(configText);
|
||||
});
|
||||
await expect(
|
||||
readOpenShellSshConfig({
|
||||
configText,
|
||||
gatewayEndpoint: "http://replacement",
|
||||
}),
|
||||
).resolves.toContain(
|
||||
"ProxyCommand openshell ssh-proxy --gateway-name alice --name demo --server 'http://existing'",
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("openshell backend manager", () => {
|
||||
@@ -174,7 +154,6 @@ describe("openshell backend manager", () => {
|
||||
};
|
||||
});
|
||||
({
|
||||
buildOpenShellDirectoryUploadArgs,
|
||||
ENSURE_OPEN_SHELL_REMOTE_REAL_DIRECTORY_SCRIPT: ensureOpenShellRemoteRealDirectoryScript,
|
||||
createOpenShellSandboxBackendFactory,
|
||||
createOpenShellSandboxBackendManager,
|
||||
@@ -190,30 +169,6 @@ describe("openshell backend manager", () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("uploads staged directory snapshots to the managed remote directory itself", () => {
|
||||
expect(
|
||||
buildOpenShellDirectoryUploadArgs({
|
||||
sandboxName: "openclaw-session",
|
||||
localPath: "/tmp/openclaw-upload/sandbox/seed.txt",
|
||||
remotePath: "/sandbox",
|
||||
}),
|
||||
).toEqual([
|
||||
"sandbox",
|
||||
"upload",
|
||||
"--no-git-ignore",
|
||||
"openclaw-session",
|
||||
"/tmp/openclaw-upload/sandbox/seed.txt",
|
||||
"/sandbox",
|
||||
]);
|
||||
expect(
|
||||
buildOpenShellDirectoryUploadArgs({
|
||||
sandboxName: "openclaw-session",
|
||||
localPath: "/tmp/openclaw-upload/project",
|
||||
remotePath: "/sandbox/./project",
|
||||
}).at(-1),
|
||||
).toBe("/sandbox/project");
|
||||
});
|
||||
|
||||
it.runIf(process.platform !== "win32")(
|
||||
"preserves caller positional args after OpenShell remote directory validation",
|
||||
async () => {
|
||||
@@ -560,6 +515,35 @@ async function makeExecutable(params: { name: string; script: string }): Promise
|
||||
return file;
|
||||
}
|
||||
|
||||
async function readOpenShellSshConfig(params: {
|
||||
configText: string;
|
||||
gatewayEndpoint: string;
|
||||
}): Promise<string> {
|
||||
const command = await makeExecutable({
|
||||
name: "openshell-ssh-config",
|
||||
script: [
|
||||
"#!/bin/sh",
|
||||
"cat <<'OPENCLAW_SSH_CONFIG'",
|
||||
params.configText,
|
||||
"OPENCLAW_SSH_CONFIG",
|
||||
].join("\n"),
|
||||
});
|
||||
const session = await createOpenShellSshSession({
|
||||
context: {
|
||||
sandboxName: "demo",
|
||||
config: resolveOpenShellPluginConfig({
|
||||
command,
|
||||
gatewayEndpoint: params.gatewayEndpoint,
|
||||
}),
|
||||
},
|
||||
});
|
||||
try {
|
||||
return await fs.readFile(session.configPath, "utf8");
|
||||
} finally {
|
||||
await disposeSshSandboxSession(session);
|
||||
}
|
||||
}
|
||||
|
||||
async function expectPathMissing(targetPath: string): Promise<void> {
|
||||
let error: unknown;
|
||||
try {
|
||||
|
||||
@@ -178,17 +178,8 @@ export const KNIP_UNUSED_EXPORT_BASELINE = [
|
||||
"extensions/ollama/src/wsl2-crash-loop-check.ts: hasWslCuda",
|
||||
"extensions/ollama/src/wsl2-crash-loop-check.ts: isOllamaEnabledWithRestartAlways",
|
||||
"extensions/ollama/src/wsl2-crash-loop-check.ts: parseSystemctlShowProperties",
|
||||
"extensions/openshell/src/backend.ts: buildOpenShellDirectoryUploadArgs",
|
||||
"extensions/openshell/src/backend.ts: buildOpenShellSandboxName",
|
||||
"extensions/openshell/src/backend.ts: buildOpenShellSshExecEnv",
|
||||
"extensions/openshell/src/backend.ts: ENSURE_OPEN_SHELL_REMOTE_REAL_DIRECTORY_SCRIPT",
|
||||
"extensions/openshell/src/backend.ts: OpenShellSandboxBackend",
|
||||
"extensions/openshell/src/backend.ts: PINNED_REMOTE_PATH_MUTATION_SCRIPT",
|
||||
"extensions/openshell/src/cli.ts: applyGatewayEndpointToSshConfig",
|
||||
"extensions/openshell/src/cli.ts: buildExecRemoteCommand",
|
||||
"extensions/openshell/src/cli.ts: buildOpenShellBaseArgv",
|
||||
"extensions/openshell/src/cli.ts: resolveOpenShellCommand",
|
||||
"extensions/openshell/src/cli.ts: shellEscape",
|
||||
"extensions/parallel/src/parallel-mcp-search.runtime.ts: extractMcpToolPayload",
|
||||
"extensions/parallel/src/parallel-mcp-search.runtime.ts: iterMcpMessages",
|
||||
"extensions/parallel/src/parallel-mcp-search.runtime.ts: selectMcpEnvelope",
|
||||
|
||||
Reference in New Issue
Block a user