fix: keep live cancellation terminal
This commit is contained in:
@@ -168,6 +168,22 @@ describe("useTimelineAgent", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("live cancel does not call next", async () => {
|
||||||
|
const cancelReview = vi.fn(async () => {});
|
||||||
|
const next = vi.fn(async () => {});
|
||||||
|
const demo = demoController({
|
||||||
|
state: { ...initialDemoTimelineState, mode: "live", phase: "review" },
|
||||||
|
cancelReview,
|
||||||
|
next,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useTimelineAgent(demo, { mode: "live", status: readyStatus }));
|
||||||
|
await act(async () => result.current.cancelReview());
|
||||||
|
|
||||||
|
expect(cancelReview).toHaveBeenCalledWith("Cancelled by operator.");
|
||||||
|
expect(next).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("uses replay label when live target failed", () => {
|
it("uses replay label when live target failed", () => {
|
||||||
const demo = demoController();
|
const demo = demoController();
|
||||||
const { result } = renderHook(() =>
|
const { result } = renderHook(() =>
|
||||||
|
|||||||
@@ -122,11 +122,8 @@ export const useTimelineAgent = (
|
|||||||
|
|
||||||
const cancelReview = useCallback(async () => {
|
const cancelReview = useCallback(async () => {
|
||||||
await demo.cancelReview("Cancelled by operator.");
|
await demo.cancelReview("Cancelled by operator.");
|
||||||
// In replay the canonical recording only contains the submitted branch.
|
// Cancellation is terminal in presentation mode. Do not call next() or the UI
|
||||||
// Do not call next() or the UI would falsely advance into submitted evidence.
|
// would falsely advance into submitted/resume evidence.
|
||||||
if (demo.state.mode === "live") {
|
|
||||||
await demo.next();
|
|
||||||
}
|
|
||||||
setMessages((current) => appendToolMessage(
|
setMessages((current) => appendToolMessage(
|
||||||
current,
|
current,
|
||||||
"timeline-agent-cancel",
|
"timeline-agent-cancel",
|
||||||
|
|||||||
@@ -451,4 +451,82 @@ describe("useDemoTimeline", () => {
|
|||||||
expect(result.current.state.events).toEqual([]);
|
expect(result.current.state.events).toEqual([]);
|
||||||
expect(result.current.interruptPayload).toBeNull();
|
expect(result.current.interruptPayload).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("live cancellation is terminal and does not advance", async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
mockedCallOperation
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
operation: "workflow.deployments.inspect" as const,
|
||||||
|
label: "Inspect deployment",
|
||||||
|
interpreted: {
|
||||||
|
id: "lda_report_case_study.default",
|
||||||
|
artifactId: "lda_report_case_study",
|
||||||
|
artifactVersion: 1,
|
||||||
|
bindings: [],
|
||||||
|
driftPolicy: "block",
|
||||||
|
},
|
||||||
|
exchange: { request: {}, response: {} },
|
||||||
|
equivalentCli: "uv run wf deploy inspect lda_report_case_study.default",
|
||||||
|
durationMs: 4,
|
||||||
|
})
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
ok: true,
|
||||||
|
operation: "workflow.runs.start" as const,
|
||||||
|
label: "Start run",
|
||||||
|
interpreted: {
|
||||||
|
runId: "run_demo",
|
||||||
|
deploymentId: "lda_report_case_study.default",
|
||||||
|
artifactId: "lda_report_case_study",
|
||||||
|
artifactVersion: 1,
|
||||||
|
status: "interrupted",
|
||||||
|
resumeReadiness: "ready",
|
||||||
|
interrupt: {
|
||||||
|
kind: "issue_review",
|
||||||
|
payload: {
|
||||||
|
report_markdown: "# Report",
|
||||||
|
proposed_issues: [
|
||||||
|
{ id: "risk-1", title: "Defense", body: "Review paths.", severity: "medium" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
outcomes: ["submitted", "cancelled"],
|
||||||
|
typed: true,
|
||||||
|
request_schema: { type: "object" },
|
||||||
|
resume_schema: { type: "object" },
|
||||||
|
},
|
||||||
|
outcome: null,
|
||||||
|
error: null,
|
||||||
|
output: null,
|
||||||
|
diagnostics: [],
|
||||||
|
traceCount: 6,
|
||||||
|
nextActions: {
|
||||||
|
canContinue: true,
|
||||||
|
canSaveNow: null,
|
||||||
|
recommendedNextTool: "wf.workflow.resume_run",
|
||||||
|
reason: "Run is interrupted for issue review.",
|
||||||
|
patchExamples: [],
|
||||||
|
warnings: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
exchange: { request: {}, response: {} },
|
||||||
|
equivalentCli: "uv run wf run start lda_report_case_study.default --input '<json>'",
|
||||||
|
durationMs: 88,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useDemoTimeline("http://127.0.0.1:8765/rpc", vi.fn()),
|
||||||
|
);
|
||||||
|
act(() => result.current.start());
|
||||||
|
await act(async () => vi.advanceTimersByTimeAsync(900));
|
||||||
|
await act(async () => vi.advanceTimersByTimeAsync(900));
|
||||||
|
expect(result.current.state.phase).toBe("review");
|
||||||
|
|
||||||
|
await act(async () => result.current.cancelReview("Cancelled."));
|
||||||
|
await act(async () => vi.advanceTimersByTimeAsync(1800));
|
||||||
|
|
||||||
|
expect(result.current.state.phase).toBe("cancelled");
|
||||||
|
expect(result.current.output).toBeNull();
|
||||||
|
expect(result.current.trace).toBeNull();
|
||||||
|
expect(mockedCallOperation).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -292,8 +292,8 @@ export const useDemoTimeline = (
|
|||||||
comment,
|
comment,
|
||||||
outcome: "cancelled",
|
outcome: "cancelled",
|
||||||
};
|
};
|
||||||
dispatch({ type: state.mode === "live" ? "continue_review" : "cancel_review" });
|
dispatch({ type: "cancel_review" });
|
||||||
}, [state.mode]);
|
}, []);
|
||||||
|
|
||||||
const restart = useCallback(() => {
|
const restart = useCallback(() => {
|
||||||
resetRuntime();
|
resetRuntime();
|
||||||
|
|||||||
Reference in New Issue
Block a user