fix(agents): recover edited draft when sending after rollback fails

This commit is contained in:
l0ng-ai
2026-10-29 21:26:57 +08:00
parent 3c87a8eea7
commit f0486a6826
2 changed files with 37 additions and 2 deletions
+28 -1
View File
@@ -25,7 +25,7 @@ vi.mock("@tauri-apps/api/webview", () => ({
}));
import { SessionEditor } from "./SessionEditor";
import { useAgents } from "./store";
import { updateComposer, useAgents } from "./store";
const task: AgentTask = {
id: "editing",
@@ -110,6 +110,33 @@ describe("editing a sent agent message", () => {
);
});
it.each(["", "Another draft"])("recovers the edit after rollback if sending fails (draft: %j)", async (draft) => {
ipc.rollback.mockImplementation(async () => {
// The backend emits Truncated before replying, removing the inline editor.
useAgents.setState({ entries: new Map([[task.id, []]]) });
return { status: "ok", data: null };
});
let finish!: (value: unknown) => void;
ipc.prompt.mockReturnValue(
new Promise((resolve) => {
finish = resolve;
}),
);
const { user, box, send } = await edit();
act(() => updateComposer(task.id, { text: draft }));
await user.click(send);
expect(box).not.toBeInTheDocument();
await act(async () => finish({ status: "error", error: { kind: "internal", message: "Send failed" } }));
expect(screen.getByRole("textbox", { name: "Message" })).toHaveValue(
draft ? `Edited message\n${draft}` : "Edited message",
);
expect(screen.getByRole("textbox", { name: "Message" })).toHaveFocus();
ipc.prompt.mockResolvedValue({ status: "ok", data: null });
await user.click(screen.getByRole("button", { name: "Send" }));
expect(ipc.prompt).toHaveBeenCalledTimes(2);
expect(ipc.rollback).toHaveBeenCalledTimes(1);
});
it.each([{ metaKey: true }, { ctrlKey: true }])("still sends with modified Enter (%j)", async (modifier) => {
const { box } = await edit();
fireEvent.keyDown(box, { key: "Enter", ...modifier });
+9 -1
View File
@@ -415,7 +415,15 @@ export async function rerunFrom(task: string, turn: number, text: string): Promi
await settled(task);
}
await call(commands.agentRollback(task, turn));
await call(commands.agentPrompt(task, { text, files: [], images: [] }, false));
try {
await call(commands.agentPrompt(task, { text, files: [], images: [] }, false));
} catch (e) {
// Rollback removes the inline editor; recover its text in the session composer.
const current = composerDraft(task);
updateComposer(task, { text: current.text ? `${text}\n${current.text}` : text });
focusComposer(task);
throw e;
}
}
/** Resolves once the task's turn has ended (or after a while). */