fix(custom-cli): cartes progress/tools visibles dès la 2e sollicitation malgré la réutilisation du progressId — la clé d'upsert borne le progressId au scope du tour au lieu d'un dédup global sur l'historique — #157 (QA verte)

This commit is contained in:
2026-08-24 09:43:20 +02:00
parent d9af4b5630
commit 4db264ea8c
2 changed files with 113 additions and 1 deletions

View File

@ -1764,6 +1764,118 @@ describe("CustomAgentChatView", () => {
expect(screen.getAllByTestId("chat-tool-progress")).toHaveLength(2);
});
it("keeps second-turn progress visible when the provider reuses the same progressId across turns", async () => {
const harness = createStreamingAgentHarness();
render(
<DIProvider
gateways={{
agent: harness.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(harness.agent.reattachAgentChat).toHaveBeenCalledWith(
"chat-session-1",
expect.any(Function),
),
);
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
target: { value: "first request" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
await waitFor(() => expect(harness.emitterCount()).toBe(1));
act(() => {
harness.emit(0, {
kind: "progress",
progress: {
progressId: "codex:item:call_1",
source: "providerNative",
kind: "tool",
stage: "started",
label: "shell",
toolName: "shell",
text: "Running first request",
},
});
harness.emit(0, {
kind: "progress",
progress: {
progressId: "codex:item:call_1",
source: "providerNative",
kind: "tool",
stage: "completed",
label: "shell",
toolName: "shell",
text: "Done first request",
},
});
harness.emit(0, { kind: "final", content: "done first request" });
});
await screen.findByText("done first request");
fireEvent.change(screen.getByLabelText(/message CLI custom/), {
target: { value: "second request" },
});
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
await waitFor(() => expect(harness.emitterCount()).toBe(2));
act(() => {
harness.emit(1, {
kind: "progress",
progress: {
progressId: "codex:item:call_1",
source: "providerNative",
kind: "tool",
stage: "started",
label: "shell",
toolName: "shell",
text: "Running second request",
},
});
});
expect(screen.getByText("Running second request")).toBeTruthy();
act(() => {
harness.emit(1, {
kind: "progress",
progress: {
progressId: "codex:item:call_1",
source: "providerNative",
kind: "tool",
stage: "completed",
label: "shell",
toolName: "shell",
text: "Done second request",
},
});
harness.emit(1, { kind: "final", content: "done second request" });
});
await screen.findByText("done second request");
expect(screen.getByText("Done first request")).toBeTruthy();
expect(screen.getByText("Done second request")).toBeTruthy();
expect(screen.getAllByTestId("chat-tool-progress")).toHaveLength(2);
});
it("ignores a late first-turn progress chunk but still shows the real second-turn progress from the next callback", async () => {
const harness = createStreamingAgentHarness();

View File

@ -325,7 +325,7 @@ function progressKey(progress: ReplyProgress): string {
function progressUpsertKey(progress: ReplyProgress, scope: number): string {
const progressId = progress.progressId?.trim();
if (progressId) return progressId;
if (progressId) return `progress:${scope}:${progressId}`;
return `legacy:${scope}:${progressDisplayKey(progress)}`;
}