fix(custom-cli): le bouton Stop annule le tour courant sans fermer la session et cascade l'annulation aux agents délégués — stop_agent suit l'arbre des dépendances actives (cancel_current_turn + préemption runtime + enfants d'exécution), TurnCancel tue le process du tour en cours, autorisation requester/sous-arbre sur idea_stop_agent, fixtures DTO runtime_state/node_id alignées — #257 (QA verte)
This commit is contained in:
@ -856,28 +856,27 @@ describe("CustomAgentChatView", () => {
|
||||
expect(screen.queryByText("Le modèle n'a renvoyé aucune réponse.")).toBeNull();
|
||||
});
|
||||
|
||||
it("stops the live agent with cascade feedback and releases the structured session", async () => {
|
||||
let resolveStop: (value: {
|
||||
agentId: string;
|
||||
sessionId: string;
|
||||
kind: "structured";
|
||||
}) => void = () => {};
|
||||
it("cancels only the current request and keeps the structured session usable", async () => {
|
||||
let resolveCancel: () => void = () => {};
|
||||
const agent = {
|
||||
launchAgentChat: vi.fn(),
|
||||
reattachAgentChat: vi.fn(async (sessionId: string) => ({
|
||||
sessionId,
|
||||
scrollback: [],
|
||||
})),
|
||||
sendAgentChat: vi.fn(() => new Promise<void>(() => {})),
|
||||
stopLiveAgent: vi.fn(
|
||||
sendAgentChat: vi
|
||||
.fn()
|
||||
.mockImplementationOnce(() => new Promise<void>(() => {}))
|
||||
.mockImplementationOnce(async (_sessionId: string, _prompt: string, onChunk) => {
|
||||
onChunk({ kind: "final", content: "Next answer" });
|
||||
}),
|
||||
stopLiveAgent: vi.fn(),
|
||||
cancelAgentChat: vi.fn(
|
||||
() =>
|
||||
new Promise<{ agentId: string; sessionId: string; kind: "structured" }>(
|
||||
(resolve) => {
|
||||
resolveStop = resolve;
|
||||
},
|
||||
),
|
||||
new Promise<void>((resolve) => {
|
||||
resolveCancel = resolve;
|
||||
}),
|
||||
),
|
||||
cancelAgentChat: vi.fn(async () => {}),
|
||||
closeAgentChat: vi.fn(async () => {}),
|
||||
};
|
||||
const onSessionId = vi.fn();
|
||||
@ -928,28 +927,35 @@ describe("CustomAgentChatView", () => {
|
||||
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Stop" }));
|
||||
|
||||
const stoppingLabel = await screen.findByText("Arrêt en cours...");
|
||||
const stoppingLabel = await screen.findByText("Stopping...");
|
||||
expect(stoppingLabel.closest("button")?.hasAttribute("disabled")).toBe(true);
|
||||
fireEvent.click(stoppingLabel.closest("button")!);
|
||||
await waitFor(() =>
|
||||
expect(agent.stopLiveAgent).toHaveBeenCalledWith("project-1", "agent-1"),
|
||||
expect(agent.cancelAgentChat).toHaveBeenCalledWith("chat-session-1"),
|
||||
);
|
||||
resolveStop({
|
||||
agentId: "agent-1",
|
||||
sessionId: "chat-session-1",
|
||||
kind: "structured",
|
||||
});
|
||||
await waitFor(() => expect(onSessionId).toHaveBeenCalledWith(null));
|
||||
expect(agent.cancelAgentChat).toHaveBeenCalledTimes(1);
|
||||
resolveCancel();
|
||||
await waitFor(() => expect(screen.getByText("Request stopped.")).toBeTruthy());
|
||||
expect(hasProjectCliInteraction("project-1")).toBe(false);
|
||||
expect(agent.cancelAgentChat).not.toHaveBeenCalled();
|
||||
expect(onSessionId).not.toHaveBeenCalledWith(null);
|
||||
expect(agent.stopLiveAgent).not.toHaveBeenCalled();
|
||||
expect(agent.closeAgentChat).not.toHaveBeenCalled();
|
||||
expect(
|
||||
screen.getByText(
|
||||
"Agent arrêté. Cascade d'arrêt demandée pour les tâches et appels MCP liés.",
|
||||
),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Envoyer" }).hasAttribute("disabled"),
|
||||
).toBe(true);
|
||||
const composer = screen.getByLabelText(/message CLI custom/);
|
||||
expect(composer).toHaveProperty("disabled", false);
|
||||
|
||||
fireEvent.change(composer, {
|
||||
target: { value: "next turn" },
|
||||
});
|
||||
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
|
||||
|
||||
await waitFor(() => expect(agent.sendAgentChat).toHaveBeenCalledTimes(2));
|
||||
expect(agent.sendAgentChat).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"chat-session-1",
|
||||
"next turn",
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(await screen.findByText("Next answer")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("keeps the composer locked from canonical work-state busy even without local stream activity", async () => {
|
||||
@ -4848,7 +4854,7 @@ describe("CustomAgentChatView", () => {
|
||||
expect(screen.queryByText(/not found: structured session/)).toBeNull();
|
||||
});
|
||||
|
||||
it("treats NOT_FOUND during cancel as an already-gone session", async () => {
|
||||
it("treats NOT_FOUND during request cancel as an already-stopped request", async () => {
|
||||
const agent = {
|
||||
launchAgentChat: vi.fn(),
|
||||
reattachAgentChat: vi.fn(async (sessionId: string) => ({
|
||||
@ -4856,11 +4862,11 @@ describe("CustomAgentChatView", () => {
|
||||
scrollback: [],
|
||||
})),
|
||||
sendAgentChat: vi.fn(() => new Promise<void>(() => {})),
|
||||
stopLiveAgent: vi.fn().mockRejectedValueOnce({
|
||||
stopLiveAgent: vi.fn(),
|
||||
cancelAgentChat: vi.fn().mockRejectedValueOnce({
|
||||
code: "NOT_FOUND",
|
||||
message: "agent agent-1 has no live session",
|
||||
}),
|
||||
cancelAgentChat: vi.fn(async () => {}),
|
||||
closeAgentChat: vi.fn(async () => {}),
|
||||
};
|
||||
const onSessionId = vi.fn();
|
||||
@ -4902,9 +4908,10 @@ describe("CustomAgentChatView", () => {
|
||||
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Stop" }));
|
||||
|
||||
expect(await screen.findByText("Session déjà arrêtée.")).toBeTruthy();
|
||||
expect(onSessionId).toHaveBeenCalledWith(null);
|
||||
expect(agent.cancelAgentChat).not.toHaveBeenCalled();
|
||||
expect(await screen.findByText("Request already stopped.")).toBeTruthy();
|
||||
expect(onSessionId).not.toHaveBeenCalledWith(null);
|
||||
expect(agent.cancelAgentChat).toHaveBeenCalledWith("gone-session");
|
||||
expect(agent.stopLiveAgent).not.toHaveBeenCalled();
|
||||
expect(agent.closeAgentChat).not.toHaveBeenCalled();
|
||||
expect(screen.queryByText(/not found: structured session/)).toBeNull();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user