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:
@ -85,6 +85,24 @@ impl UpdateLiveState {
|
||||
/// [`AppError::Invalid`] if a text field exceeds the domain hard threshold;
|
||||
/// [`AppError::Store`] on a persistence failure.
|
||||
pub async fn execute(&self, input: UpdateLiveStateInput) -> Result<(), AppError> {
|
||||
self.execute_with_correlation(input, None).await
|
||||
}
|
||||
|
||||
/// Publishes a transition carrying an external cross-project correlation id.
|
||||
pub async fn execute_correlated(
|
||||
&self,
|
||||
input: UpdateLiveStateInput,
|
||||
correlation_id: String,
|
||||
) -> Result<(), AppError> {
|
||||
self.execute_with_correlation(input, Some(correlation_id))
|
||||
.await
|
||||
}
|
||||
|
||||
async fn execute_with_correlation(
|
||||
&self,
|
||||
input: UpdateLiveStateInput,
|
||||
correlation_id: Option<String>,
|
||||
) -> Result<(), AppError> {
|
||||
let now_ms = u64::try_from(self.clock.now_millis()).unwrap_or(0);
|
||||
let entry = LiveEntry::new(
|
||||
input.agent_id,
|
||||
@ -95,6 +113,7 @@ impl UpdateLiveState {
|
||||
input.last_delegation,
|
||||
now_ms,
|
||||
)
|
||||
.and_then(|entry| entry.with_correlation_id(correlation_id))
|
||||
.map_err(|e| AppError::Invalid(e.to_string()))?;
|
||||
self.store.upsert(entry).await?;
|
||||
Ok(())
|
||||
@ -117,6 +136,9 @@ pub struct LeanLiveEntry {
|
||||
pub ticket: Option<TicketId>,
|
||||
/// The ticket of the most recent delegation it issued, if any.
|
||||
pub last_delegation: Option<TicketId>,
|
||||
/// External correlation for an active/completed cross-project request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub correlation_id: Option<String>,
|
||||
}
|
||||
|
||||
impl From<LiveEntry> for LeanLiveEntry {
|
||||
@ -127,6 +149,7 @@ impl From<LiveEntry> for LeanLiveEntry {
|
||||
status: e.status,
|
||||
ticket: e.ticket,
|
||||
last_delegation: e.last_delegation,
|
||||
correlation_id: e.correlation_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -246,6 +269,32 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn correlated_update_is_preserved_in_the_lean_projection() {
|
||||
let store = Arc::new(InMemoryLiveStateStore::default());
|
||||
let uc = UpdateLiveState::new(store.clone(), Arc::new(FixedClock(4_242)));
|
||||
|
||||
uc.execute_correlated(
|
||||
UpdateLiveStateInput {
|
||||
agent_id: aid(1),
|
||||
ticket: None,
|
||||
intent: "waiting for another project".to_owned(),
|
||||
status: WorkStatus::Waiting,
|
||||
progress: None,
|
||||
last_delegation: None,
|
||||
},
|
||||
"cross-284".to_owned(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let lean = GetLiveStateLean::new(store, Arc::new(FixedClock(4_242)))
|
||||
.execute()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(lean.entries[0].correlation_id.as_deref(), Some("cross-284"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_applies_domain_bounds_truncation() {
|
||||
let store = Arc::new(InMemoryLiveStateStore::default());
|
||||
|
||||
Reference in New Issue
Block a user