feat(app): auto-update live-state depuis le cycle de délégation (LS3)

Branche la mise à jour automatique du live-state sur le cycle de
délégation de l'orchestrateur, en best-effort non bloquant.
- Trait `LiveStateProvider` (résolution par root) + champ `Option` dans
  l'`OrchestratorService`, injecté via le builder `with_live_state` ;
  `None` ⇒ aucun effet (zéro régression).
- Transitions dérivées du cycle : `ask` → entrée `Working`,
  `reply` → `Done` + `last_delegation`.
- Helpers best-effort : un échec de mise à jour du live-state ne
  transforme jamais un succès de délégation en erreur.
- `AppLiveStateProvider` + wiring côté app-tauri (provider par root).

cargo test -p application : 0 échec ; --test orchestrator_service : 55/0
(dont 4 nouveaux tests LS3) ; cargo fmt --all --check : exit 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 19:28:02 +02:00
parent 18401116aa
commit 9d46e6cd21
5 changed files with 467 additions and 42 deletions

View File

@ -22,17 +22,17 @@ use application::{
GitCommit, GitGraph, GitInit, GitLog, GitStage, GitStatus, GitUnstage, HarvestMemoryFromTurn,
HealthUseCase, InspectConversation, LaunchAgent, LaunchAgentInput, ListAgents, ListAgentsInput,
ListEmbedderProfiles, ListLayouts, ListMemories, ListProfiles, ListProjects,
ListResumableAgents, ListSkills, ListTemplates, LiveAgentRegistry, LiveSessions, LoadLayout,
McpRuntime, MoveTabToNewWindow, MutateLayout, OnnxModelView, OpenProject, OpenTerminal,
OrchestratorService, PermissionProjectorRegistry, ProposeContext, ReadAgentContext,
ReadContext, ReadMemory, ReadMemoryIndex, ReadProjectContext, ReadSkill, RecallMemory,
ReconcileLayouts, RecordTurn, RecordTurnProvider, ReferenceProfiles, RenameLayout,
ResizeTerminal, ResolveAgentPermissions, ResolveMemoryLinks, SaveEmbedderProfile, SaveProfile,
SessionLimitService, SetActiveLayout, SnapshotRunningAgents, StopLiveAgent, StructuredSessions,
SuggestedThisSession, SyncAgentWithTemplate, TerminalSessions, UnassignSkillFromAgent,
UpdateAgentContext, UpdateAgentPermissions, UpdateMemory, UpdateProjectContext,
UpdateProjectPermissions, UpdateSkill, UpdateTemplate, WriteMemory, WriteToTerminal,
AGENT_MEMORY_RECALL_BUDGET,
ListResumableAgents, ListSkills, ListTemplates, LiveAgentRegistry, LiveSessions,
LiveStateProvider, LoadLayout, McpRuntime, MoveTabToNewWindow, MutateLayout, OnnxModelView,
OpenProject, OpenTerminal, OrchestratorService, PermissionProjectorRegistry, ProposeContext,
ReadAgentContext, ReadContext, ReadMemory, ReadMemoryIndex, ReadProjectContext, ReadSkill,
RecallMemory, ReconcileLayouts, RecordTurn, RecordTurnProvider, ReferenceProfiles,
RenameLayout, ResizeTerminal, ResolveAgentPermissions, ResolveMemoryLinks, SaveEmbedderProfile,
SaveProfile, SessionLimitService, SetActiveLayout, SnapshotRunningAgents, StopLiveAgent,
StructuredSessions, SuggestedThisSession, SyncAgentWithTemplate, TerminalSessions,
UnassignSkillFromAgent, UpdateAgentContext, UpdateAgentPermissions, UpdateLiveState,
UpdateMemory, UpdateProjectContext, UpdateProjectPermissions, UpdateSkill, UpdateTemplate,
WriteMemory, WriteToTerminal, AGENT_MEMORY_RECALL_BUDGET,
};
use domain::ports::{
AgentContextStore, AgentRuntime, AgentSessionFactory, Clock, Embedder, EmbedderEnvInspector,
@ -52,8 +52,8 @@ use infrastructure::{
embedder_from_profile, AdaptiveMemoryRecall, ClaudePermissionProjector,
ClaudeTranscriptInspector, CliAgentRuntime, CodexPermissionProjector, EmbedderEnvProbe,
FsConversationLog, FsEmbedderProfileStore, FsEmbedderPromptStore, FsHandoffStore,
FsMemoryStore, FsOrchestratorWatcher, FsPermissionStore, FsProfileStore, FsProjectStore,
FsProviderSessionStore, FsSkillStore, FsTemplateStore, Git2Repository,
FsLiveStateStore, FsMemoryStore, FsOrchestratorWatcher, FsPermissionStore, FsProfileStore,
FsProjectStore, FsProviderSessionStore, FsSkillStore, FsTemplateStore, Git2Repository,
HeuristicHandoffSummarizer, IdeaiContextStore, InMemoryConversationRegistry, InMemoryMailbox,
LocalFileSystem, LocalProcessSpawner, McpServer, MediatedInbox, NaiveMemoryRecall,
OrchestratorWatchHandle, PortablePtyAdapter, RwFileGuard, StructuredSessionFactory,
@ -130,6 +130,29 @@ impl application::ConversationLogProvider for AppConversationLogProvider {
}
}
/// Implémente [`LiveStateProvider`] (programme live-state, lot LS3) en matérialisant un
/// [`UpdateLiveState`] dont le [`FsLiveStateStore`] cible le **project root** du tour.
///
/// Même raison d'être que [`AppRecordTurnProvider`] : l'[`OrchestratorService`] est
/// unique pour tous les projets, alors que le live-state est **par project root**
/// (`<root>/.ideai/live-state.json`) et le store fixe sa racine à la construction. On
/// construit donc un `UpdateLiveState` frais par transition, ciblant le bon dossier.
/// Porte l'horloge (port [`Clock`]) injectée au composition root — `UpdateLiveState`
/// l'utilise pour estampiller `updated_at_ms`.
struct AppLiveStateProvider {
clock: Arc<dyn Clock>,
}
impl LiveStateProvider for AppLiveStateProvider {
fn live_state_for(&self, root: &domain::project::ProjectPath) -> Option<Arc<UpdateLiveState>> {
let store = Arc::new(FsLiveStateStore::new(root));
Some(Arc::new(UpdateLiveState::new(
store,
Arc::clone(&self.clock),
)))
}
}
/// Implémente [`ProviderSessionProvider`](application::ProviderSessionProvider) (lot
/// P8b) en matérialisant un [`FsProviderSessionStore`] ciblant le **project root** du
/// lancement en cours.
@ -1196,7 +1219,15 @@ impl AppState {
)))
// Outil MCP `idea_skill_read` (feature « skills à la MCP ») : sans ça,
// `skill.read` renverrait « not configured ». Compose le SkillStore existant.
.with_read_skill(Arc::clone(&read_skill)),
.with_read_skill(Arc::clone(&read_skill))
// Auto-update du live-state (programme live-state, lot LS3) : dérivé des
// transitions de délégation (ask→Working, reply→Done), zéro token agent.
// Provider par root (`<root>/.ideai/live-state.json`) car le port fixe sa
// racine à la construction. Best-effort strict : un échec n'altère jamais la
// délégation.
.with_live_state(Arc::new(AppLiveStateProvider {
clock: Arc::clone(&clock) as Arc<dyn Clock>,
}) as Arc<dyn LiveStateProvider>),
// NB (régression corrigée) : on ne câble PAS `.with_structured(...)` ici.
// Décision produit lot B-2 (« Option 1 Terminal + MCP », cf. construction
// de `LaunchAgent` plus haut) : la fabrique structurée est décâblée, donc