fix: keep replay cancellation honest
This commit is contained in:
@@ -89,4 +89,25 @@ describe("useTimelineAgent", () => {
|
||||
const { result } = renderHook(() => useTimelineAgent(demo, "live"));
|
||||
expect(result.current.canRun).toBe(false);
|
||||
});
|
||||
|
||||
it("does not advance replay cancellation into the submitted recording branch", async () => {
|
||||
const cancelReview = vi.fn(async () => {});
|
||||
const next = vi.fn(async () => {});
|
||||
const demo = demoController({
|
||||
state: { ...initialDemoTimelineState, mode: "replay", phase: "review" },
|
||||
cancelReview,
|
||||
next,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useTimelineAgent(demo, "replay"));
|
||||
await act(async () => result.current.cancelReview());
|
||||
|
||||
expect(cancelReview).toHaveBeenCalledWith("Cancelled by operator.");
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
expect(result.current.messages.at(-1)?.parts).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ type: "tool-result" }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,7 +88,11 @@ export const useTimelineAgent = (
|
||||
|
||||
const cancelReview = useCallback(async () => {
|
||||
await demo.cancelReview("Cancelled by operator.");
|
||||
await demo.next();
|
||||
// In replay the canonical recording only contains the submitted branch.
|
||||
// Do not call next() or the UI would falsely advance into submitted evidence.
|
||||
if (demo.state.mode === "live") {
|
||||
await demo.next();
|
||||
}
|
||||
setMessages((current) => appendToolMessage(
|
||||
current,
|
||||
"timeline-agent-cancel",
|
||||
|
||||
@@ -212,4 +212,23 @@ describe("PresentationRoute", () => {
|
||||
expect(window.location.hash).toBe("#scene/interrupt-evidence/resume");
|
||||
expect(screen.getByLabelText("workflow.runs.resume operation")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("cancels Scene 10 approval in replay without applying submitted evidence", async () => {
|
||||
const user = userEvent.setup();
|
||||
setReplayMode();
|
||||
window.location.hash = "#scene/interrupt-evidence/approval";
|
||||
const { PresentationRoute } = await import("./PresentationRoute.js");
|
||||
render(<PresentationRoute />);
|
||||
|
||||
const cancelButton = await screen.findByRole("button", { name: "Cancel" });
|
||||
await waitFor(() => expect(cancelButton).toBeEnabled(), { timeout: 10000 });
|
||||
|
||||
await act(async () => {
|
||||
await user.click(cancelButton);
|
||||
});
|
||||
|
||||
expect(screen.getByText(/Outcome: cancelled/i)).toBeInTheDocument();
|
||||
expect(window.location.hash).toBe("#scene/interrupt-evidence/approval");
|
||||
expect(screen.queryByLabelText("workflow.runs.resume operation")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -135,8 +135,17 @@ export const PresentationRoute = () => {
|
||||
}, [demo]);
|
||||
|
||||
const handleCancelApproval = useCallback(async () => {
|
||||
if (demo.state.phase !== "review") return;
|
||||
|
||||
setApprovalState("cancelled");
|
||||
}, []);
|
||||
await demo.cancelReview("Cancelled by operator.");
|
||||
|
||||
// The canonical replay only records the submitted branch. Do not call
|
||||
// next() in replay, or the UI would falsely show submitted run evidence.
|
||||
if (demo.state.mode === "live") {
|
||||
await demo.next();
|
||||
}
|
||||
}, [demo]);
|
||||
|
||||
const approvalActions = useMemo<DemoApprovalActions>(() => ({
|
||||
state: approvalState,
|
||||
@@ -147,10 +156,10 @@ export const PresentationRoute = () => {
|
||||
}), [approvalState, demo.state.phase, demo.interruptPayload, handleSubmitApproval, handleCancelApproval]);
|
||||
|
||||
useEffect(() => {
|
||||
if (demo.state.phase === "ready" || demo.state.phase === "running") {
|
||||
if ((demo.state.phase === "ready" || demo.state.phase === "running") && approvalState === "ready") {
|
||||
setApprovalState("ready");
|
||||
}
|
||||
}, [demo.state.phase]);
|
||||
}, [demo.state.phase, approvalState]);
|
||||
|
||||
const handleJump = useCallback(
|
||||
(location: MainLocation) => dispatch({ type: "jump", location }),
|
||||
|
||||
Reference in New Issue
Block a user