
























@@ -5,10 +5,11 @@ import { createSandboxTestContext } from "openclaw/plugin-sdk/test-fixtures";
55import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
66import type { OpenShellSandboxBackend } from "./backend.js";
77import {
8+applyGatewayEndpointToSshConfig,
89buildExecRemoteCommand,
910buildOpenShellBaseArgv,
1011resolveOpenShellCommand,
11-setBundledOpenShellCommandResolverForTest,
12+runOpenShellCli,
1213shellEscape,
1314} from "./cli.js";
1415import { resolveOpenShellPluginConfig } from "./config.js";
@@ -20,8 +21,15 @@ const cliMocks = vi.hoisted(() => ({
2021let createOpenShellSandboxBackendManager: typeof import("./backend.js").createOpenShellSandboxBackendManager;
21222223describe("openshell cli helpers", () => {
24+const originalEnv = { ...process.env };
25+2326afterEach(() => {
24-setBundledOpenShellCommandResolverForTest();
27+for (const key of Object.keys(process.env)) {
28+if (!(key in originalEnv)) {
29+delete process.env[key];
30+}
31+}
32+Object.assign(process.env, originalEnv);
2533});
26342735it("builds base argv with gateway overrides", () => {
@@ -39,18 +47,17 @@ describe("openshell cli helpers", () => {
3947]);
4048});
414942-it("prefers the bundled openshell command when available", () => {
43-setBundledOpenShellCommandResolverForTest(() => "/tmp/node_modules/.bin/openshell");
50+it("uses the configured NVIDIA OpenShell CLI command directly", () => {
4451const config = resolveOpenShellPluginConfig(undefined);
455246-expect(resolveOpenShellCommand("openshell")).toBe("/tmp/node_modules/.bin/openshell");
47-expect(buildOpenShellBaseArgv(config)).toEqual(["/tmp/node_modules/.bin/openshell"]);
53+expect(resolveOpenShellCommand("openshell")).toBe("openshell");
54+expect(buildOpenShellBaseArgv(config)).toEqual(["openshell"]);
4855});
495650-it("falls back to the PATH command when no bundled openshell is present", () => {
51-setBundledOpenShellCommandResolverForTest(() => null);
52-53-expect(resolveOpenShellCommand("openshell")).toBe("openshell");
57+it("preserves an explicit NVIDIA OpenShell CLI path", () => {
58+expect(resolveOpenShellCommand("/opt/openshell/bin/openshell")).toBe(
59+ "/opt/openshell/bin/openshell",
60+);
5461});
55625663it("shell escapes single quotes", () => {
@@ -69,6 +76,70 @@ describe("openshell cli helpers", () => {
6976expect(command).toContain(`'TOKEN=abc 123'`);
7077expect(command).toContain(`'cd '"'"'/sandbox/project'"'"' && pwd && printenv TOKEN'`);
7178});
79+80+it("passes direct gateway endpoints to openshell commands without registration", async () => {
81+const calls: string[][] = [];
82+const openshellCommand = await makeExecutable({
83+name: "openshell",
84+script: ["#!/bin/sh", `printf '%s\\n' "$*" >> "__LOG__"`, "exit 0"].join("\n"),
85+});
86+87+await runOpenShellCli({
88+context: {
89+sandboxName: "demo",
90+config: resolveOpenShellPluginConfig({
91+command: openshellCommand,
92+gateway: "alice",
93+gatewayEndpoint: "http://openshell.openshell-alice.svc.cluster.local:8080",
94+}),
95+},
96+args: ["sandbox", "get", "demo"],
97+});
98+99+const log = await fs.readFile(process.env.OPEN_SHELL_CLI_TEST_LOG as string, "utf8");
100+for (const line of log.trim().split("\n")) {
101+calls.push(line.split(" "));
102+}
103+expect(calls[0]).toEqual([
104+"--gateway",
105+"alice",
106+"--gateway-endpoint",
107+"http://openshell.openshell-alice.svc.cluster.local:8080",
108+"sandbox",
109+"get",
110+"demo",
111+]);
112+});
113+114+it("adds direct gateway endpoints to generated ssh proxy configs", () => {
115+const configText = [
116+"Host openshell-demo",
117+" User sandbox",
118+" ProxyCommand /usr/local/bin/openshell ssh-proxy --gateway-name alice --name demo",
119+"",
120+].join("\n");
121+122+expect(
123+applyGatewayEndpointToSshConfig({
124+ configText,
125+gatewayEndpoint: "http://openshell.openshell-alice.svc.cluster.local:8080",
126+}),
127+).toContain(
128+"ProxyCommand /usr/local/bin/openshell ssh-proxy --gateway-name alice --name demo --server 'http://openshell.openshell-alice.svc.cluster.local:8080'",
129+);
130+});
131+132+it("leaves ssh proxy configs with an explicit endpoint unchanged", () => {
133+const configText =
134+"Host openshell-demo\n ProxyCommand openshell ssh-proxy --gateway-name alice --name demo --server 'http://existing'\n";
135+136+expect(
137+applyGatewayEndpointToSshConfig({
138+ configText,
139+gatewayEndpoint: "http://replacement",
140+}),
141+).toBe(configText);
142+});
72143});
7314474145describe("openshell backend manager", () => {
@@ -200,6 +271,16 @@ async function makeTempDir(prefix: string) {
200271return dir;
201272}
202273274+async function makeExecutable(params: { name: string; script: string }): Promise<string> {
275+const dir = await makeTempDir("openclaw-openshell-bin-");
276+const file = path.join(dir, params.name);
277+const logPath = path.join(dir, "openshell.log");
278+await fs.writeFile(file, params.script.replaceAll("__LOG__", logPath), { mode: 0o755 });
279+await fs.chmod(file, 0o755);
280+process.env.OPEN_SHELL_CLI_TEST_LOG = logPath;
281+return file;
282+}
283+203284async function expectPathMissing(targetPath: string): Promise<void> {
204285let error: unknown;
205286try {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。