fix(orchestrator,plugins): contact cross-project corrélé + découvrabilité assignation plugin par agent — #284 (backend: registre des demandes actives clé projet/agent appelant + correlationId, refus corrélation vide/dupliquée, cancel routé après correspondance exacte sans redirection vers une autre cible, appelant projeté Waiting→Done dans le live-state de son propre projet avec correlationId exposé par idea_workstate_read, gardes RAII réconcilient l'abandon du futur, tools ask/cancel WriteAction deny-by-default dont le refus indique où activer la permission; frontend: action Attribuer aux agents depuis Settings projet > Plugins ouvrant le panneau Agents flottant/ancré/détaché, section Capabilities de plugins — {agent} explicitant attribution skills et tools MCP des plugins activés; QA verte: orchestrator_service 79/79, mcp_server 40/40, domain live_state 15/15, correlated_update_is_preserved_in_the_lean_projection 1/1, frontend ProjectPluginSettings 3 tests, npm run build frontend, cargo fmt --check, git diff --check; AppImage IdeA_0.3.0_amd64.AppImage rebuildée workflow build-appimage, validation live cross-project restant après remplacement/relance manuels; réserves: registre corrélation en mémoire non persisté, surface web unsupported, catalogue dynamique tools MCP = réserve #282)

This commit is contained in:
2026-09-08 17:35:04 +02:00
parent df5728a50a
commit 8bfcb3a534
13 changed files with 604 additions and 35 deletions

View File

@ -106,6 +106,9 @@ pub struct LiveEntry {
pub progress: Option<String>,
/// The ticket of the most recent delegation this agent issued, if any.
pub last_delegation: Option<TicketId>,
/// External correlation carried by an active cross-project request, if any.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub correlation_id: Option<String>,
/// Wall-clock update time in epoch milliseconds (drives `prune` ordering).
pub updated_at_ms: u64,
}
@ -138,9 +141,35 @@ impl LiveEntry {
status,
progress,
last_delegation,
correlation_id: None,
updated_at_ms,
})
}
/// Attaches a validated external correlation id to this projection.
///
/// Unlike human-readable preview fields, correlation ids are never truncated:
/// changing the value would make cancellation routing ambiguous.
pub fn with_correlation_id(
mut self,
correlation_id: Option<String>,
) -> Result<Self, DomainError> {
if let Some(value) = correlation_id {
if value.trim().is_empty() {
return Err(DomainError::Invariant(
"live-state correlation id must not be empty".to_owned(),
));
}
if value.len() > FIELD_MAX_BYTES {
return Err(DomainError::Invariant(format!(
"live-state correlation id exceeds the max size of {FIELD_MAX_BYTES} bytes ({} bytes)",
value.len()
)));
}
self.correlation_id = Some(value);
}
Ok(self)
}
}
/// Enforces the field bounds: reject above the hard byte threshold, otherwise
@ -224,6 +253,7 @@ impl LiveState {
status: WorkStatus::Idle,
progress: Some(STALE_AT_RESTART_MARKER.to_owned()),
last_delegation: None,
correlation_id: e.correlation_id.clone(),
updated_at_ms: now_ms,
})
.collect()