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:
@ -503,6 +503,48 @@ impl AppReconcileBackgroundTasks {
|
||||
}
|
||||
}
|
||||
|
||||
fn schedule_background_ready_retry(
|
||||
ready_tx: tokio::sync::mpsc::UnboundedSender<BackgroundTaskReadyToDeliver>,
|
||||
ready: BackgroundTaskReadyToDeliver,
|
||||
) {
|
||||
tauri::async_runtime::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
let _ = ready_tx.send(ready);
|
||||
});
|
||||
}
|
||||
|
||||
fn schedule_background_wake_retry(
|
||||
wake: Arc<dyn AgentWakePort>,
|
||||
project: Project,
|
||||
owner_agent_id: AgentId,
|
||||
task_id: TaskId,
|
||||
) {
|
||||
tauri::async_runtime::spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
match wake
|
||||
.wake_agent(
|
||||
&project,
|
||||
owner_agent_id,
|
||||
WakeReason::BackgroundCompletion { task_id },
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => break,
|
||||
Err(WakeError::AgentBusy { .. }) => continue,
|
||||
Err(err) => {
|
||||
application::diag!(
|
||||
"[background-task] completion wake retry failed: task={} owner={} err={err}",
|
||||
task_id,
|
||||
owner_agent_id
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
struct AppWakeSessionProvider {
|
||||
launch_agent: Arc<LaunchAgent>,
|
||||
structured_sessions: Arc<StructuredSessions>,
|
||||
@ -1690,6 +1732,7 @@ impl AppState {
|
||||
let wake = Arc::clone(&background_wake);
|
||||
let projects = Arc::clone(&store_port);
|
||||
let clock_for_items = Arc::clone(&clock) as Arc<dyn Clock>;
|
||||
let retry_ready = background_ready_tx.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
while let Some(ready) = background_ready_rx.recv().await {
|
||||
let item = InboxItem {
|
||||
@ -1712,6 +1755,7 @@ impl AppState {
|
||||
ready.owner_agent_id,
|
||||
receipt.depth
|
||||
);
|
||||
schedule_background_ready_retry(retry_ready.clone(), ready);
|
||||
continue;
|
||||
}
|
||||
Ok(_) => {}
|
||||
@ -1722,6 +1766,7 @@ impl AppState {
|
||||
ready.task_id,
|
||||
ready.owner_agent_id
|
||||
);
|
||||
schedule_background_ready_retry(retry_ready.clone(), ready);
|
||||
continue;
|
||||
}
|
||||
Err(err) => {
|
||||
@ -1758,6 +1803,20 @@ impl AppState {
|
||||
)
|
||||
.await
|
||||
{
|
||||
if matches!(err, WakeError::AgentBusy { .. }) {
|
||||
application::diag!(
|
||||
"[background-task] completion wake postponed: task={} owner={}",
|
||||
ready.task_id,
|
||||
ready.owner_agent_id
|
||||
);
|
||||
schedule_background_wake_retry(
|
||||
Arc::clone(&wake),
|
||||
project,
|
||||
ready.owner_agent_id,
|
||||
ready.task_id,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
application::diag!(
|
||||
"[background-task] completion wake failed: task={} owner={} err={err}",
|
||||
ready.task_id,
|
||||
@ -1801,7 +1860,8 @@ impl AppState {
|
||||
Arc::new(AppHandoffProvider) as Arc<dyn application::HandoffProvider>,
|
||||
Arc::new(AppConversationLogProvider)
|
||||
as Arc<dyn application::ConversationLogProvider>,
|
||||
),
|
||||
)
|
||||
.with_background_tasks(Arc::clone(&background_tasks_port)),
|
||||
);
|
||||
// Lot LS6 — rotation (hors chemin chaud) + lecture humaine paginée. Tous deux
|
||||
// composent le provider d'archive par root ; la rotation lit aussi le handoff
|
||||
|
||||
Reference in New Issue
Block a user