test: add workspace path regressions
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
- Models/Auth: allow MiniMax API configs without `models.providers.minimax.apiKey` (auth profiles / `MINIMAX_API_KEY`). (#656) — thanks @mneves75.
|
||||
- Agents: avoid duplicate replies when the message tool sends. (#659) — thanks @mickahouan.
|
||||
- Agents/Tools: resolve workspace-relative Read/Write/Edit paths; align bash default cwd. (#642) — thanks @mukhtharcm.
|
||||
- Tests/Agents: add regression coverage for workspace tool path resolution and bash cwd defaults.
|
||||
- iOS/Android: enable stricter concurrency/lint checks; fix Swift 6 strict concurrency issues + Android lint errors (ExifInterface, obsolete SDK check). (#662) — thanks @KristijanJovanovski.
|
||||
- iOS/macOS: share `AsyncTimeout`, require explicit `bridgeStableID` on connect, and harden tool display defaults (avoids missing-resource label fallbacks).
|
||||
- Docs: showcase entries for ParentPay, R2 Upload, iOS TestFlight, and Oura Health. (#650) — thanks @henrino3.
|
||||
|
||||
229
src/agents/pi-tools.workspace-paths.test.ts
Normal file
229
src/agents/pi-tools.workspace-paths.test.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createClawdbotCodingTools } from "./pi-tools.js";
|
||||
|
||||
const normalizeText = (value?: string) =>
|
||||
(value ?? "").replace(/\r\n/g, "\n").trim();
|
||||
|
||||
async function withTempDir<T>(prefix: string, fn: (dir: string) => Promise<T>) {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
||||
try {
|
||||
return await fn(dir);
|
||||
} finally {
|
||||
await fs.rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function getTextContent(result?: {
|
||||
content?: Array<{ type: string; text?: string }>;
|
||||
}) {
|
||||
const textBlock = result?.content?.find((block) => block.type === "text");
|
||||
return textBlock?.text ?? "";
|
||||
}
|
||||
|
||||
describe("workspace path resolution", () => {
|
||||
it("reads relative paths against workspaceDir even after cwd changes", async () => {
|
||||
await withTempDir("clawdbot-ws-", async (workspaceDir) => {
|
||||
await withTempDir("clawdbot-cwd-", async (otherDir) => {
|
||||
const prevCwd = process.cwd();
|
||||
const testFile = "read.txt";
|
||||
const contents = "workspace read ok";
|
||||
await fs.writeFile(path.join(workspaceDir, testFile), contents, "utf8");
|
||||
|
||||
process.chdir(otherDir);
|
||||
try {
|
||||
const tools = createClawdbotCodingTools({ workspaceDir });
|
||||
const readTool = tools.find((tool) => tool.name === "read");
|
||||
expect(readTool).toBeDefined();
|
||||
|
||||
const result = await readTool?.execute("ws-read", { path: testFile });
|
||||
expect(getTextContent(result)).toContain(contents);
|
||||
} finally {
|
||||
process.chdir(prevCwd);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("writes relative paths against workspaceDir even after cwd changes", async () => {
|
||||
await withTempDir("clawdbot-ws-", async (workspaceDir) => {
|
||||
await withTempDir("clawdbot-cwd-", async (otherDir) => {
|
||||
const prevCwd = process.cwd();
|
||||
const testFile = "write.txt";
|
||||
const contents = "workspace write ok";
|
||||
|
||||
process.chdir(otherDir);
|
||||
try {
|
||||
const tools = createClawdbotCodingTools({ workspaceDir });
|
||||
const writeTool = tools.find((tool) => tool.name === "write");
|
||||
expect(writeTool).toBeDefined();
|
||||
|
||||
await writeTool?.execute("ws-write", {
|
||||
path: testFile,
|
||||
content: contents,
|
||||
});
|
||||
|
||||
const written = await fs.readFile(
|
||||
path.join(workspaceDir, testFile),
|
||||
"utf8",
|
||||
);
|
||||
expect(written).toBe(contents);
|
||||
} finally {
|
||||
process.chdir(prevCwd);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("edits relative paths against workspaceDir even after cwd changes", async () => {
|
||||
await withTempDir("clawdbot-ws-", async (workspaceDir) => {
|
||||
await withTempDir("clawdbot-cwd-", async (otherDir) => {
|
||||
const prevCwd = process.cwd();
|
||||
const testFile = "edit.txt";
|
||||
await fs.writeFile(
|
||||
path.join(workspaceDir, testFile),
|
||||
"hello world",
|
||||
"utf8",
|
||||
);
|
||||
|
||||
process.chdir(otherDir);
|
||||
try {
|
||||
const tools = createClawdbotCodingTools({ workspaceDir });
|
||||
const editTool = tools.find((tool) => tool.name === "edit");
|
||||
expect(editTool).toBeDefined();
|
||||
|
||||
await editTool?.execute("ws-edit", {
|
||||
path: testFile,
|
||||
oldText: "world",
|
||||
newText: "clawdbot",
|
||||
});
|
||||
|
||||
const updated = await fs.readFile(
|
||||
path.join(workspaceDir, testFile),
|
||||
"utf8",
|
||||
);
|
||||
expect(updated).toBe("hello clawdbot");
|
||||
} finally {
|
||||
process.chdir(prevCwd);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("defaults bash cwd to workspaceDir when workdir is omitted", async () => {
|
||||
await withTempDir("clawdbot-ws-", async (workspaceDir) => {
|
||||
const tools = createClawdbotCodingTools({ workspaceDir });
|
||||
const bashTool = tools.find((tool) => tool.name === "bash");
|
||||
expect(bashTool).toBeDefined();
|
||||
|
||||
const result = await bashTool?.execute("ws-bash", {
|
||||
command: 'node -e "console.log(process.cwd())"',
|
||||
});
|
||||
const output = normalizeText(getTextContent(result));
|
||||
const [resolvedOutput, resolvedWorkspace] = await Promise.all([
|
||||
fs.realpath(output),
|
||||
fs.realpath(workspaceDir),
|
||||
]);
|
||||
expect(resolvedOutput).toBe(resolvedWorkspace);
|
||||
});
|
||||
});
|
||||
|
||||
it("lets bash workdir override the workspace default", async () => {
|
||||
await withTempDir("clawdbot-ws-", async (workspaceDir) => {
|
||||
await withTempDir("clawdbot-override-", async (overrideDir) => {
|
||||
const tools = createClawdbotCodingTools({ workspaceDir });
|
||||
const bashTool = tools.find((tool) => tool.name === "bash");
|
||||
expect(bashTool).toBeDefined();
|
||||
|
||||
const result = await bashTool?.execute("ws-bash-override", {
|
||||
command: 'node -e "console.log(process.cwd())"',
|
||||
workdir: overrideDir,
|
||||
});
|
||||
const output = normalizeText(getTextContent(result));
|
||||
const [resolvedOutput, resolvedOverride] = await Promise.all([
|
||||
fs.realpath(output),
|
||||
fs.realpath(overrideDir),
|
||||
]);
|
||||
expect(resolvedOutput).toBe(resolvedOverride);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("sandboxed workspace paths", () => {
|
||||
it("uses sandbox workspace for relative read/write/edit", async () => {
|
||||
await withTempDir("clawdbot-sandbox-", async (sandboxDir) => {
|
||||
await withTempDir("clawdbot-workspace-", async (workspaceDir) => {
|
||||
const sandbox = {
|
||||
enabled: true,
|
||||
sessionKey: "sandbox:test",
|
||||
workspaceDir: sandboxDir,
|
||||
agentWorkspaceDir: workspaceDir,
|
||||
workspaceAccess: "rw",
|
||||
containerName: "clawdbot-sbx-test",
|
||||
containerWorkdir: "/workspace",
|
||||
docker: {
|
||||
image: "clawdbot-sandbox:bookworm-slim",
|
||||
containerPrefix: "clawdbot-sbx-",
|
||||
workdir: "/workspace",
|
||||
readOnlyRoot: true,
|
||||
tmpfs: [],
|
||||
network: "none",
|
||||
user: "1000:1000",
|
||||
capDrop: ["ALL"],
|
||||
env: { LANG: "C.UTF-8" },
|
||||
},
|
||||
tools: { allow: [], deny: [] },
|
||||
};
|
||||
|
||||
const testFile = "sandbox.txt";
|
||||
await fs.writeFile(
|
||||
path.join(sandboxDir, testFile),
|
||||
"sandbox read",
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(workspaceDir, testFile),
|
||||
"workspace read",
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const tools = createClawdbotCodingTools({ workspaceDir, sandbox });
|
||||
const readTool = tools.find((tool) => tool.name === "read");
|
||||
const writeTool = tools.find((tool) => tool.name === "write");
|
||||
const editTool = tools.find((tool) => tool.name === "edit");
|
||||
|
||||
expect(readTool).toBeDefined();
|
||||
expect(writeTool).toBeDefined();
|
||||
expect(editTool).toBeDefined();
|
||||
|
||||
const result = await readTool?.execute("sbx-read", { path: testFile });
|
||||
expect(getTextContent(result)).toContain("sandbox read");
|
||||
|
||||
await writeTool?.execute("sbx-write", {
|
||||
path: "new.txt",
|
||||
content: "sandbox write",
|
||||
});
|
||||
const written = await fs.readFile(
|
||||
path.join(sandboxDir, "new.txt"),
|
||||
"utf8",
|
||||
);
|
||||
expect(written).toBe("sandbox write");
|
||||
|
||||
await editTool?.execute("sbx-edit", {
|
||||
path: "new.txt",
|
||||
oldText: "write",
|
||||
newText: "edit",
|
||||
});
|
||||
const edited = await fs.readFile(
|
||||
path.join(sandboxDir, "new.txt"),
|
||||
"utf8",
|
||||
);
|
||||
expect(edited).toBe("sandbox edit");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user