mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-27 19:48:48 +00:00
fix: preview edits through configured backend
This commit is contained in:
@@ -423,6 +423,11 @@ export interface EditDiffError {
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface EditDiffOperations {
|
||||
readFile: (absolutePath: string) => Promise<Buffer | string>;
|
||||
access: (absolutePath: string) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the diff for one or more edit operations without applying them.
|
||||
* Used for preview rendering in the TUI before the tool executes.
|
||||
@@ -431,13 +436,18 @@ export async function computeEditsDiff(
|
||||
path: string,
|
||||
edits: Edit[],
|
||||
cwd: string,
|
||||
operations?: EditDiffOperations,
|
||||
): Promise<EditDiffResult | EditDiffError> {
|
||||
const absolutePath = resolveToCwd(path, cwd);
|
||||
|
||||
try {
|
||||
// Check if file exists and is readable
|
||||
try {
|
||||
await access(absolutePath, constants.R_OK);
|
||||
if (operations) {
|
||||
await operations.access(absolutePath);
|
||||
} else {
|
||||
await access(absolutePath, constants.R_OK);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const errorMessage =
|
||||
error instanceof Error && "code" in error
|
||||
@@ -447,7 +457,11 @@ export async function computeEditsDiff(
|
||||
}
|
||||
|
||||
// Read the file
|
||||
const rawContent = await readFile(absolutePath, "utf-8");
|
||||
const rawContentResult = operations
|
||||
? await operations.readFile(absolutePath)
|
||||
: await readFile(absolutePath, "utf-8");
|
||||
const rawContent =
|
||||
typeof rawContentResult === "string" ? rawContentResult : rawContentResult.toString("utf-8");
|
||||
|
||||
// Strip BOM before matching (LLM won't include invisible BOM in oldText)
|
||||
const { text: content } = stripBom(rawContent);
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { createEditTool, type EditOperations } from "./edit.js";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { Theme } from "../../modes/interactive/theme/theme.js";
|
||||
import { createEditTool, createEditToolDefinition, type EditOperations } from "./edit.js";
|
||||
|
||||
const testTheme = {
|
||||
bg: (_name: string, text: string) => text,
|
||||
bold: (text: string) => text,
|
||||
fg: (_name: string, text: string) => text,
|
||||
} as Theme;
|
||||
|
||||
describe("edit tool", () => {
|
||||
let tmpDir = "";
|
||||
@@ -125,4 +132,40 @@ describe("edit tool", () => {
|
||||
text: `Successfully replaced 2 block(s) in ${filePath}.`,
|
||||
});
|
||||
});
|
||||
|
||||
it("renders previews through custom edit operations", async () => {
|
||||
const readFile = vi.fn(async () => Buffer.from("remote original\n"));
|
||||
const operations: EditOperations = {
|
||||
access: async () => {},
|
||||
readFile,
|
||||
writeFile: async () => {},
|
||||
};
|
||||
const tool = createEditToolDefinition("/workspace", { operations });
|
||||
const args = {
|
||||
path: "remote.txt",
|
||||
edits: [{ oldText: "remote original", newText: "remote changed" }],
|
||||
};
|
||||
const context = {
|
||||
args,
|
||||
argsComplete: true,
|
||||
cwd: "/workspace",
|
||||
executionStarted: false,
|
||||
expanded: false,
|
||||
invalidate: vi.fn(),
|
||||
isError: false,
|
||||
isPartial: false,
|
||||
lastComponent: undefined,
|
||||
showImages: false,
|
||||
state: {},
|
||||
toolCallId: "call-preview",
|
||||
};
|
||||
|
||||
const component = tool.renderCall?.(args, testTheme, context);
|
||||
await vi.waitFor(() => expect(context.invalidate).toHaveBeenCalled());
|
||||
|
||||
expect(readFile).toHaveBeenCalledWith(path.join("/workspace", "remote.txt"));
|
||||
expect((component as { preview?: { diff?: string } } | undefined)?.preview?.diff).toContain(
|
||||
"remote changed",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -471,7 +471,7 @@ export function createEditToolDefinition(
|
||||
if (context.argsComplete && previewInput && !component.preview && !component.previewPending) {
|
||||
component.previewPending = true;
|
||||
const requestKey = argsKey;
|
||||
void computeEditsDiff(previewInput.path, previewInput.edits, context.cwd).then(
|
||||
void computeEditsDiff(previewInput.path, previewInput.edits, context.cwd, ops).then(
|
||||
(preview) => {
|
||||
if (component.previewArgsKey === requestKey) {
|
||||
setEditPreview(component, preview, requestKey);
|
||||
|
||||
Reference in New Issue
Block a user