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:
@ -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
|
||||
|
||||
@ -23,18 +23,19 @@ use application::{
|
||||
CreateSprint, CreateTemplate, DeleteAgent, DeleteEmbedderProfile, DeleteIssue, DeleteLayout,
|
||||
DeleteMemory, DeleteModelArtifact, DeleteModelServer, DeleteProfile, DeleteSkill, DeleteSprint,
|
||||
DeleteTemplate, DescribeEmbedderEngines, DetectAgentDrift, DetectProfiles,
|
||||
DismissEmbedderSuggestion, EnsureLocalModelServer, FirstRunState, GetAppExitWorkGuardState,
|
||||
GetLiveStateLean, GetMemory, GetProjectPermissions, GetProjectSystemPermissions,
|
||||
GetProjectWorkState, GitBranches, GitCheckout, GitCommit, GitGraph, GitInit, GitLog, GitStage,
|
||||
GitStatus, GitUnstage, HarvestMemoryFromTurn, HealthUseCase, ImportChatAttachments,
|
||||
InspectConversation, InstallPluginFromArchive, InstallPluginFromDirectory,
|
||||
JsonPluginManifestValidator, LaunchAgent, LaunchAgentInput, LinkIssues, ListAgents,
|
||||
ListAgentsInput, ListClaudeModels, ListCodexModels, ListDevices, ListEmbedderProfiles,
|
||||
ListIssues, ListLayouts, ListMemories, ListModelServers, ListOpenCodeProviders,
|
||||
ListPluginRuntimeContributions, ListPlugins, ListProfiles, ListProjects, ListResumableAgents,
|
||||
ListSkills, ListSprints, ListTemplates, LiveAgentRegistry, LiveSessions, LiveStateLeanProvider,
|
||||
LiveStateProvider, LiveStateReadProvider, LoadLayout, MarkIssueAttachmentSummarized,
|
||||
McpRuntime, McpToolPermissionCatalogue, MoveTabToNewWindow, MutateLayout, OnnxModelView,
|
||||
DismissEmbedderSuggestion, EnsureLocalModelServer, ExecuteSlashCommand, FirstRunState,
|
||||
GetAppExitWorkGuardState, GetLiveStateLean, GetMemory, GetProjectPermissions,
|
||||
GetProjectSystemPermissions, GetProjectWorkState, GitBranches, GitCheckout, GitCommit,
|
||||
GitGraph, GitInit, GitLog, GitStage, GitStatus, GitUnstage, HarvestMemoryFromTurn,
|
||||
HealthUseCase, ImportChatAttachments, InspectConversation, InstallPluginFromArchive,
|
||||
InstallPluginFromDirectory, JsonPluginManifestValidator, LaunchAgent, LaunchAgentInput,
|
||||
LinkIssues, ListAgents, ListAgentsInput, ListClaudeModels, ListCodexModels, ListDevices,
|
||||
ListEmbedderProfiles, ListIssues, ListLayouts, ListMemories, ListModelServers,
|
||||
ListOpenCodeProviders, ListPluginRuntimeContributions, ListPlugins, ListProfiles, ListProjects,
|
||||
ListResumableAgents, ListSkills, ListSlashCommands, ListSprints, ListTemplates,
|
||||
LiveAgentRegistry, LiveSessions, LiveStateLeanProvider, LiveStateProvider,
|
||||
LiveStateReadProvider, LoadLayout, MarkIssueAttachmentSummarized, McpRuntime,
|
||||
McpToolPermissionCatalogue, MoveTabToNewWindow, MutateLayout, OnnxModelView,
|
||||
OpenPluginLayoutWindow, OpenProject, OpenTerminal, OpenTicketAssistant, OrchestratorService,
|
||||
PairAttemptLimiter, PairDevice, PermissionProjectorRegistry, PluginCommandTasks,
|
||||
PluginConfigDocuments, PluginEventSubscriptions, PluginStorageAccess,
|
||||
@ -48,15 +49,15 @@ use application::{
|
||||
ResolveMemoryLinks, RestoreOpenWindows, RetryBackgroundTask, ReviewPluginPackage,
|
||||
RevokeAllDevices, RevokeDevice, RotateConversationLog, SaveEmbedderProfile, SaveModelServer,
|
||||
SaveOpenCodeProviderProfile, SaveProfile, SessionLimitService, SetActiveLayout,
|
||||
SetPluginEnabled, SnapshotOpenWindows, SnapshotRunningAgents, SpawnBackgroundCommand,
|
||||
StopLiveAgent, StructuredRoutingMode, StructuredSessions, SuggestedThisSession,
|
||||
SyncAgentWithTemplate, TerminalSessions, TouchDevice, UnassignSkillFromAgent,
|
||||
UnassignTicketFromSprint, UninstallPlugin, UnlinkIssues, UpdateAgentContext, UpdateAgentEffort,
|
||||
UpdateAgentMcpToolPermissions, UpdateAgentPermissions, UpdateAgentSystemPermissions,
|
||||
UpdateIssue, UpdateIssueCarnet, UpdateLiveState, UpdateMemory, UpdateProjectContext,
|
||||
UpdateProjectMcpToolPermissions, UpdateProjectPermissions, UpdateProjectSystemPermissions,
|
||||
UpdateSkill, UpdateTemplate, WakeSessionProvider, WriteMemory, WriteToTerminal,
|
||||
AGENT_MEMORY_RECALL_BUDGET,
|
||||
SetPluginEnabled, SlashCommandRegistry, SnapshotOpenWindows, SnapshotRunningAgents,
|
||||
SpawnBackgroundCommand, StopLiveAgent, StructuredRoutingMode, StructuredSessions,
|
||||
SuggestedThisSession, SyncAgentWithTemplate, TerminalSessions, TouchDevice,
|
||||
UnassignSkillFromAgent, UnassignTicketFromSprint, UninstallPlugin, UnlinkIssues,
|
||||
UpdateAgentContext, UpdateAgentEffort, UpdateAgentMcpToolPermissions, UpdateAgentPermissions,
|
||||
UpdateAgentSystemPermissions, UpdateIssue, UpdateIssueCarnet, UpdateLiveState, UpdateMemory,
|
||||
UpdateProjectContext, UpdateProjectMcpToolPermissions, UpdateProjectPermissions,
|
||||
UpdateProjectSystemPermissions, UpdateSkill, UpdateTemplate, WakeSessionProvider, WriteMemory,
|
||||
WriteToTerminal, AGENT_MEMORY_RECALL_BUDGET,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use domain::ports::{
|
||||
@ -877,6 +878,10 @@ impl AgentResumer for AppAgentResumer {
|
||||
pub struct BackendCore {
|
||||
/// Trivial health use case validating the end-to-end wiring.
|
||||
pub health: Arc<HealthUseCase>,
|
||||
/// List/filter the unified slash-command registry.
|
||||
pub list_slash_commands: Arc<ListSlashCommands>,
|
||||
/// Plan execution of a slash command.
|
||||
pub execute_slash_command: Arc<ExecuteSlashCommand>,
|
||||
/// Pair a persistent device session.
|
||||
pub pair_device: Arc<PairDevice>,
|
||||
/// Limits failed pairing attempts.
|
||||
@ -1448,6 +1453,9 @@ impl BackendCore {
|
||||
Arc::clone(&ids) as Arc<dyn IdGenerator>,
|
||||
Arc::clone(&events_port),
|
||||
));
|
||||
let slash_command_registry = SlashCommandRegistry::new();
|
||||
let list_slash_commands = Arc::new(ListSlashCommands::new(slash_command_registry.clone()));
|
||||
let execute_slash_command = Arc::new(ExecuteSlashCommand::new(slash_command_registry));
|
||||
let pair_device = Arc::new(PairDevice::new(
|
||||
Arc::clone(&device_session_port),
|
||||
Arc::clone(&ids) as Arc<dyn IdGenerator>,
|
||||
@ -2924,6 +2932,8 @@ impl BackendCore {
|
||||
|
||||
Self {
|
||||
health,
|
||||
list_slash_commands,
|
||||
execute_slash_command,
|
||||
pair_device,
|
||||
pair_attempt_limiter: Arc::clone(&pair_attempt_limiter_port),
|
||||
authenticate_session,
|
||||
|
||||
@ -156,6 +156,20 @@ where
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn clear_scrollback(&self, key: &K) -> usize {
|
||||
self.entries
|
||||
.lock()
|
||||
.ok()
|
||||
.and_then(|mut m| {
|
||||
m.get_mut(key).map(|entry| {
|
||||
let cleared = entry.scrollback.len();
|
||||
entry.scrollback.clear();
|
||||
cleared
|
||||
})
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn unregister(&self, key: &K) {
|
||||
if let Ok(mut map) = self.entries.lock() {
|
||||
map.remove(key);
|
||||
|
||||
Reference in New Issue
Block a user