feat(slash): fondation du système de commandes slash pour la CLI custom — #162 (QA verte)

Introduit le contrat unifié des commandes slash consommable par le frontend,
indépendant de la source (native, puis plugin plus tard) :

- domain: modèle pur SlashCommand / SlashCommandSource / SlashCommandAvailability,
  métadonnées UI (nom, description, disponibilité, confirmation requise) et
  validation des noms/préfixes.
- application: registry + list/filter par préfixe + plan d'exécution natif ;
  commandes natives /help et /clean (/clean = nettoyage de la vue de session
  courante ; pas de /reset distinct tant qu'aucune utilité produit ne le justifie).
- backend + app-tauri: exposition du contrat via DTO/transport + tests DTO.

Source neutre : les commandes contribuées par plugins (#165) transiteront par
le même registry sans changer le contrat frontend.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 14:52:36 +02:00
parent 4e57161e1d
commit 6997138a71
11 changed files with 793 additions and 49 deletions

View File

@ -13,14 +13,15 @@ use application::{
AgentBackgroundTaskState, AgentTicketState, AppError, AppExitWorkGuardDetail,
AppExitWorkGuardState, AttachLiveAgentOutput, BackgroundTaskKindLabel,
ConversationPreviewStatus, ConversationTurnWorkPreview, ConversationWorkSummary,
CreateProjectInput, CreateProjectOutput, GitGraphOutput, HealthInput, HealthReport, LayoutKind,
ListProjectsOutput, LiveSessionKind, LiveSessionSnapshot, OpenProjectOutput, ProjectWorkState,
CreateProjectInput, CreateProjectOutput, ExecuteSlashCommandOutput, GitGraphOutput,
HealthInput, HealthReport, LayoutKind, ListProjectsOutput, ListSlashCommandsOutput,
LiveSessionKind, LiveSessionSnapshot, OpenProjectOutput, ProjectWorkState, SlashCommandEffect,
StopLiveAgentOutput, TicketWorkSource, TicketWorkStatus, TurnPage, TurnSource, TurnView,
};
use domain::ports::{ReplyProgress, ReplyProgressKind, ReplyProgressSource, ReplyProgressStage};
use domain::{
AgentBusyState, PageCursor, PageDirection, Project, ProjectId, ProjectSystemPermissions,
ResolvedAgentSystemPermissions, SystemPermissionSet, TurnRole,
ResolvedAgentSystemPermissions, SlashCommand, SystemPermissionSet, TurnRole,
};
pub use crate::ticket_dto::*;
@ -764,6 +765,106 @@ impl From<HealthReport> for HealthResponseDto {
}
}
/// Request DTO for slash-command list/filter.
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListSlashCommandsRequestDto {
/// Optional typed prefix, with or without the leading `/`.
#[serde(default)]
pub prefix: Option<String>,
}
/// UI-facing slash-command metadata DTO.
pub type SlashCommandDto = SlashCommand;
/// Slash-command list response DTO.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SlashCommandListDto {
/// Commands in deterministic UI order.
pub commands: Vec<SlashCommandDto>,
}
impl From<ListSlashCommandsOutput> for SlashCommandListDto {
fn from(value: ListSlashCommandsOutput) -> Self {
Self {
commands: value.commands,
}
}
}
/// Request DTO for slash-command execution.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExecuteSlashCommandRequestDto {
/// Command name, with or without the leading `/`.
pub name: String,
/// Current structured chat session id, required by `/clean` and future
/// conversation-local commands.
#[serde(default)]
pub session_id: Option<String>,
}
/// Slash-command execution response DTO.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExecuteSlashCommandResponseDto {
/// Executed command metadata.
pub command: SlashCommandDto,
/// Effect handled by the UI/adapter.
pub effect: SlashCommandEffectDto,
}
impl From<ExecuteSlashCommandOutput> for ExecuteSlashCommandResponseDto {
fn from(value: ExecuteSlashCommandOutput) -> Self {
Self {
command: value.command,
effect: value.effect.into(),
}
}
}
/// Slash-command execution effect DTO.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "kind")]
pub enum SlashCommandEffectDto {
/// Display command help.
Help {
/// Commands to display.
commands: Vec<SlashCommandDto>,
},
/// The current conversation was cleared.
CleanConversation {
/// Session id that was cleared.
#[serde(rename = "sessionId")]
session_id: String,
/// Number of retained chunks removed by the backend bridge.
#[serde(rename = "clearedChunks")]
cleared_chunks: usize,
},
/// Placeholder for ticket #164.
ProfileSwitch {
/// Session id targeted by the profile flow.
#[serde(rename = "sessionId")]
session_id: String,
},
}
impl From<SlashCommandEffect> for SlashCommandEffectDto {
fn from(value: SlashCommandEffect) -> Self {
match value {
SlashCommandEffect::Help { commands } => Self::Help { commands },
SlashCommandEffect::CleanConversation { session_id } => Self::CleanConversation {
session_id: session_id.to_string(),
cleared_chunks: 0,
},
SlashCommandEffect::ProfileSwitch { session_id } => Self::ProfileSwitch {
session_id: session_id.to_string(),
},
}
}
}
/// Error DTO returned to the frontend in the `Err` arm of every command.
///
/// `code` is a stable machine-readable string (see [`AppError::code`]); the