feat(background): livraison auto au propriétaire + projection des tâches de fond dans le work-state (T1+T3)
T1 — Wake automatique du propriétaire à la complétion. La complétion d'une tâche de fond est désormais livrée à l'agent propriétaire dès que la session accepte l'envoi (wake.rs : mark_completion_delivered au send accepté), via un drain de flux dédié (structured.rs : drain_reply_stream_with_readiness). L'inbox médiée enfile l'item sans démarrer de tour ni marquer l'agent busy (input/mod.rs : enqueue FIFO silencieux). Régression couverte (tests/agent_wake.rs, tests input/mod.rs). T3 — Tâches de fond projetées dans le read-model du panneau Work. AgentWorkState porte désormais background_tasks (VO AgentBackgroundTaskState), alimenté par un builder best-effort with_background_tasks(store) : union list_open_for_agent + dispatch des completions non livrées par owner_agent_id, erreur store => Vec vide (aucune régression live/busy/tickets). DTO Tauri backgroundTasks et wiring du BackgroundTaskStore côté state.rs. Le frontend, déjà câblé, affiche Cancel/Retry (mapping queued/waiting -> pending, tri sur updatedAtMs). Borne V1 : une tâche terminale déjà livrée n'est plus énumérable (Retry limité à la fenêtre non livrée). Tests : cargo build --workspace OK ; cargo test -p application / -p app-tauri / -p infrastructure verts ; frontend build + vitest verts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -1077,10 +1077,7 @@ impl AgentInbox for MediatedInbox {
|
||||
let item_id = item.id;
|
||||
let ticket = inbox_item_to_ticket(&item);
|
||||
self.inbox_items().insert(item_id, item);
|
||||
let _pending = match ticket.source {
|
||||
domain::input::InputSource::Human => self.enqueue(agent, ticket),
|
||||
domain::input::InputSource::Agent { .. } => self.enqueue_silent(agent, ticket),
|
||||
};
|
||||
let _pending = self.mailbox.enqueue(agent, ticket);
|
||||
let depth = self.mailbox.pending(&agent);
|
||||
if let Some(events) = &self.tracker.events {
|
||||
events.publish(DomainEvent::AgentInboxQueued {
|
||||
@ -1347,6 +1344,36 @@ mod tests {
|
||||
assert_eq!(inbox.mailbox.pending(&a), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enqueue_message_queues_inbox_item_without_starting_turn() {
|
||||
let bus = Arc::new(RecordingBus::default());
|
||||
let inbox = MediatedInbox::new(Arc::new(InMemoryMailbox::new()), Arc::new(FixedClock(1)))
|
||||
.with_events(Arc::clone(&bus) as Arc<dyn EventBus>);
|
||||
let a = agent(1);
|
||||
let task_id = domain::TaskId::from_uuid(uuid::Uuid::from_u128(42));
|
||||
let item = domain::InboxItem {
|
||||
id: TicketId::from_uuid(uuid::Uuid::from_u128(10)),
|
||||
agent_id: a,
|
||||
source: InboxSource::BackgroundTask { task_id },
|
||||
kind: domain::InboxItemKind::BackgroundCompletion,
|
||||
body: "Background task completed.".to_owned(),
|
||||
created_at_ms: 1,
|
||||
correlation_id: Some(task_id.to_string()),
|
||||
};
|
||||
|
||||
let receipt = inbox.enqueue_message(a, item.clone()).unwrap();
|
||||
|
||||
assert_eq!(receipt.status, InboxReceiptStatus::Queued);
|
||||
assert_eq!(inbox.busy_state(a), AgentBusyState::Idle);
|
||||
assert_eq!(inbox.mailbox.pending(&a), 1);
|
||||
assert!(
|
||||
bus.busy_events().is_empty(),
|
||||
"inbox enqueue must not start a mediated turn"
|
||||
);
|
||||
assert_eq!(inbox.dequeue_next(a), Some(item));
|
||||
assert_eq!(inbox.mailbox.pending(&a), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delegation_ready_carries_bound_submit_config() {
|
||||
let bus = Arc::new(RecordingBus::default());
|
||||
|
||||
Reference in New Issue
Block a user