feat(cli): commande /profile + sélection de profil avec reset de session confirmé — #164 (QA verte)
Ajoute /profile comme commande slash first-class dont la sémantique de reset
de session reste explicite et testable, séparée de la palette générique :
- domain + application: /profile est enregistrée et renvoie un effet
SlashCommandEffect::ProfileSwitch { session_id }. La commande ne reset pas
directement : elle délègue l'effet au frontend pour imposer une confirmation.
- frontend (CustomAgentChatView): à l'exécution de /profile, ouverture d'un
popup de sélection de profil informant que la validation reset la session,
avec confirmation explicite (confirmedReset) requise avant changeAgentProfile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -522,6 +522,129 @@ describe("CustomAgentChatView", () => {
|
||||
expect(screen.queryByRole("listbox", { name: "suggestions commandes slash" })).toBeNull();
|
||||
});
|
||||
|
||||
it("executes /profile as a confirmed profile-switch flow that resets the current session", async () => {
|
||||
const agent = {
|
||||
launchAgentChat: vi.fn(async () => ({
|
||||
sessionId: "chat-session-2",
|
||||
cellKind: "chat" as const,
|
||||
assignedConversationId: "conversation-2",
|
||||
})),
|
||||
reattachAgentChat: vi.fn(async (sessionId: string) => ({
|
||||
sessionId,
|
||||
scrollback: [],
|
||||
})),
|
||||
sendAgentChat: vi.fn(async () => {}),
|
||||
executeSlashCommand: vi.fn(async () => ({
|
||||
command: {
|
||||
name: "/profile",
|
||||
shortDescription: "Changer le profil de l'agent",
|
||||
requiresConfirmation: true,
|
||||
availability: { status: "available" as const },
|
||||
source: "native" as const,
|
||||
native: "profile" as const,
|
||||
},
|
||||
effect: {
|
||||
kind: "profileSwitch" as const,
|
||||
sessionId: "chat-session-1",
|
||||
},
|
||||
})),
|
||||
changeAgentProfile: vi.fn(async () => ({ agent: { id: "agent-1" } })),
|
||||
cancelAgentChat: vi.fn(async () => {}),
|
||||
closeAgentChat: vi.fn(async () => {}),
|
||||
};
|
||||
const profileGateway = {
|
||||
listProfiles: vi.fn(async () => [
|
||||
profile,
|
||||
{
|
||||
...profile,
|
||||
id: "codex-high",
|
||||
name: "Codex High",
|
||||
},
|
||||
]),
|
||||
};
|
||||
const onSessionId = vi.fn();
|
||||
const onConversationId = vi.fn();
|
||||
|
||||
render(
|
||||
<DIProvider
|
||||
gateways={{
|
||||
agent,
|
||||
profile: profileGateway,
|
||||
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={onSessionId}
|
||||
onConversationId={onConversationId}
|
||||
/>
|
||||
</DIProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(agent.reattachAgentChat).toHaveBeenCalledWith(
|
||||
"chat-session-1",
|
||||
expect.any(Function),
|
||||
),
|
||||
);
|
||||
|
||||
const composer = screen.getByLabelText(/message CLI custom/) as HTMLTextAreaElement;
|
||||
fireEvent.change(composer, { target: { value: "/profile" } });
|
||||
fireEvent.keyDown(composer, { key: "Enter" });
|
||||
|
||||
const dialog = await screen.findByRole("dialog", { name: "Changer de profil" });
|
||||
expect(dialog.textContent).toContain("réinitialiser la session courante");
|
||||
expect(dialog.textContent).toContain("L'historique de cette CLI custom sera perdu");
|
||||
expect(agent.executeSlashCommand).toHaveBeenCalledWith("/profile", {
|
||||
sessionId: "chat-session-1",
|
||||
});
|
||||
expect(agent.sendAgentChat).not.toHaveBeenCalled();
|
||||
|
||||
const confirmButton = screen.getByRole("button", {
|
||||
name: "Reset et changer le profil",
|
||||
}) as HTMLButtonElement;
|
||||
expect(confirmButton.disabled).toBe(true);
|
||||
|
||||
fireEvent.change(screen.getByLabelText("profil cible"), {
|
||||
target: { value: "codex-high" },
|
||||
});
|
||||
fireEvent.click(screen.getByLabelText("Je confirme que la session courante sera reset."));
|
||||
expect(confirmButton.disabled).toBe(false);
|
||||
fireEvent.click(confirmButton);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(agent.changeAgentProfile).toHaveBeenCalledWith(
|
||||
"project-1",
|
||||
"agent-1",
|
||||
"codex-high",
|
||||
24,
|
||||
80,
|
||||
),
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(agent.launchAgentChat).toHaveBeenCalledWith("project-1", "agent-1", {
|
||||
cwd: "/repo",
|
||||
rows: 24,
|
||||
cols: 80,
|
||||
conversationId: undefined,
|
||||
nodeId: "node-1",
|
||||
}),
|
||||
);
|
||||
expect(onSessionId).toHaveBeenCalledWith(null);
|
||||
expect(onConversationId).toHaveBeenCalledWith(null);
|
||||
expect(onSessionId).toHaveBeenCalledWith("chat-session-2");
|
||||
expect(onConversationId).toHaveBeenCalledWith("conversation-2");
|
||||
expect(screen.getByText("Profil changé vers Codex High.")).toBeTruthy();
|
||||
expect(screen.queryByRole("dialog", { name: "Changer de profil" })).toBeNull();
|
||||
});
|
||||
|
||||
it("pastes a clipboard image as a removable preview chip", async () => {
|
||||
const agent = {
|
||||
launchAgentChat: vi.fn(),
|
||||
|
||||
Reference in New Issue
Block a user