diff --git a/src/infra/command-explainer/extract.test.ts b/src/infra/command-explainer/extract.test.ts index 9161d2ed0a3..cc4157a0705 100644 --- a/src/infra/command-explainer/extract.test.ts +++ b/src/infra/command-explainer/extract.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import type { Parser } from "web-tree-sitter"; import { explainShellCommand } from "./extract.js"; import { @@ -20,6 +20,7 @@ afterEach(() => { setBashParserLoaderForCommandExplanationForTest(); parserLoaderOverridden = false; } + vi.restoreAllMocks(); }); describe("command explainer tree-sitter runtime", () => { @@ -71,6 +72,28 @@ describe("command explainer tree-sitter runtime", () => { ).toThrow("Unable to locate missing-openclaw-parser.wasm in web-tree-sitter"); }); + it("reports parser progress cancellation as a timeout", async () => { + const reset = vi.fn(); + const parser = { + parse: ( + _source: string, + _oldTree: unknown, + options?: { progressCallback?: (state: unknown) => boolean }, + ) => { + options?.progressCallback?.({ currentOffset: 0, hasError: false }); + return null; + }, + reset, + } as unknown as Parser; + vi.spyOn(performance, "now").mockReturnValueOnce(0).mockReturnValue(501); + setParserLoaderForTest(async () => parser); + + await expect(parseBashForCommandExplanation("echo hi")).rejects.toThrow( + "tree-sitter-bash timed out after 500ms while parsing shell command", + ); + expect(reset).toHaveBeenCalledOnce(); + }); + it("explains a pipeline with python inline eval", async () => { const explanation = await explainShellCommand('ls | grep "stuff" | python -c \'print("hi")\''); diff --git a/src/infra/command-explainer/tree-sitter-runtime.ts b/src/infra/command-explainer/tree-sitter-runtime.ts index 140ab9ec158..08792a7851a 100644 --- a/src/infra/command-explainer/tree-sitter-runtime.ts +++ b/src/infra/command-explainer/tree-sitter-runtime.ts @@ -87,11 +87,20 @@ export async function parseBashForCommandExplanation(source: string): Promise