feat(workstate): snapshot des délégations en file par agent (Lot B backend)
Ajoute un port de lecture ségrégué `AgentQueueSnapshot` (ISP) distinct du `AgentMailbox` mutant : il expose `queue_for(agent)` qui renvoie des `QueuedTicketSnapshot` clonés, ordonnés FIFO, avec position recalculée (0 = tête). Observer la file ne la mute jamais ; le one-shot reply sender reste dans l'adaptateur. - domain : value object `QueuedTicketSnapshot` + trait `AgentQueueSnapshot` (object-safe, partagé en `Arc<dyn …>`). - infrastructure : `InMemoryMailbox` implémente la vue lecture en plus de la vue mutation ; positions recalculées à chaque appel. - application : le read-model work-state liste les délégations en attente via ce port, troncature de l'aperçu de tâche en conservant la longueur d'origine. - app-tauri : DTO `camelCase` des tickets en file câblé dans l'état. Tests verts : domain mailbox (6), infrastructure mailbox (13), application workstate (12), app-tauri dto_agents (20). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -9,8 +9,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use application::{
|
||||
AppError, CreateProjectInput, CreateProjectOutput, GitGraphOutput, HealthInput, HealthReport,
|
||||
LayoutKind, ListProjectsOutput, LiveSessionKind, OpenProjectOutput, ProjectWorkState,
|
||||
AgentTicketState, AppError, CreateProjectInput, CreateProjectOutput, GitGraphOutput,
|
||||
HealthInput, HealthReport, LayoutKind, ListProjectsOutput, LiveSessionKind, OpenProjectOutput,
|
||||
ProjectWorkState, TicketWorkSource, TicketWorkStatus,
|
||||
};
|
||||
use domain::{AgentBusyState, Project, ProjectId};
|
||||
|
||||
@ -1503,6 +1504,87 @@ pub struct LiveWorkSessionDto {
|
||||
pub kind: LiveWorkSessionKindDto,
|
||||
}
|
||||
|
||||
/// Derived processing status of a queued ticket.
|
||||
#[derive(Debug, Clone, Copy, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum TicketWorkStatusDto {
|
||||
/// The agent's current busy turn is running this ticket.
|
||||
InProgress,
|
||||
/// Waiting behind the head / the agent is idle.
|
||||
Queued,
|
||||
}
|
||||
|
||||
impl From<TicketWorkStatus> for TicketWorkStatusDto {
|
||||
fn from(status: TicketWorkStatus) -> Self {
|
||||
match status {
|
||||
TicketWorkStatus::InProgress => Self::InProgress,
|
||||
TicketWorkStatus::Queued => Self::Queued,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Origin of a queued ticket (human operator or a delegating agent).
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase", tag = "kind")]
|
||||
pub enum TicketWorkSourceDto {
|
||||
/// The human operator.
|
||||
Human,
|
||||
/// Another agent delegating via `idea_ask_agent`.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Agent {
|
||||
/// The delegating agent id.
|
||||
agent_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<TicketWorkSource> for TicketWorkSourceDto {
|
||||
fn from(source: TicketWorkSource) -> Self {
|
||||
match source {
|
||||
TicketWorkSource::Human => Self::Human,
|
||||
TicketWorkSource::Agent { agent_id } => Self::Agent {
|
||||
agent_id: agent_id.to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One queued/in-progress delegation ticket for an agent.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AgentTicketStateDto {
|
||||
/// Stable id of the queued ticket.
|
||||
pub ticket_id: String,
|
||||
/// Conversation thread this task enters.
|
||||
pub conversation_id: String,
|
||||
/// FIFO position at snapshot time (`0` = head).
|
||||
pub position: u32,
|
||||
/// Derived status (in-progress vs queued).
|
||||
pub status: TicketWorkStatusDto,
|
||||
/// Origin of the ticket.
|
||||
pub source: TicketWorkSourceDto,
|
||||
/// Display label of the requester.
|
||||
pub requester_label: String,
|
||||
/// Bounded excerpt of the task.
|
||||
pub task_preview: String,
|
||||
/// Character length of the original (un-truncated) task.
|
||||
pub task_len: usize,
|
||||
}
|
||||
|
||||
impl From<AgentTicketState> for AgentTicketStateDto {
|
||||
fn from(ticket: AgentTicketState) -> Self {
|
||||
Self {
|
||||
ticket_id: ticket.ticket_id.to_string(),
|
||||
conversation_id: ticket.conversation_id.to_string(),
|
||||
position: ticket.position,
|
||||
status: ticket.status.into(),
|
||||
source: ticket.source.into(),
|
||||
requester_label: ticket.requester_label,
|
||||
task_preview: ticket.task_preview,
|
||||
task_len: ticket.task_len,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One manifest agent's current live/busy state.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -1517,6 +1599,8 @@ pub struct AgentWorkStateDto {
|
||||
pub live: Option<LiveWorkSessionDto>,
|
||||
/// Current mediated-input busy state.
|
||||
pub busy: AgentBusyState,
|
||||
/// Pending/in-progress delegation tickets, in FIFO order.
|
||||
pub tickets: Vec<AgentTicketStateDto>,
|
||||
}
|
||||
|
||||
/// Project-level read model for conversation/delegation UX.
|
||||
@ -1543,6 +1627,11 @@ impl From<ProjectWorkState> for ProjectWorkStateDto {
|
||||
kind: live.kind.into(),
|
||||
}),
|
||||
busy: agent.busy,
|
||||
tickets: agent
|
||||
.tickets
|
||||
.into_iter()
|
||||
.map(AgentTicketStateDto::from)
|
||||
.collect(),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user