mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-11 03:54:32 +00:00
refactor(lobster): remove lobsterPath overrides
This commit is contained in:
@@ -66,8 +66,6 @@ function setProcessPlatform(platform: NodeJS.Platform) {
|
||||
|
||||
describe("lobster plugin tool", () => {
|
||||
let tempDir = "";
|
||||
let lobsterBinPath = "";
|
||||
let lobsterExePath = "";
|
||||
const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform");
|
||||
const originalPath = process.env.PATH;
|
||||
const originalPathAlt = process.env.Path;
|
||||
@@ -78,10 +76,6 @@ describe("lobster plugin tool", () => {
|
||||
({ createLobsterTool } = await import("./lobster-tool.js"));
|
||||
|
||||
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-lobster-plugin-"));
|
||||
lobsterBinPath = path.join(tempDir, process.platform === "win32" ? "lobster.cmd" : "lobster");
|
||||
lobsterExePath = path.join(tempDir, "lobster.exe");
|
||||
await fs.writeFile(lobsterBinPath, "", { encoding: "utf8", mode: 0o755 });
|
||||
await fs.writeFile(lobsterExePath, "", { encoding: "utf8", mode: 0o755 });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -151,6 +145,28 @@ describe("lobster plugin tool", () => {
|
||||
});
|
||||
});
|
||||
|
||||
const queueSuccessfulEnvelope = (hello = "world") => {
|
||||
spawnState.queue.push({
|
||||
stdout: JSON.stringify({
|
||||
ok: true,
|
||||
status: "ok",
|
||||
output: [{ hello }],
|
||||
requiresApproval: null,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
const createWindowsShimFixture = async (params: {
|
||||
shimPath: string;
|
||||
scriptPath: string;
|
||||
scriptToken: string;
|
||||
}) => {
|
||||
await fs.mkdir(path.dirname(params.scriptPath), { recursive: true });
|
||||
await fs.mkdir(path.dirname(params.shimPath), { recursive: true });
|
||||
await fs.writeFile(params.scriptPath, "module.exports = {};\n", "utf8");
|
||||
await fs.writeFile(params.shimPath, `@echo off\r\n"${params.scriptToken}" %*\r\n`, "utf8");
|
||||
};
|
||||
|
||||
it("runs lobster and returns parsed envelope in details", async () => {
|
||||
spawnState.queue.push({
|
||||
stdout: JSON.stringify({
|
||||
@@ -188,26 +204,43 @@ describe("lobster plugin tool", () => {
|
||||
expect(res.details).toMatchObject({ ok: true, status: "ok" });
|
||||
});
|
||||
|
||||
it("requires absolute lobsterPath when provided (even though it is ignored)", async () => {
|
||||
it("requires action", async () => {
|
||||
const tool = createLobsterTool(fakeApi());
|
||||
await expect(
|
||||
tool.execute("call2", {
|
||||
action: "run",
|
||||
pipeline: "noop",
|
||||
lobsterPath: "./lobster",
|
||||
}),
|
||||
).rejects.toThrow(/absolute path/);
|
||||
await expect(tool.execute("call-action-missing", {})).rejects.toThrow(/action required/);
|
||||
});
|
||||
|
||||
it("rejects lobsterPath (deprecated) when invalid", async () => {
|
||||
it("requires pipeline for run action", async () => {
|
||||
const tool = createLobsterTool(fakeApi());
|
||||
await expect(
|
||||
tool.execute("call2b", {
|
||||
tool.execute("call-pipeline-missing", {
|
||||
action: "run",
|
||||
pipeline: "noop",
|
||||
lobsterPath: "/bin/bash",
|
||||
}),
|
||||
).rejects.toThrow(/lobster executable/);
|
||||
).rejects.toThrow(/pipeline required/);
|
||||
});
|
||||
|
||||
it("requires token and approve for resume action", async () => {
|
||||
const tool = createLobsterTool(fakeApi());
|
||||
await expect(
|
||||
tool.execute("call-resume-token-missing", {
|
||||
action: "resume",
|
||||
approve: true,
|
||||
}),
|
||||
).rejects.toThrow(/token required/);
|
||||
await expect(
|
||||
tool.execute("call-resume-approve-missing", {
|
||||
action: "resume",
|
||||
token: "resume-token",
|
||||
}),
|
||||
).rejects.toThrow(/approve required/);
|
||||
});
|
||||
|
||||
it("rejects unknown action", async () => {
|
||||
const tool = createLobsterTool(fakeApi());
|
||||
await expect(
|
||||
tool.execute("call-action-unknown", {
|
||||
action: "explode",
|
||||
}),
|
||||
).rejects.toThrow(/Unknown action/);
|
||||
});
|
||||
|
||||
it("rejects absolute cwd", async () => {
|
||||
@@ -232,32 +265,6 @@ describe("lobster plugin tool", () => {
|
||||
).rejects.toThrow(/must stay within/);
|
||||
});
|
||||
|
||||
it("uses pluginConfig.lobsterPath when provided", async () => {
|
||||
spawnState.queue.push({
|
||||
stdout: JSON.stringify({
|
||||
ok: true,
|
||||
status: "ok",
|
||||
output: [{ hello: "world" }],
|
||||
requiresApproval: null,
|
||||
}),
|
||||
});
|
||||
|
||||
const configuredLobsterPath = process.platform === "win32" ? lobsterExePath : lobsterBinPath;
|
||||
const tool = createLobsterTool(
|
||||
fakeApi({ pluginConfig: { lobsterPath: configuredLobsterPath } }),
|
||||
);
|
||||
const res = await tool.execute("call-plugin-config", {
|
||||
action: "run",
|
||||
pipeline: "noop",
|
||||
timeoutMs: 1000,
|
||||
});
|
||||
|
||||
expect(spawnState.spawn).toHaveBeenCalled();
|
||||
const [execPath] = spawnState.spawn.mock.calls[0] ?? [];
|
||||
expect(execPath).toBe(configuredLobsterPath);
|
||||
expect(res.details).toMatchObject({ ok: true, status: "ok" });
|
||||
});
|
||||
|
||||
it("rejects invalid JSON from lobster", async () => {
|
||||
spawnState.queue.push({ stdout: "nope" });
|
||||
|
||||
@@ -273,25 +280,17 @@ describe("lobster plugin tool", () => {
|
||||
it("runs Windows cmd shims through Node without enabling shell", async () => {
|
||||
setProcessPlatform("win32");
|
||||
const shimScriptPath = path.join(tempDir, "shim-dist", "lobster-cli.cjs");
|
||||
const shimPath = path.join(tempDir, "shim", "lobster.cmd");
|
||||
await fs.mkdir(path.dirname(shimScriptPath), { recursive: true });
|
||||
await fs.mkdir(path.dirname(shimPath), { recursive: true });
|
||||
await fs.writeFile(shimScriptPath, "module.exports = {};\n", "utf8");
|
||||
await fs.writeFile(
|
||||
const shimPath = path.join(tempDir, "shim-bin", "lobster.cmd");
|
||||
await createWindowsShimFixture({
|
||||
shimPath,
|
||||
`@echo off\r\n"%dp0%\\..\\shim-dist\\lobster-cli.cjs" %*\r\n`,
|
||||
"utf8",
|
||||
);
|
||||
spawnState.queue.push({
|
||||
stdout: JSON.stringify({
|
||||
ok: true,
|
||||
status: "ok",
|
||||
output: [{ hello: "world" }],
|
||||
requiresApproval: null,
|
||||
}),
|
||||
scriptPath: shimScriptPath,
|
||||
scriptToken: "%dp0%\\..\\shim-dist\\lobster-cli.cjs",
|
||||
});
|
||||
process.env.PATHEXT = ".CMD;.EXE";
|
||||
process.env.PATH = `${path.dirname(shimPath)};${process.env.PATH ?? ""}`;
|
||||
queueSuccessfulEnvelope();
|
||||
|
||||
const tool = createLobsterTool(fakeApi({ pluginConfig: { lobsterPath: shimPath } }));
|
||||
const tool = createLobsterTool(fakeApi());
|
||||
await tool.execute("call-win-shim", {
|
||||
action: "run",
|
||||
pipeline: "noop",
|
||||
@@ -304,127 +303,6 @@ describe("lobster plugin tool", () => {
|
||||
expect(options).not.toHaveProperty("shell");
|
||||
});
|
||||
|
||||
it("runs Windows cmd shims with rooted dp0 tokens through Node", async () => {
|
||||
setProcessPlatform("win32");
|
||||
const shimScriptPath = path.join(tempDir, "shim-dist", "lobster-cli.cjs");
|
||||
const shimPath = path.join(tempDir, "shim", "lobster.cmd");
|
||||
await fs.mkdir(path.dirname(shimScriptPath), { recursive: true });
|
||||
await fs.mkdir(path.dirname(shimPath), { recursive: true });
|
||||
await fs.writeFile(shimScriptPath, "module.exports = {};\n", "utf8");
|
||||
await fs.writeFile(
|
||||
shimPath,
|
||||
`@echo off\r\n"%dp0%\\..\\shim-dist\\lobster-cli.cjs" %*\r\n`,
|
||||
"utf8",
|
||||
);
|
||||
spawnState.queue.push({
|
||||
stdout: JSON.stringify({
|
||||
ok: true,
|
||||
status: "ok",
|
||||
output: [{ hello: "rooted" }],
|
||||
requiresApproval: null,
|
||||
}),
|
||||
});
|
||||
|
||||
const tool = createLobsterTool(fakeApi({ pluginConfig: { lobsterPath: shimPath } }));
|
||||
await tool.execute("call-win-rooted-shim", {
|
||||
action: "run",
|
||||
pipeline: "noop",
|
||||
});
|
||||
|
||||
const [command, argv] = spawnState.spawn.mock.calls[0] ?? [];
|
||||
expect(command).toBe(process.execPath);
|
||||
expect(argv).toEqual([shimScriptPath, "run", "--mode", "tool", "noop"]);
|
||||
});
|
||||
|
||||
it("ignores node.exe shim entries and resolves the actual lobster script", async () => {
|
||||
setProcessPlatform("win32");
|
||||
const shimDir = path.join(tempDir, "shim-with-node");
|
||||
const nodeExePath = path.join(shimDir, "node.exe");
|
||||
const scriptPath = path.join(tempDir, "shim-dist-node", "lobster-cli.cjs");
|
||||
const shimPath = path.join(shimDir, "lobster.cmd");
|
||||
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
|
||||
await fs.mkdir(shimDir, { recursive: true });
|
||||
await fs.writeFile(nodeExePath, "", "utf8");
|
||||
await fs.writeFile(scriptPath, "module.exports = {};\n", "utf8");
|
||||
await fs.writeFile(
|
||||
shimPath,
|
||||
`@echo off\r\n"%~dp0%\\node.exe" "%~dp0%\\..\\shim-dist-node\\lobster-cli.cjs" %*\r\n`,
|
||||
"utf8",
|
||||
);
|
||||
spawnState.queue.push({
|
||||
stdout: JSON.stringify({
|
||||
ok: true,
|
||||
status: "ok",
|
||||
output: [{ hello: "node-first" }],
|
||||
requiresApproval: null,
|
||||
}),
|
||||
});
|
||||
|
||||
const tool = createLobsterTool(fakeApi({ pluginConfig: { lobsterPath: shimPath } }));
|
||||
await tool.execute("call-win-node-first", {
|
||||
action: "run",
|
||||
pipeline: "noop",
|
||||
});
|
||||
|
||||
const [command, argv] = spawnState.spawn.mock.calls[0] ?? [];
|
||||
expect(command).toBe(process.execPath);
|
||||
expect(argv).toEqual([scriptPath, "run", "--mode", "tool", "noop"]);
|
||||
});
|
||||
|
||||
it("resolves lobster.cmd from PATH and unwraps npm layout shim", async () => {
|
||||
setProcessPlatform("win32");
|
||||
const binDir = path.join(tempDir, "node_modules", ".bin");
|
||||
const packageDir = path.join(tempDir, "node_modules", "lobster");
|
||||
const scriptPath = path.join(packageDir, "dist", "cli.js");
|
||||
const shimPath = path.join(binDir, "lobster.cmd");
|
||||
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
|
||||
await fs.mkdir(binDir, { recursive: true });
|
||||
await fs.writeFile(shimPath, "@echo off\r\n", "utf8");
|
||||
await fs.writeFile(
|
||||
path.join(packageDir, "package.json"),
|
||||
JSON.stringify({ name: "lobster", version: "0.0.0", bin: { lobster: "dist/cli.js" } }),
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(scriptPath, "module.exports = {};\n", "utf8");
|
||||
process.env.PATHEXT = ".CMD;.EXE";
|
||||
process.env.PATH = `${binDir};${process.env.PATH ?? ""}`;
|
||||
|
||||
spawnState.queue.push({
|
||||
stdout: JSON.stringify({
|
||||
ok: true,
|
||||
status: "ok",
|
||||
output: [{ hello: "path" }],
|
||||
requiresApproval: null,
|
||||
}),
|
||||
});
|
||||
|
||||
const tool = createLobsterTool(fakeApi());
|
||||
await tool.execute("call-win-path", {
|
||||
action: "run",
|
||||
pipeline: "noop",
|
||||
});
|
||||
|
||||
const [command, argv] = spawnState.spawn.mock.calls[0] ?? [];
|
||||
expect(command).toBe(process.execPath);
|
||||
expect(argv).toEqual([scriptPath, "run", "--mode", "tool", "noop"]);
|
||||
});
|
||||
|
||||
it("fails fast when cmd wrapper cannot be resolved without shell execution", async () => {
|
||||
setProcessPlatform("win32");
|
||||
const badShimPath = path.join(tempDir, "bad-shim", "lobster.cmd");
|
||||
await fs.mkdir(path.dirname(badShimPath), { recursive: true });
|
||||
await fs.writeFile(badShimPath, "@echo off\r\nREM no entrypoint\r\n", "utf8");
|
||||
|
||||
const tool = createLobsterTool(fakeApi({ pluginConfig: { lobsterPath: badShimPath } }));
|
||||
await expect(
|
||||
tool.execute("call-win-bad", {
|
||||
action: "run",
|
||||
pipeline: "noop",
|
||||
}),
|
||||
).rejects.toThrow(/without shell execution/);
|
||||
expect(spawnState.spawn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not retry a failed Windows spawn with shell fallback", async () => {
|
||||
setProcessPlatform("win32");
|
||||
spawnState.spawn.mockReset();
|
||||
@@ -442,7 +320,7 @@ describe("lobster plugin tool", () => {
|
||||
return child;
|
||||
});
|
||||
|
||||
const tool = createLobsterTool(fakeApi({ pluginConfig: { lobsterPath: lobsterExePath } }));
|
||||
const tool = createLobsterTool(fakeApi());
|
||||
await expect(
|
||||
tool.execute("call-win-no-retry", {
|
||||
action: "run",
|
||||
|
||||
Reference in New Issue
Block a user