feat(tickets): création d'un assistant IA de ticket (backend + frontend)
Ajoute le chat assistant IA attaché à un ticket (#8). Backend Rust : - use cases OpenTicketAssistant/CloseTicketAssistant + tests - politique d'outils par agent (domain/agent_tool_policy) et policy MCP - store de contexte assistant + gabarit default_ticket_assistant.md + tests - events TicketAssistantOpened/Closed - commandes Tauri open_ticket_chat/close_ticket_chat et câblage state/lib/events Frontend : - gateway (ports, adapters ticket + mock, domain) - hook useTicketAssistant + composant TicketAssistantPanel - intégration dans TicketDetail Tests verts : vitest tickets.test.tsx (23), cargo test application::ticket_assistant (1), infrastructure::assistant_context_store (2). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -14,15 +14,16 @@ use tauri::State;
|
||||
use uuid::Uuid;
|
||||
|
||||
use application::{
|
||||
AppError, AssignIssueAgentInput, AssignTicketToSprintInput, CreateIssueInput,
|
||||
CreateSprintInput, DeleteSprintInput, LinkIssuesInput, ListIssuesInput, ListSprintsInput,
|
||||
OpenProjectInput, ReadIssueCarnetInput, ReadIssueInput, RenameSprintInput, ReorderSprintsInput,
|
||||
UnassignTicketFromSprintInput, UnlinkIssuesInput, UpdateIssueCarnetInput, UpdateIssueInput,
|
||||
AppError, AssignIssueAgentInput, AssignTicketToSprintInput, CloseTicketAssistantInput,
|
||||
CreateIssueInput, CreateSprintInput, DeleteSprintInput, LinkIssuesInput, ListIssuesInput,
|
||||
ListSprintsInput, OpenProjectInput, OpenTicketAssistantInput, ReadIssueCarnetInput,
|
||||
ReadIssueInput, RenameSprintInput, ReorderSprintsInput, UnassignTicketFromSprintInput,
|
||||
UnlinkIssuesInput, UpdateIssueCarnetInput, UpdateIssueInput,
|
||||
};
|
||||
use domain::{
|
||||
AgentId, AgentIssueRole, Issue, IssueActor, IssueCarnet, IssueIndexEntry, IssueLink,
|
||||
IssueLinkKind, IssueListFilter, IssuePriority, IssueRef, IssueStatus, IssueVersion, Project,
|
||||
ProjectId, SprintId, SprintStatus, SprintVersion,
|
||||
IssueLinkKind, IssueListFilter, IssuePriority, IssueRef, IssueStatus, IssueVersion, ProfileId,
|
||||
Project, ProjectId, SprintId, SprintStatus, SprintVersion,
|
||||
};
|
||||
use infrastructure::{TicketToolError, TicketToolProvider};
|
||||
|
||||
@ -106,6 +107,29 @@ pub struct SprintListDto {
|
||||
pub items: Vec<SprintDto>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OpenTicketChatRequestDto {
|
||||
pub project_id: String,
|
||||
pub issue_ref: String,
|
||||
pub profile_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CloseTicketChatRequestDto {
|
||||
pub project_id: String,
|
||||
pub issue_ref: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TicketChatDto {
|
||||
pub session_id: String,
|
||||
pub requester: String,
|
||||
pub issue_ref: String,
|
||||
}
|
||||
|
||||
/// Public link DTO.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -588,6 +612,43 @@ pub async fn ticket_read(
|
||||
Ok(TicketDto::from_issue(issue, carnet))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn open_ticket_chat(
|
||||
request: OpenTicketChatRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<TicketChatDto, ErrorDto> {
|
||||
let project = resolve_project(&state, &request.project_id).await?;
|
||||
let output = state
|
||||
.open_ticket_assistant
|
||||
.execute(OpenTicketAssistantInput {
|
||||
project,
|
||||
issue_ref: parse_ref_dto(&request.issue_ref)?,
|
||||
profile_id: parse_profile_id_dto(&request.profile_id)?,
|
||||
})
|
||||
.await
|
||||
.map_err(ErrorDto::from)?;
|
||||
Ok(TicketChatDto {
|
||||
session_id: output.session_id.to_string(),
|
||||
requester: output.requester,
|
||||
issue_ref: output.issue_ref.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn close_ticket_chat(
|
||||
request: CloseTicketChatRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<(), ErrorDto> {
|
||||
let _project = resolve_project(&state, &request.project_id).await?;
|
||||
state
|
||||
.close_ticket_assistant
|
||||
.execute(CloseTicketAssistantInput {
|
||||
issue_ref: parse_ref_dto(&request.issue_ref)?,
|
||||
})
|
||||
.await
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn ticket_list(
|
||||
request: TicketListRequestDto,
|
||||
@ -1234,6 +1295,12 @@ fn parse_agent_id_dto(raw: &str) -> Result<AgentId, ErrorDto> {
|
||||
.map_err(|e| ErrorDto::invalid(format!("invalid agent id: {e}")))
|
||||
}
|
||||
|
||||
fn parse_profile_id_dto(raw: &str) -> Result<ProfileId, ErrorDto> {
|
||||
Uuid::parse_str(raw)
|
||||
.map(ProfileId::from_uuid)
|
||||
.map_err(|e| ErrorDto::invalid(format!("invalid profile id: {e}")))
|
||||
}
|
||||
|
||||
fn parse_sprint_id_dto(raw: &str) -> Result<SprintId, ErrorDto> {
|
||||
Uuid::parse_str(raw)
|
||||
.map(SprintId::from_uuid)
|
||||
|
||||
Reference in New Issue
Block a user