feat(agent): menu de profils restreint Claude/Codex + retrait custom (D7) — §17

Dernier lot de §17 : seuls les profils pilotables en mode structuré sont
proposés à la sélection/création.

- domain : AgentProfile::is_selectable() = structured_adapter.is_some(),
  source unique de vérité du prédicat.
- infrastructure : AgentSessionFactory::supports délègue à is_selectable
  (supports et is_selectable ne peuvent plus diverger).
- application : selectable_reference_profiles() = reference_profiles()
  filtré ; ReferenceProfiles et FirstRunState exposent la liste filtrée
  (Claude/Codex). reference_profiles() brut reste à 4 (data intacte) ⇒
  un agent Gemini/Aider/custom legacy déjà configuré continue de tourner.
- frontend : bloc AddCustomProfile retiré du wizard first-run, action
  addCustom retirée du viewmodel ; wizard n'affiche que la liste filtrée.

Tests (QA) : is_selectable (table de vérité), cohérence stricte
is_selectable<->supports, liste exposée=2 / data brute=4, garde
anti-régression Vitest sur l'absence du bloc custom — validées par
mutation. cargo test --workspace : 820 passed. npx vitest run : 344 passed.

§17 COMPLET (D0->D7). Suivi restant : D6b (surfacer reply dans le writer
wire .response.json pour la délégation par protocole fichier).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 23:57:51 +02:00
parent dd1194abe8
commit 6de4e5a6e0
15 changed files with 324 additions and 245 deletions

View File

@ -1,7 +1,7 @@
/**
* `useFirstRun` — view-model for the first-run wizard and profile management
* (L5). Owns the wizard state (reference candidates + selection + availability +
* custom profiles) and the actions. Consumes the {@link ProfileGateway}
* (L5). Owns the wizard state (selectable reference candidates + selection +
* availability) and the actions. Consumes the {@link ProfileGateway}
* exclusively — never `invoke()` — so the feature is testable with a mock.
*/
@ -27,7 +27,7 @@ export interface WizardEntry {
export interface FirstRunViewModel {
/** Whether the wizard should be shown (null while loading). */
isFirstRun: boolean | null;
/** The candidate rows (reference + added custom profiles). */
/** The candidate rows (selectable reference profiles). */
entries: WizardEntry[];
/** Last error message, or null. */
error: string | null;
@ -37,9 +37,7 @@ export interface FirstRunViewModel {
toggle: (id: string) => void;
/** Replaces a candidate's profile (edited command/args/injection). */
updateProfile: (id: string, profile: AgentProfile) => void;
/** Adds a custom profile (selected by default). */
addCustom: (profile: AgentProfile) => void;
/** Removes a candidate by id (e.g. a custom one). */
/** Removes a candidate by id. */
remove: (id: string) => void;
/** Runs detection over all candidates, filling availability. */
detect: () => Promise<void>;
@ -116,13 +114,6 @@ export function useFirstRun(): FirstRunViewModel {
);
}, []);
const addCustom = useCallback((p: AgentProfile) => {
setEntries((prev) => [
...prev,
{ profile: p, selected: true, available: null },
]);
}, []);
const remove = useCallback((id: string) => {
setEntries((prev) => prev.filter((e) => e.profile.id !== id));
}, []);
@ -169,7 +160,6 @@ export function useFirstRun(): FirstRunViewModel {
busy,
toggle,
updateProfile,
addCustom,
remove,
detect,
finish,