feat: livrable ticket #91 — notification fin BackgroundTask enrichie
- Backend: enrichissement HeadlessRendezvous avec requester/target/conversationId - Frontend: toast 'Requester -> Target completed/...' explicite - Clic ouvrant viewer de conversation pour visualiser l'échange
This commit is contained in:
@ -2611,6 +2611,15 @@ pub struct AgentBackgroundTaskStateDto {
|
||||
/// Bounded stderr tail.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stderr_tail: Option<String>,
|
||||
/// Agent that requested a headless rendezvous, when known.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub requester_agent_id: Option<String>,
|
||||
/// Target agent for a headless rendezvous.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub target_agent_id: Option<String>,
|
||||
/// Conversation opened by a headless rendezvous.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub conversation_id: Option<String>,
|
||||
/// Creation timestamp, epoch milliseconds.
|
||||
pub created_at_ms: u64,
|
||||
/// Last update timestamp, epoch milliseconds.
|
||||
@ -2627,6 +2636,9 @@ impl From<AgentBackgroundTaskState> for AgentBackgroundTaskStateDto {
|
||||
summary: task.summary,
|
||||
stdout_tail: task.stdout_tail,
|
||||
stderr_tail: task.stderr_tail,
|
||||
requester_agent_id: task.requester_agent_id.map(|id| id.to_string()),
|
||||
target_agent_id: task.target_agent_id.map(|id| id.to_string()),
|
||||
conversation_id: task.conversation_id.map(|id| id.to_string()),
|
||||
created_at_ms: task.created_at_ms,
|
||||
updated_at_ms: task.updated_at_ms,
|
||||
}
|
||||
@ -4052,6 +4064,15 @@ pub struct BackgroundTaskDto {
|
||||
/// Bounded stderr tail (unset for PTY-backed commands, which merge streams).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stderr_tail: Option<String>,
|
||||
/// Agent that requested a headless rendezvous, when known.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub requester_agent_id: Option<String>,
|
||||
/// Target agent for a headless rendezvous.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub target_agent_id: Option<String>,
|
||||
/// Conversation opened by a headless rendezvous.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub conversation_id: Option<String>,
|
||||
/// Creation timestamp, epoch milliseconds.
|
||||
pub created_at_ms: u64,
|
||||
/// Last update timestamp, epoch milliseconds.
|
||||
@ -4093,6 +4114,8 @@ fn background_state_label(state: BackgroundTaskState) -> &'static str {
|
||||
|
||||
impl From<BackgroundTask> for BackgroundTaskDto {
|
||||
fn from(task: BackgroundTask) -> Self {
|
||||
let (requester_agent_id, target_agent_id, conversation_id) =
|
||||
background_rendezvous_context_labels(&task.kind);
|
||||
let (exit_code, summary, stdout_tail, stderr_tail) = match &task.result {
|
||||
Some(BackgroundTaskResult::Success {
|
||||
exit_code,
|
||||
@ -4134,12 +4157,33 @@ impl From<BackgroundTask> for BackgroundTaskDto {
|
||||
summary,
|
||||
stdout_tail,
|
||||
stderr_tail,
|
||||
requester_agent_id,
|
||||
target_agent_id,
|
||||
conversation_id,
|
||||
created_at_ms: task.created_at_ms,
|
||||
updated_at_ms: task.updated_at_ms,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn background_rendezvous_context_labels(
|
||||
kind: &BackgroundTaskKind,
|
||||
) -> (Option<String>, Option<String>, Option<String>) {
|
||||
match kind {
|
||||
BackgroundTaskKind::HeadlessRendezvous {
|
||||
requester_agent_id,
|
||||
target_agent_id,
|
||||
conversation_id,
|
||||
..
|
||||
} => (
|
||||
requester_agent_id.map(|id| id.to_string()),
|
||||
Some(target_agent_id.to_string()),
|
||||
Some(conversation_id.to_string()),
|
||||
),
|
||||
_ => (None, None, None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a task-id string (UUID) coming from the frontend.
|
||||
///
|
||||
/// # Errors
|
||||
@ -4188,7 +4232,8 @@ pub struct SpawnBackgroundCommandRequestDto {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use application::McpToolPermissionCatalogue;
|
||||
use domain::{AgentId, ProjectMcpToolPermissions};
|
||||
use domain::mailbox::TicketId;
|
||||
use domain::{AgentId, ConversationId, ProjectMcpToolPermissions};
|
||||
use serde_json::json;
|
||||
use uuid::Uuid;
|
||||
|
||||
@ -4239,4 +4284,78 @@ mod tests {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn background_task_dto_exposes_rendezvous_context_only_for_headless_rendezvous() {
|
||||
let project_id = ProjectId::from_uuid(Uuid::from_u128(1));
|
||||
let owner = AgentId::from_uuid(Uuid::from_u128(2));
|
||||
let requester = AgentId::from_uuid(Uuid::from_u128(3));
|
||||
let conversation_id = ConversationId::from_uuid(Uuid::from_u128(4));
|
||||
let task = BackgroundTask::new(
|
||||
TaskId::from_uuid(Uuid::from_u128(5)),
|
||||
project_id,
|
||||
owner,
|
||||
BackgroundTaskKind::HeadlessRendezvous {
|
||||
requester_agent_id: Some(requester),
|
||||
target_agent_id: owner,
|
||||
ticket_id: TicketId::from_uuid(Uuid::from_u128(6)),
|
||||
conversation_id,
|
||||
},
|
||||
domain::BackgroundTaskWakePolicy::RecordOnly,
|
||||
100,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let json = serde_json::to_value(BackgroundTaskDto::from(task)).unwrap();
|
||||
|
||||
assert_eq!(json["kind"], "headlessRendezvous");
|
||||
assert_eq!(json["requesterAgentId"], requester.to_string());
|
||||
assert_eq!(json["targetAgentId"], owner.to_string());
|
||||
assert_eq!(json["conversationId"], conversation_id.to_string());
|
||||
|
||||
let command = BackgroundTask::new(
|
||||
TaskId::from_uuid(Uuid::from_u128(7)),
|
||||
project_id,
|
||||
owner,
|
||||
BackgroundTaskKind::Command {
|
||||
label: "cargo test".to_owned(),
|
||||
},
|
||||
domain::BackgroundTaskWakePolicy::RecordOnly,
|
||||
100,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let json = serde_json::to_value(BackgroundTaskDto::from(command)).unwrap();
|
||||
assert!(json.get("requesterAgentId").is_none());
|
||||
assert!(json.get("targetAgentId").is_none());
|
||||
assert!(json.get("conversationId").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agent_background_task_state_dto_exposes_rendezvous_context() {
|
||||
let requester = AgentId::from_uuid(Uuid::from_u128(11));
|
||||
let target = AgentId::from_uuid(Uuid::from_u128(12));
|
||||
let conversation_id = ConversationId::from_uuid(Uuid::from_u128(13));
|
||||
let state = AgentBackgroundTaskState {
|
||||
task_id: TaskId::from_uuid(Uuid::from_u128(14)),
|
||||
kind: BackgroundTaskKindLabel::HeadlessRendezvous,
|
||||
state: BackgroundTaskState::Completed,
|
||||
exit_code: None,
|
||||
summary: Some("ok".to_owned()),
|
||||
stdout_tail: None,
|
||||
stderr_tail: None,
|
||||
requester_agent_id: Some(requester),
|
||||
target_agent_id: Some(target),
|
||||
conversation_id: Some(conversation_id),
|
||||
created_at_ms: 100,
|
||||
updated_at_ms: 200,
|
||||
};
|
||||
|
||||
let json = serde_json::to_value(AgentBackgroundTaskStateDto::from(state)).unwrap();
|
||||
|
||||
assert_eq!(json["requesterAgentId"], requester.to_string());
|
||||
assert_eq!(json["targetAgentId"], target.to_string());
|
||||
assert_eq!(json["conversationId"], conversation_id.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user