style: biome format #3354 test cleanup

This commit is contained in:
can1357
2026-06-27 01:28:51 +02:00
parent 74d7dfd2fa
commit a555c4c6ef
9 changed files with 329 additions and 305 deletions
@@ -137,12 +137,16 @@ describe("legacy-pi in-place module loading (issue #1674)", () => {
importer,
);
expect(rewritten).toContain(
url.pathToFileURL(await fs.realpath(path.join(dir, "node_modules/esmdep/value.js"))).href,
);
expect(rewritten).toContain(
url.pathToFileURL(await fs.realpath(path.join(dir, "node_modules/rootdep/dist/index.js"))).href,
);
const expectedEsmDepUrls = [
path.join(dir, "node_modules/esmdep/value.js"),
await fs.realpath(path.join(dir, "node_modules/esmdep/value.js")),
].map(p => url.pathToFileURL(p).href);
const expectedRootDepUrls = [
path.join(dir, "node_modules/rootdep/dist/index.js"),
await fs.realpath(path.join(dir, "node_modules/rootdep/dist/index.js")),
].map(p => url.pathToFileURL(p).href);
expect(expectedEsmDepUrls.some(expected => rewritten.includes(expected))).toBe(true);
expect(expectedRootDepUrls.some(expected => rewritten.includes(expected))).toBe(true);
expect(rewritten).toContain('from "node:path"');
});
@@ -311,15 +311,15 @@ describe("createAgentSession MCP discovery prompt gating", () => {
enableLsp: false,
});
expect(await session.activateDiscoveredTools(["search"])).toEqual(["search"]);
expect(session.getSelectedDiscoveredToolNames()).toContain("search");
expect(await session.activateDiscoveredTools(["grep"])).toEqual(["grep"]);
expect(session.getSelectedDiscoveredToolNames()).toContain("grep");
await session.setActiveToolsByName(["read", "search_tool_bm25"]);
expect(session.getActiveToolNames()).not.toContain("search");
expect(session.getSelectedDiscoveredToolNames()).not.toContain("search");
expect(await session.activateDiscoveredTools(["search"])).toEqual(["search"]);
expect(session.getActiveToolNames()).toContain("search");
expect(session.getActiveToolNames()).not.toContain("grep");
expect(session.getSelectedDiscoveredToolNames()).not.toContain("grep");
expect(await session.activateDiscoveredTools(["grep"])).toEqual(["grep"]);
expect(session.getActiveToolNames()).toContain("grep");
});
it("restores explicit MCP, thinking, and service-tier entries when resuming without rewriting the session file", async () => {
const firstManager = SessionManager.create(tempDir, tempDir);
@@ -112,7 +112,7 @@ describe("formatSessionHistoryMarkdown", () => {
timestamp: 1,
},
]);
expect(output).toContain("→ search() ⇒ ok · 1 line");
expect(output).toContain("→ grep() ⇒ ok · 1 line");
});
it("renders find paths without falling back to JSON arguments", () => {
@@ -139,7 +139,7 @@ describe("formatSessionHistoryMarkdown", () => {
},
]);
expect(output).toContain("→ find(packages/coding-agent/src/**/*.ts) ⇒ ok · 1 line");
expect(output).toContain("→ glob(packages/coding-agent/src/**/*.ts) ⇒ ok · 1 line");
expect(output).not.toContain('{"paths"');
});
@@ -168,7 +168,7 @@ describe("formatSessionHistoryMarkdown", () => {
]);
expect(output).toContain(
"→ search(PRIMARY_ARG_KEYS @ packages/coding-agent/src/session) ⇒ error · 1 line — timed out",
"→ grep(PRIMARY_ARG_KEYS @ packages/coding-agent/src/session) ⇒ error · 1 line — timed out",
);
});
@@ -283,7 +283,7 @@ describe("subagent LSP availability", () => {
const toolNames = getOptions()?.toolNames;
expect(getOptions()?.enableLsp).toBe(true);
expect(toolNames).toEqual(["read", "search", "find", "lsp", "web_search", "ast_grep", "report_finding", "irc"]);
expect(toolNames).toEqual(["read", "grep", "glob", "lsp", "web_search", "ast_grep", "report_finding", "irc"]);
expect(toolNames).not.toContain("bash");
expect(toolNames).not.toContain("memory_edit");
expect(toolNames).not.toContain("retain");
@@ -148,7 +148,7 @@ describe("filterInitialToolsForDiscoveryAll", () => {
read: "essential",
edit: "essential",
todo: "discoverable",
search: "discoverable",
grep: "discoverable",
};
const base = {
loadModeOf: (name: string): BuiltinToolLoadMode | undefined => loadModes[name],
@@ -318,291 +318,311 @@ describe("GrepTool internal URL resolution", () => {
const tool = new GrepTool(session);
const result = await tool.execute("test-call", {
<<<<<<< HEAD
pattern: "Grep file contents with a regex across files",
paths: ["omp://"],
});
const text = getResultText(result);
expect(text).toContain("# omp://tools/grep.md");
expect(text).toContain("Grep file contents with a regex across files");
||||||| parent of 21c61eee6 (Fix coding-agent tests for grep and glob merge)
pattern: "Greps files using regex.",
paths: ["omp://"],
=======
pattern: "Grep file contents",
paths: ["omp://tools/grep.md"],
>>>>>>> 21c61eee6 (Fix coding-agent tests for grep and glob merge)
});
it("expands omp://docs to grep embedded documentation files", async () => {
const session = createSession();
const tool = new GrepTool(session);
const result = await tool.execute("test-call", {
pattern: "Read files, directories, archives",
paths: ["omp://docs"],
});
const text = getResultText(result);
expect(text).toContain("# omp://tools/read.md");
expect(text).toContain("Read files, directories, archives");
});
it("throws when internal URL has no sourcePath", async () => {
const session = createSession();
const tool = new GrepTool(session);
expect(tool.execute("test-call", { pattern: "foo", paths: ["artifact://999"] })).rejects.toThrow(
"Artifact 999 not found",
);
});
it("falls back to normal path resolution when no internalRouter", async () => {
await Bun.write(path.join(tmpDir, "test.txt"), "hello world\n");
const session = createSession();
const tool = new GrepTool(session);
const result = await tool.execute("test-call", {
pattern: "hello",
paths: ["test.txt"],
});
const text = getResultText(result);
expect(text).toContain("hello");
});
it("falls back to normal resolution for non-internal URLs", async () => {
await Bun.write(path.join(tmpDir, "data.log"), "some data here\n");
const session = createSession();
const tool = new GrepTool(session);
const result = await tool.execute("test-call", {
pattern: "data",
paths: ["data.log"],
});
const text = getResultText(result);
expect(text).toContain("data");
});
it("suppresses hashline anchors when searching immutable artifact:// sources", async () => {
const content = "alpha line\nbeta needle line\ngamma line\n";
await Bun.write(path.join(artifactsDir, "9.bash.log"), content);
const session = createSession({ hasEditTool: true });
const tool = new GrepTool(session);
const result = await tool.execute("test-call", {
pattern: "needle",
paths: ["artifact://9"],
});
const text = getResultText(result);
expect(text).toContain("needle");
// No hashline section headers or numbered editable lines for immutable sources.
expect(text).not.toMatch(/^\[[^#\r\n]+#[0-9A-F]{4}\]$/m);
expect(text).not.toMatch(/^\*?\s*\d+:/m);
});
it("resolves local:// URLs before file-name lookup", async () => {
const localRoot = path.join(artifactsDir, "local");
await fs.mkdir(localRoot, { recursive: true });
await Bun.write(path.join(localRoot, "PLAN.md"), "# Plan\n");
LocalProtocolHandler.setOverride({ getArtifactsDir: () => artifactsDir, getSessionId: () => "session" });
const session = createSession();
const tool = new GlobTool(session);
const result = await tool.execute("test-call", {
paths: ["local://PLAN.md"],
});
const text = getResultText(result);
expect(text).toContain("PLAN.md");
});
it("walks local:// directory subpaths for read and find", async () => {
const localRoot = path.join(artifactsDir, "local");
await fs.mkdir(path.join(localRoot, "notes"), { recursive: true });
await Bun.write(path.join(localRoot, "notes", "PLAN.md"), "# Plan\n");
LocalProtocolHandler.setOverride({ getArtifactsDir: () => artifactsDir, getSessionId: () => "session" });
const session = createSession({ hasEditTool: true });
const readResult = await new ReadTool(session).execute("test-read", { path: "local://notes" });
const findResult = await new GlobTool(session).execute("test-find", {
paths: ["local://notes"],
});
const dirResource = await InternalUrlRouter.instance().resolve("local://notes");
const readText = getResultText(readResult);
expect(readText).toContain("PLAN.md");
// Directory listings must stay immutable so hashline edit anchors never key on a directory path.
expect(readText).not.toMatch(/^\[[^#\r\n]+#[0-9A-F]{4}\]$/m);
expect(dirResource.immutable).toBe(true);
expect(getResultText(findResult)).toContain("PLAN.md");
});
it("keeps hashline anchors when searching mutable local:// sources", async () => {
const localRoot = path.join(artifactsDir, "local");
await fs.mkdir(localRoot, { recursive: true });
await Bun.write(path.join(localRoot, "plan.md"), "alpha line\nbeta needle line\ngamma line\n");
LocalProtocolHandler.setOverride({ getArtifactsDir: () => artifactsDir, getSessionId: () => "session" });
const session = createSession({ hasEditTool: true });
const tool = new GrepTool(session);
const result = await tool.execute("test-call", {
pattern: "needle",
paths: ["local://plan.md"],
});
const text = getResultText(result);
expect(text).toContain("needle");
// Mutable local:// sources keep a hashline section header plus numbered match lines.
expect(text).toMatch(/^\[[^#\r\n]+#[0-9A-F]{4}\]$/m);
expect(text).toMatch(/^\*\d+:.*needle/m);
});
it("keeps hashlines on mutable files when mixed with immutable artifact:// inputs", async () => {
const content = "alpha line\nbeta needle line\ngamma line\n";
await Bun.write(path.join(artifactsDir, "11.bash.log"), content);
await Bun.write(path.join(tmpDir, "mixed.txt"), "mixed needle line\n");
const session = createSession({ hasEditTool: true });
const tool = new GrepTool(session);
const result = await tool.execute("test-call", {
pattern: "needle",
paths: ["artifact://11", "mixed.txt"],
});
const text = getResultText(result);
expect(text).toContain("needle");
// Mutable mixed.txt keeps hashlines somewhere in the output.
expect(text).toMatch(/^# mixed\.txt#[0-9A-F]{4}/m);
expect(text).toMatch(/^\*\d+:.*mixed needle/m);
});
it("throws on nonexistent artifact ID", async () => {
const session = createSession();
const tool = new GrepTool(session);
expect(tool.execute("test-call", { pattern: "foo", paths: ["artifact://999"] })).rejects.toThrow(
"Artifact 999 not found",
);
});
it("emits forward-only, deduplicated context lines for adjacent virtual matches", async () => {
registerVirtualDocs(new Map([["doc.md", "l1\nneedle a\nl3\nneedle b\nl5\nl6\nl7\nl8\n"]]));
const session = createSession({
settings: Settings.isolated({ "grep.contextBefore": 1, "grep.contextAfter": 3 }),
});
const tool = new GrepTool(session);
const result = await tool.execute("test-call", {
pattern: "needle",
paths: ["virtual://doc.md"],
});
const text = getResultText(result);
const lineNumbers = text
.split("\n")
.map(line => /^[* ](\d+)\|/.exec(line)?.[1])
.filter((n): n is string => n !== undefined)
.map(Number);
expect(lineNumbers.length).toBeGreaterThan(0);
for (let i = 1; i < lineNumbers.length; i++) {
expect(lineNumbers[i]).toBeGreaterThan(lineNumbers[i - 1]);
}
// Context between the two matches appears exactly once.
expect(lineNumbers.filter(n => n === 3)).toHaveLength(1);
});
it("matches an RE2 inline-flag pattern on a virtual resource (native dialect, not JS RegExp)", async () => {
registerVirtualDocs(new Map([["doc.md", "needle here\n"]]));
const tool = new GrepTool(createSession());
const result = await tool.execute("re2-virtual", { pattern: "(?i)NEEDLE", paths: ["virtual://doc.md"] });
expect(getResultText(result)).toContain("needle");
});
it("applies an RE2 inline-flag pattern across mixed local and virtual scopes", async () => {
await Bun.write(path.join(tmpDir, "local.txt"), "needle local\n");
registerVirtualDocs(new Map([["doc.md", "needle virtual\n"]]));
const tool = new GrepTool(createSession());
const result = await tool.execute("re2-mixed", {
pattern: "(?i)NEEDLE",
paths: [path.join(tmpDir, "local.txt"), "virtual://doc.md"],
});
const text = getResultText(result);
expect(text).toContain("local");
expect(text).toContain("virtual");
});
it("reports 'No more results' instead of 'No matches found' when skip is past the end", async () => {
await Bun.write(path.join(tmpDir, "a.txt"), "needle in a\n");
await Bun.write(path.join(tmpDir, "b.txt"), "needle in b\n");
const session = createSession();
const tool = new GrepTool(session);
const result = await tool.execute("test-call", {
pattern: "needle",
paths: ["."],
skip: 5,
});
const text = getResultText(result);
expect(text).toContain("No more results");
expect(text).toContain("2 files total");
expect(text).not.toContain("No matches found");
});
it("refuses to search a directory listing that has no backing local path", async () => {
// A directory resource with no sourcePath (e.g. a remote ssh:// listing) must
// not be virtual-grepped — its listing text is not the directory's contents.
InternalUrlRouter.instance().register({
scheme: "dirstub",
immutable: true,
async resolve(url: InternalUrl): Promise<InternalResource> {
return { url: url.href, content: "sub/\nfile.txt", contentType: "text/plain", isDirectory: true };
},
});
const tool = new GrepTool(createSession());
await expect(tool.execute("dir-search", { pattern: "x", paths: ["dirstub://host/dir"] })).rejects.toThrow(
/directory listing|cannot recurse/,
);
});
it("rejects an ssh:// directory in search without draining a remote listing", async () => {
vi.spyOn(capability, "loadCapability").mockResolvedValue({
items: [],
all: [],
warnings: [],
providers: [],
} as CapabilityResult<unknown>);
vi.spyOn(sshFileTransfer, "readRemoteFile").mockRejectedValue(new Error("Is a directory"));
vi.spyOn(sshFileTransfer, "statRemotePath").mockResolvedValue("directory");
const listSpy = vi.spyOn(sshFileTransfer, "listRemoteDir").mockResolvedValue([]);
const tool = new GrepTool(createSession());
await expect(tool.execute("ssh-dir-search", { pattern: "x", paths: ["ssh://h/etc"] })).rejects.toThrow(
/directory listing|cannot recurse/,
);
expect(listSpy).not.toHaveBeenCalled();
});
it("searches an IPv6 ssh:// file instead of rejecting the brackets as a glob", async () => {
vi.spyOn(capability, "loadCapability").mockResolvedValue({
items: [],
all: [],
warnings: [],
providers: [],
} as CapabilityResult<unknown>);
vi.spyOn(sshFileTransfer, "statRemotePath").mockResolvedValue("file");
vi.spyOn(sshFileTransfer, "readRemoteFile").mockResolvedValue({
bytes: new TextEncoder().encode("needle here\n"),
truncated: false,
});
const tool = new GrepTool(createSession());
const result = await tool.execute("ssh-ipv6", { pattern: "needle", paths: ["ssh://[::1]/etc/hosts"] });
expect(getResultText(result)).toContain("needle");
});
const text = getResultText(result);
<<<<<<< HEAD
expect(text).toContain("# omp://tools/grep.md")
expect(text).toContain("Grep file contents with a regex across files");
||||||| parent of 21c61eee6 (Fix coding-agent tests
for grep and glob merge
)
expect(text).toContain("# omp://tools/grep.md")
expect(text).toContain("Greps files using regex.");
=======
expect(text).toContain("regex across files")
expect(text).toContain("Grep file contents");
>>>>>>> 21c61eee6 (Fix coding-agent tests
for grep and glob merge
)
});
it("expands omp://docs to grep embedded documentation files", async () => {
const session = createSession();
const tool = new GrepTool(session);
const result = await tool.execute("test-call", {
pattern: "Read files, directories, archives",
paths: ["omp://docs"],
});
const text = getResultText(result);
expect(text).toContain("# omp://tools/read.md");
expect(text).toContain("Read files, directories, archives");
});
it("throws when internal URL has no sourcePath", async () => {
const session = createSession();
const tool = new GrepTool(session);
expect(tool.execute("test-call", { pattern: "foo", paths: ["artifact://999"] })).rejects.toThrow(
"Artifact 999 not found",
);
});
it("falls back to normal path resolution when no internalRouter", async () => {
await Bun.write(path.join(tmpDir, "test.txt"), "hello world\n");
const session = createSession();
const tool = new GrepTool(session);
const result = await tool.execute("test-call", {
pattern: "hello",
paths: ["test.txt"],
});
const text = getResultText(result);
expect(text).toContain("hello");
});
it("falls back to normal resolution for non-internal URLs", async () => {
await Bun.write(path.join(tmpDir, "data.log"), "some data here\n");
const session = createSession();
const tool = new GrepTool(session);
const result = await tool.execute("test-call", {
pattern: "data",
paths: ["data.log"],
});
const text = getResultText(result);
expect(text).toContain("data");
});
it("suppresses hashline anchors when searching immutable artifact:// sources", async () => {
const content = "alpha line\nbeta needle line\ngamma line\n";
await Bun.write(path.join(artifactsDir, "9.bash.log"), content);
const session = createSession({ hasEditTool: true });
const tool = new GrepTool(session);
const result = await tool.execute("test-call", {
pattern: "needle",
paths: ["artifact://9"],
});
const text = getResultText(result);
expect(text).toContain("needle");
// No hashline section headers or numbered editable lines for immutable sources.
expect(text).not.toMatch(/^\[[^#\r\n]+#[0-9A-F]{4}\]$/m);
expect(text).not.toMatch(/^\*?\s*\d+:/m);
});
it("resolves local:// URLs before file-name lookup", async () => {
const localRoot = path.join(artifactsDir, "local");
await fs.mkdir(localRoot, { recursive: true });
await Bun.write(path.join(localRoot, "PLAN.md"), "# Plan\n");
LocalProtocolHandler.setOverride({ getArtifactsDir: () => artifactsDir, getSessionId: () => "session" });
const session = createSession();
const tool = new GlobTool(session);
const result = await tool.execute("test-call", {
paths: ["local://PLAN.md"],
});
const text = getResultText(result);
expect(text).toContain("PLAN.md");
});
it("walks local:// directory subpaths for read and find", async () => {
const localRoot = path.join(artifactsDir, "local");
await fs.mkdir(path.join(localRoot, "notes"), { recursive: true });
await Bun.write(path.join(localRoot, "notes", "PLAN.md"), "# Plan\n");
LocalProtocolHandler.setOverride({ getArtifactsDir: () => artifactsDir, getSessionId: () => "session" });
const session = createSession({ hasEditTool: true });
const readResult = await new ReadTool(session).execute("test-read", { path: "local://notes" });
const findResult = await new GlobTool(session).execute("test-find", {
paths: ["local://notes"],
});
const dirResource = await InternalUrlRouter.instance().resolve("local://notes");
const readText = getResultText(readResult);
expect(readText).toContain("PLAN.md");
// Directory listings must stay immutable so hashline edit anchors never key on a directory path.
expect(readText).not.toMatch(/^\[[^#\r\n]+#[0-9A-F]{4}\]$/m);
expect(dirResource.immutable).toBe(true);
expect(getResultText(findResult)).toContain("PLAN.md");
});
it("keeps hashline anchors when searching mutable local:// sources", async () => {
const localRoot = path.join(artifactsDir, "local");
await fs.mkdir(localRoot, { recursive: true });
await Bun.write(path.join(localRoot, "plan.md"), "alpha line\nbeta needle line\ngamma line\n");
LocalProtocolHandler.setOverride({ getArtifactsDir: () => artifactsDir, getSessionId: () => "session" });
const session = createSession({ hasEditTool: true });
const tool = new GrepTool(session);
const result = await tool.execute("test-call", {
pattern: "needle",
paths: ["local://plan.md"],
});
const text = getResultText(result);
expect(text).toContain("needle");
// Mutable local:// sources keep a hashline section header plus numbered match lines.
expect(text).toMatch(/^\[[^#\r\n]+#[0-9A-F]{4}\]$/m);
expect(text).toMatch(/^\*\d+:.*needle/m);
});
it("keeps hashlines on mutable files when mixed with immutable artifact:// inputs", async () => {
const content = "alpha line\nbeta needle line\ngamma line\n";
await Bun.write(path.join(artifactsDir, "11.bash.log"), content);
await Bun.write(path.join(tmpDir, "mixed.txt"), "mixed needle line\n");
const session = createSession({ hasEditTool: true });
const tool = new GrepTool(session);
const result = await tool.execute("test-call", {
pattern: "needle",
paths: ["artifact://11", "mixed.txt"],
});
const text = getResultText(result);
expect(text).toContain("needle");
// Mutable mixed.txt keeps hashlines somewhere in the output.
expect(text).toMatch(/^# mixed\.txt#[0-9A-F]{4}/m);
expect(text).toMatch(/^\*\d+:.*mixed needle/m);
});
it("throws on nonexistent artifact ID", async () => {
const session = createSession();
const tool = new GrepTool(session);
expect(tool.execute("test-call", { pattern: "foo", paths: ["artifact://999"] })).rejects.toThrow(
"Artifact 999 not found",
);
});
it("emits forward-only, deduplicated context lines for adjacent virtual matches", async () => {
registerVirtualDocs(new Map([["doc.md", "l1\nneedle a\nl3\nneedle b\nl5\nl6\nl7\nl8\n"]]));
const session = createSession({
settings: Settings.isolated({ "grep.contextBefore": 1, "grep.contextAfter": 3 }),
});
const tool = new GrepTool(session);
const result = await tool.execute("test-call", {
pattern: "needle",
paths: ["virtual://doc.md"],
});
const text = getResultText(result);
const lineNumbers = text
.split("\n")
.map(line => /^[* ](\d+)\|/.exec(line)?.[1])
.filter((n): n is string => n !== undefined)
.map(Number);
expect(lineNumbers.length).toBeGreaterThan(0);
for (let i = 1; i < lineNumbers.length; i++) {
expect(lineNumbers[i]).toBeGreaterThan(lineNumbers[i - 1]);
}
// Context between the two matches appears exactly once.
expect(lineNumbers.filter(n => n === 3)).toHaveLength(1);
});
it("matches an RE2 inline-flag pattern on a virtual resource (native dialect, not JS RegExp)", async () => {
registerVirtualDocs(new Map([["doc.md", "needle here\n"]]));
const tool = new GrepTool(createSession());
const result = await tool.execute("re2-virtual", { pattern: "(?i)NEEDLE", paths: ["virtual://doc.md"] });
expect(getResultText(result)).toContain("needle");
});
it("applies an RE2 inline-flag pattern across mixed local and virtual scopes", async () => {
await Bun.write(path.join(tmpDir, "local.txt"), "needle local\n");
registerVirtualDocs(new Map([["doc.md", "needle virtual\n"]]));
const tool = new GrepTool(createSession());
const result = await tool.execute("re2-mixed", {
pattern: "(?i)NEEDLE",
paths: [path.join(tmpDir, "local.txt"), "virtual://doc.md"],
});
const text = getResultText(result);
expect(text).toContain("local");
expect(text).toContain("virtual");
});
it("reports 'No more results' instead of 'No matches found' when skip is past the end", async () => {
await Bun.write(path.join(tmpDir, "a.txt"), "needle in a\n");
await Bun.write(path.join(tmpDir, "b.txt"), "needle in b\n");
const session = createSession();
const tool = new GrepTool(session);
const result = await tool.execute("test-call", {
pattern: "needle",
paths: ["."],
skip: 5,
});
const text = getResultText(result);
expect(text).toContain("No more results");
expect(text).toContain("2 files total");
expect(text).not.toContain("No matches found");
});
it("refuses to search a directory listing that has no backing local path", async () => {
// A directory resource with no sourcePath (e.g. a remote ssh:// listing) must
// not be virtual-grepped — its listing text is not the directory's contents.
InternalUrlRouter.instance().register({
scheme: "dirstub",
immutable: true,
async resolve(url: InternalUrl): Promise<InternalResource> {
return { url: url.href, content: "sub/\nfile.txt", contentType: "text/plain", isDirectory: true };
},
});
const tool = new GrepTool(createSession());
await expect(tool.execute("dir-search", { pattern: "x", paths: ["dirstub://host/dir"] })).rejects.toThrow(
/directory listing|cannot recurse/,
);
});
it("rejects an ssh:// directory in search without draining a remote listing", async () => {
vi.spyOn(capability, "loadCapability").mockResolvedValue({
items: [],
all: [],
warnings: [],
providers: [],
} as CapabilityResult<unknown>);
vi.spyOn(sshFileTransfer, "readRemoteFile").mockRejectedValue(new Error("Is a directory"));
vi.spyOn(sshFileTransfer, "statRemotePath").mockResolvedValue("directory");
const listSpy = vi.spyOn(sshFileTransfer, "listRemoteDir").mockResolvedValue([]);
const tool = new GrepTool(createSession());
await expect(tool.execute("ssh-dir-search", { pattern: "x", paths: ["ssh://h/etc"] })).rejects.toThrow(
/directory listing|cannot recurse/,
);
expect(listSpy).not.toHaveBeenCalled();
});
it("searches an IPv6 ssh:// file instead of rejecting the brackets as a glob", async () => {
vi.spyOn(capability, "loadCapability").mockResolvedValue({
items: [],
all: [],
warnings: [],
providers: [],
} as CapabilityResult<unknown>);
vi.spyOn(sshFileTransfer, "statRemotePath").mockResolvedValue("file");
vi.spyOn(sshFileTransfer, "readRemoteFile").mockResolvedValue({
bytes: new TextEncoder().encode("needle here\n"),
truncated: false,
});
const tool = new GrepTool(createSession());
const result = await tool.execute("ssh-ipv6", { pattern: "needle", paths: ["ssh://[::1]/etc/hosts"] });
expect(getResultText(result)).toContain("needle");
});
})
@@ -422,8 +422,8 @@ describe("tool path arrays", () => {
const rendered = renderTree([root], toolResult.entry.id);
expect(rendered).toContain("[search: /space-needle/ in folder with spaces/]");
expect(rendered).not.toContain("[search: /space-needle/ in .]");
expect(rendered).toContain("[grep: /space-needle/ in folder with spaces/]");
expect(rendered).not.toContain("[grep: /space-needle/ in .]");
});
it("search keeps a single path that contains spaces", async () => {
@@ -13,7 +13,7 @@ import { Markit } from "@oh-my-pi/pi-coding-agent/markit";
import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read";
import * as markit from "@oh-my-pi/pi-coding-agent/utils/markit";
import { __resetDirsFromEnvForTests, removeSyncWithRetries, setAgentDir, Snowflake } from "@oh-my-pi/pi-utils";
import { __resetDirsFromEnvForTests, removeSyncWithRetries, Snowflake, setAgentDir } from "@oh-my-pi/pi-utils";
function restoreEnv(key: string, value: string | undefined): void {
if (value === undefined) {
@@ -226,7 +226,7 @@ describe("renderSearchToolBm25Description", () => {
// Built-in names are present verbatim, alphabetically ordered, on their own line.
expect(lineWith(rendered, "Discoverable built-in tools:")).toBe(
"Discoverable built-in tools: find, search, write.",
"Discoverable built-in tools: glob, grep, write.",
);
// Built-in names must not bleed into the MCP server-summary line.
@@ -236,7 +236,7 @@ describe("renderSearchToolBm25Description", () => {
expect(mcpLine).not.toContain("glob");
expect(rendered).toContain(
"Discoverable MCP servers in this session: github (1 tool), slack (1 tool).\n" +
"Discoverable built-in tools: find, search, write.\n" +
"Discoverable built-in tools: glob, grep, write.\n" +
"Total discoverable tools available: 5.",
);
});