feat(wave): #119/#122/#131/#132 verts + sprint plugins ESM/persistance #135/#136/#139

État d'intégration confiné à la branche batch. Les tickets #119 (skills →
capacités agent découvrables), #122 (override permissions par défaut), #131
(effort par agent/presets) et #132 (outil MCP d'édition du contexte projet)
sont verts en périmètre. Le sprint plugins multi-fichiers ESM / persistance
plugin-owned (#135/#136/#139) est co-implémenté dans les MÊMES fichiers de
câblage (frontend/src/ports/index.ts, backend/src/lib.rs, domain/ports.rs,
backend/dto.rs), inséparable sans staging interactif (indisponible ici).

Commit unique volontaire : préserve l'état vert QA sans découpe hunk risquée.
NON mergé vers develop tant que #137 (QA e2e plugins) n'est pas vert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-03 11:06:23 +02:00
parent 22c6bd803d
commit 171c6c923c
59 changed files with 3654 additions and 291 deletions

View File

@ -579,6 +579,16 @@ pub enum DomainEventDto {
#[serde(skip_serializing_if = "Option::is_none")]
orchestrator: Option<String>,
},
/// The project's global context was written directly.
#[serde(rename_all = "camelCase")]
ProjectContextUpdated {
/// The project whose global context changed.
project_id: String,
/// Writer party (`"user"` or agent id).
by: String,
/// Epoch-milliseconds of the write.
at_ms: i64,
},
/// A memory note was created or updated.
#[serde(rename_all = "camelCase")]
MemorySaved {
@ -1213,6 +1223,15 @@ impl From<&DomainEvent> for DomainEventDto {
project_id: project_id.to_string(),
orchestrator: orchestrator.as_ref().map(|a| a.to_string()),
},
DomainEvent::ProjectContextUpdated {
project_id,
by,
at_ms,
} => Self::ProjectContextUpdated {
project_id: project_id.to_string(),
by: conversation_party_wire(*by),
at_ms: *at_ms,
},
DomainEvent::MemorySaved { slug } => Self::MemorySaved {
slug: slug.as_str().to_string(),
},
@ -1426,6 +1445,28 @@ mod tests {
);
}
#[test]
fn project_context_updated_relays_writer_to_wire() {
let project_id = ProjectId::from_uuid(uuid::Uuid::from_u128(1));
let writer = agent(2);
let dto = DomainEventDto::from(&DomainEvent::ProjectContextUpdated {
project_id,
by: ConversationParty::agent(writer),
at_ms: 987_654,
});
assert_eq!(
serde_json::to_value(&dto).unwrap(),
json!({
"type": "projectContextUpdated",
"projectId": project_id.to_string(),
"by": writer.to_string(),
"atMs": 987654,
})
);
}
#[test]
fn background_completion_relays_rendezvous_context_to_wire() {
let project_id = ProjectId::from_uuid(uuid::Uuid::from_u128(1));