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

@ -97,3 +97,25 @@ pub fn reference_profiles() -> Vec<AgentProfile> {
.expect("aider reference profile is valid"),
]
}
/// Returns the **selectable** subset of [`reference_profiles`] — the profiles the
/// first-run wizard and the agent-creation menu are allowed to offer (§17.3,
/// lot D7).
///
/// A profile is selectable iff it can be driven in **structured** mode
/// ([`AgentProfile::is_selectable`] = it carries a `structured_adapter`). Today
/// that is Claude + Codex; Gemini/Aider stay in [`reference_profiles`] (the data
/// catalogue is untouched) but are **not** proposed for selection. There is no
/// custom-profile entry here either: the selection path offers only profiles we
/// know how to pilot.
///
/// This filter is the single selection gate; `is_selectable` is the same
/// predicate the `AgentSessionFactory` uses to decide it `supports` a profile, so
/// the menu and the runtime can never disagree.
#[must_use]
pub fn selectable_reference_profiles() -> Vec<AgentProfile> {
reference_profiles()
.into_iter()
.filter(AgentProfile::is_selectable)
.collect()
}

View File

@ -17,7 +17,7 @@ pub(crate) use lifecycle::unique_md_path;
pub use structured::send_blocking;
pub use catalogue::{reference_profile_id, reference_profiles};
pub use catalogue::{reference_profile_id, reference_profiles, selectable_reference_profiles};
pub use inspect::{InspectConversation, InspectConversationInput, InspectConversationOutput};
pub use resume::{
ListResumableAgents, ListResumableAgentsInput, ListResumableAgentsOutput, ResumableAgent,

View File

@ -18,7 +18,7 @@ use domain::profile::AgentProfile;
use crate::error::AppError;
use super::catalogue::reference_profiles;
use super::catalogue::selectable_reference_profiles;
// ---------------------------------------------------------------------------
// DetectProfiles
@ -256,11 +256,15 @@ impl ConfigureProfiles {
/// Output of [`ReferenceProfiles::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReferenceProfilesOutput {
/// The pre-filled, editable reference catalogue.
/// The pre-filled, editable reference catalogue, **restricted to the
/// selectable profiles** (§17.3, D7): only profiles drivable in structured
/// mode are offered to selection/creation. Today: Claude + Codex.
pub profiles: Vec<AgentProfile>,
}
/// Exposes the pre-filled reference catalogue (Claude/Codex/Gemini/Aider).
/// Exposes the **selectable** reference catalogue for the agent-creation menu
/// (§17.3, D7): the structured-drivable profiles only (Claude/Codex). Gemini and
/// Aider remain in the raw catalogue data but are not proposed here.
#[derive(Default)]
pub struct ReferenceProfiles;
@ -278,7 +282,7 @@ impl ReferenceProfiles {
#[allow(clippy::unused_async)]
pub async fn execute(&self) -> Result<ReferenceProfilesOutput, AppError> {
Ok(ReferenceProfilesOutput {
profiles: reference_profiles(),
profiles: selectable_reference_profiles(),
})
}
}
@ -292,7 +296,9 @@ impl ReferenceProfiles {
pub struct FirstRunStateOutput {
/// `true` when no `profiles.json` exists yet ⇒ show the first-run wizard.
pub is_first_run: bool,
/// The pre-filled reference catalogue to seed the wizard.
/// The pre-filled reference catalogue to seed the wizard, **restricted to the
/// selectable profiles** (§17.3, D7): only structured-drivable profiles
/// (Claude/Codex) are offered. No custom-profile entry.
pub reference_profiles: Vec<AgentProfile>,
}
@ -317,7 +323,7 @@ impl FirstRunState {
let configured = self.store.is_configured().await?;
Ok(FirstRunStateOutput {
is_first_run: !configured,
reference_profiles: reference_profiles(),
reference_profiles: selectable_reference_profiles(),
})
}
}