feat(background): runner PTY B8 + boucle sink fermée + refactor point-2

Livre le lot backend B8 des tâches de fond :

- infrastructure : runner de commandes concret (CommandBackgroundRunner)
  sur le port BackgroundTaskRunner, tail borné (bounded_tail/BoundedTail),
  éclatement du module background_task en sous-modules (mod/runner/tail,
  sink extrait de l'ancien background_task.rs).
- application : nouveau module background exposant les cas d'usage
  SpawnBackgroundCommand, CancelBackgroundTask, RetryBackgroundTask et le
  port BackgroundCommandArchive.
- domain : refactor point-2 de l'arbitrage Architect — sortie du trait
  BackgroundCommandArchive de la couche domaine vers application.
- app-tauri : câblage runtime (commands, dto, state, lib) des commandes
  spawn/cancel/retry et de la boucle de complétion sink fermée en
  composition root.

Build workspace + tests application/infrastructure verts (QA).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 14:48:46 +02:00
parent 5d88c952ec
commit 8cac1470ac
12 changed files with 1180 additions and 11 deletions

View File

@ -13,7 +13,8 @@ use std::sync::{Arc, Mutex};
use application::{
AgentResumer, AgentWakeService, AppError, AssignSkillToAgent, AttachLiveAgent,
ChangeAgentProfile, CheckEmbedderSuggestion, CloseProject, CloseTab, CloseTerminal,
BackgroundCommandArchive, CancelBackgroundTask, ChangeAgentProfile, CheckEmbedderSuggestion,
CloseProject, CloseTab, CloseTerminal, RetryBackgroundTask, SpawnBackgroundCommand,
ConfigureProfiles, ContextGuardUseCases, CreateAgentFromScratch, CreateAgentFromTemplate,
CreateLayout, CreateMemory, CreateProject, CreateSkill, CreateTemplate, DeleteAgent,
DeleteEmbedderProfile, DeleteLayout, DeleteMemory, DeleteProfile, DeleteSkill, DeleteTemplate,
@ -39,7 +40,8 @@ use application::{
use async_trait::async_trait;
use domain::ports::{
AgentContextStore, AgentRuntime, AgentSession, AgentSessionFactory, AgentWakePort,
BackgroundTaskPortError, BackgroundTaskStore, Clock, Embedder, EmbedderEnvInspector,
BackgroundTaskPortError, BackgroundTaskRunner, BackgroundTaskStore,
Clock, Embedder, EmbedderEnvInspector,
EmbedderProfileStore, EmbedderPromptStore, EventBus, FileSystem, GitPort, IdGenerator,
MemoryRecall, MemoryStore, PermissionStore, ProcessSpawner, ProfileStore, ProjectStore,
PtyPort, ScheduledTask, Scheduler, SkillStore, TemplateStore, WakeError, WakeReason,
@ -57,8 +59,9 @@ use serde_json::{json, Map, Value};
use uuid::Uuid;
use infrastructure::{
embedder_from_profile, AdaptiveMemoryRecall, BackgroundTaskReadyToDeliver,
ClaudePermissionProjector, ClaudeTranscriptInspector, CliAgentRuntime,
embedder_from_profile, AdaptiveMemoryRecall, BackgroundCompletionSink,
BackgroundTaskReadyToDeliver, ClaudePermissionProjector, ClaudeTranscriptInspector,
CliAgentRuntime, CommandBackgroundRunner,
CodexPermissionProjector, EmbedderEnvProbe, FsBackgroundTaskStore, FsConversationLog,
FsEmbedderProfileStore, FsEmbedderPromptStore, FsHandoffStore, FsLiveStateStore, FsMemoryStore,
FsOrchestratorWatcher, FsPermissionStore, FsProfileStore, FsProjectStore,
@ -795,6 +798,15 @@ pub struct AppState {
// --- Windows (L10) ---
/// Detach a tab into a new OS window (persists the workspace topology).
pub move_tab: Arc<MoveTabToNewWindow>,
// --- Background tasks (B8) ---
/// Spawn a command-backed first-class background task.
pub spawn_background_command: Arc<SpawnBackgroundCommand>,
/// Cancel a running background task.
pub cancel_background_task: Arc<CancelBackgroundTask>,
/// Retry a terminal command task under a fresh task id.
pub retry_background_task: Arc<RetryBackgroundTask>,
/// Store handle used by `list_background_tasks` to read the task read-model.
pub background_task_store: Arc<dyn BackgroundTaskStore>,
// --- Templates & sync (L7) ---
/// Create a template in the global store.
pub create_template: Arc<CreateTemplate>,
@ -1541,6 +1553,49 @@ impl AppState {
let background_tasks_port = Arc::clone(&background_tasks) as Arc<dyn BackgroundTaskStore>;
let (background_ready_tx, mut background_ready_rx) =
tokio::sync::mpsc::unbounded_channel::<BackgroundTaskReadyToDeliver>();
// --- B8 : runner de commandes + fermeture de la boucle du sink ---
// Le runner concret exécute les tâches command-backed sur le `PtyPort` composé
// (local ici ; SSH/WSL via `RemoteHost` quand ils atterriront — Liskov). Il
// émet UNE complétion par tâche ; le sink (single-writer) persiste l'état
// terminal AVANT de signaler la livraison (persist-avant-signal), puis le pont
// ready→inbox ci-dessous réveille l'agent propriétaire.
let background_runner = Arc::new(CommandBackgroundRunner::new(
Arc::clone(&pty_port),
Arc::clone(&clock) as Arc<dyn Clock>,
));
let background_runner_port =
Arc::clone(&background_runner) as Arc<dyn BackgroundTaskRunner>;
{
let sink = Arc::new(BackgroundCompletionSink::new(
Arc::clone(&background_tasks_port),
background_ready_tx.clone(),
));
let runner = Arc::clone(&background_runner_port);
// `start_from_runner` exige un runtime Tokio ambiant (`Handle::current`) :
// on l'appelle donc DANS la tâche async. Le `JoinHandle` du drain est gardé
// vivant en l'attendant (il ne se termine qu'à la fermeture du flux de
// complétions), ce qui maintient sink + runner en vie pour la session.
tauri::async_runtime::spawn(async move {
let drain = sink.start_from_runner(runner);
let _ = drain.await;
});
}
let spawn_background_command = Arc::new(SpawnBackgroundCommand::new(
Arc::clone(&background_tasks_port),
Arc::clone(&background_runner_port),
Arc::clone(&clock) as Arc<dyn Clock>,
Arc::clone(&ids) as Arc<dyn IdGenerator>,
));
let cancel_background_task = Arc::new(CancelBackgroundTask::new(
Arc::clone(&background_tasks_port),
Arc::clone(&background_runner_port),
));
let retry_background_task = Arc::new(RetryBackgroundTask::new(
Arc::clone(&background_tasks_port),
Arc::clone(&background_runner) as Arc<dyn BackgroundCommandArchive>,
Arc::clone(&spawn_background_command),
));
let background_wake = Arc::new(AgentWakeService::new(
Arc::clone(&mediated_inbox) as Arc<dyn AgentInbox>,
Arc::clone(&input_mediator),
@ -1977,6 +2032,10 @@ impl AppState {
fs_port: Arc::clone(&fs_port),
home_dir,
move_tab,
spawn_background_command,
cancel_background_task,
retry_background_task,
background_task_store: Arc::clone(&background_tasks_port),
}
}