fix(cli): z-order Cancel #151 + projection live événements avant Final #167 (+ alignement fixture plugin #165/#166) — QA verte
This commit is contained in:
@ -37,7 +37,17 @@ function addPluginCommand(
|
||||
pluginId,
|
||||
displayName: "Acme Plugin",
|
||||
contributes: {
|
||||
commands: [{ id: commandId, title: "Explain", shortDescription: "Explain selection" }],
|
||||
menus: [],
|
||||
menuItems: [],
|
||||
slashCommands: [
|
||||
{
|
||||
name: "/explain",
|
||||
shortDescription: "Explain selection",
|
||||
command: commandId,
|
||||
},
|
||||
],
|
||||
layouts: [],
|
||||
mcpServers: [],
|
||||
},
|
||||
commands,
|
||||
layouts: new PluginLayoutRegistry(pluginId, new Set()),
|
||||
@ -408,6 +418,83 @@ describe("CustomAgentChatView", () => {
|
||||
expect(screen.getByText("Progress").parentElement?.textContent).toContain("done");
|
||||
});
|
||||
|
||||
it("does not append late progress chunks after the final answer", async () => {
|
||||
let emitChunk: ((chunk: unknown) => void) | null = null;
|
||||
const agent = {
|
||||
launchAgentChat: vi.fn(),
|
||||
reattachAgentChat: vi.fn(async (sessionId: string) => ({
|
||||
sessionId,
|
||||
scrollback: [],
|
||||
})),
|
||||
sendAgentChat: vi.fn(
|
||||
(_sessionId: string, _prompt: string, onChunk: (chunk: unknown) => void) =>
|
||||
new Promise<void>((resolve) => {
|
||||
emitChunk = (chunk: unknown) => {
|
||||
onChunk(chunk);
|
||||
if ((chunk as { kind?: string }).kind === "final") resolve();
|
||||
};
|
||||
}),
|
||||
),
|
||||
cancelAgentChat: vi.fn(async () => {}),
|
||||
closeAgentChat: vi.fn(async () => {}),
|
||||
};
|
||||
|
||||
render(
|
||||
<DIProvider
|
||||
gateways={{
|
||||
agent,
|
||||
system: { pickFile: vi.fn(async () => null) },
|
||||
} as unknown as Gateways}
|
||||
>
|
||||
<CustomAgentChatView
|
||||
projectId="project-1"
|
||||
agentId="agent-1"
|
||||
agentName="Worker"
|
||||
profile={profile}
|
||||
cwd="/repo"
|
||||
nodeId="node-1"
|
||||
sessionId="chat-session-1"
|
||||
conversationId="conversation-1"
|
||||
onSessionId={vi.fn()}
|
||||
onConversationId={vi.fn()}
|
||||
/>
|
||||
</DIProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(agent.reattachAgentChat).toHaveBeenCalledWith(
|
||||
"chat-session-1",
|
||||
expect.any(Function),
|
||||
),
|
||||
);
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
|
||||
target: { value: "ship ordered stream" },
|
||||
});
|
||||
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
|
||||
await waitFor(() => expect(emitChunk).not.toBeNull());
|
||||
|
||||
act(() => {
|
||||
emitChunk?.({ kind: "final", content: "done" });
|
||||
});
|
||||
await screen.findByText("done");
|
||||
|
||||
act(() => {
|
||||
emitChunk?.({
|
||||
kind: "progress",
|
||||
progress: {
|
||||
source: "ideaLocal",
|
||||
kind: "mcp",
|
||||
stage: "started",
|
||||
label: "idea_ticket_read",
|
||||
toolName: "idea_ticket_read",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
expect(screen.queryByText("idea_ticket_read")).toBeNull();
|
||||
});
|
||||
|
||||
it("shows slash-command suggestions from the gateway, filters by prefix, and inserts the selected command", async () => {
|
||||
const agent = {
|
||||
launchAgentChat: vi.fn(),
|
||||
@ -1040,6 +1127,7 @@ describe("CustomAgentChatView", () => {
|
||||
expect(shell.className).toContain("overflow-hidden");
|
||||
expect(toolbar.className).toContain("overflow-hidden");
|
||||
expect(cancel.className).toContain("shrink-0");
|
||||
expect(cancel.className).toContain("z-30");
|
||||
expect(scroll.className).toContain("flex-1");
|
||||
expect(scroll.className).toContain("basis-0");
|
||||
expect(scroll.className).toContain("overflow-y-auto");
|
||||
|
||||
@ -299,6 +299,14 @@ function foldChunk(turns: ChatTurn[], raw: unknown): ChatTurn[] {
|
||||
if (!isReplyRecord(raw)) {
|
||||
return [...turns, { role: "unknown", text: unknownChunkLabel(raw) }];
|
||||
}
|
||||
const last = turns[turns.length - 1];
|
||||
if (
|
||||
(last?.role === "final" || last?.role === "error") &&
|
||||
raw.kind !== "userPrompt" &&
|
||||
raw.kind !== "UserPrompt"
|
||||
) {
|
||||
return turns;
|
||||
}
|
||||
switch (raw.kind) {
|
||||
case "textDelta":
|
||||
return appendAgentDelta(turns, String(raw.text ?? ""));
|
||||
@ -946,7 +954,7 @@ export function CustomAgentChatView({
|
||||
<div
|
||||
role="toolbar"
|
||||
aria-label="custom agent chat actions"
|
||||
className="flex shrink-0 items-center gap-2 overflow-hidden border-b border-border px-3 py-2"
|
||||
className="relative z-20 flex shrink-0 items-center gap-2 overflow-hidden border-b border-border px-3 py-2"
|
||||
>
|
||||
<div className="min-w-0 flex-1 overflow-hidden">
|
||||
<div className="truncate text-sm font-medium">{agentName}</div>
|
||||
@ -959,7 +967,7 @@ export function CustomAgentChatView({
|
||||
<Button
|
||||
size="sm"
|
||||
variant="danger"
|
||||
className="shrink-0 whitespace-nowrap"
|
||||
className="relative z-30 shrink-0 whitespace-nowrap shadow-sm"
|
||||
onClick={() => void cancel()}
|
||||
>
|
||||
Cancel
|
||||
|
||||
Reference in New Issue
Block a user