feat(cross-project): découverte et contact inter-projets — tools MCP idea_project_{list,read_context,list_agents,ask_agent,cancel_agent}, permission contactable par agent + default_contactable persistée dans les templates, routage orchestrator par projectId+agentId — #260 (tests ciblés verts, QA en attente)

This commit is contained in:
2026-09-07 16:31:14 +02:00
parent d7b24ad183
commit 98954986a7
18 changed files with 721 additions and 15 deletions

View File

@ -75,6 +75,9 @@ pub struct Agent {
/// `model_reasoning_effort` at launch.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub effort: Option<EffortSelection>,
/// Whether this agent may be discovered/contacted from another project.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub contactable: bool,
}
impl Agent {
@ -110,6 +113,7 @@ impl Agent {
synchronized,
skills: Vec::new(),
effort: None,
contactable: false,
})
}
@ -141,6 +145,13 @@ impl Agent {
self
}
/// Returns a copy with the cross-project contact permission changed.
#[must_use]
pub const fn with_contactable(mut self, contactable: bool) -> Self {
self.contactable = contactable;
self
}
/// Assigns a skill to this agent. Idempotent: re-assigning the same
/// `skill_id` is a no-op (returns `false`); a new assignment returns `true`.
pub fn assign_skill(&mut self, skill: SkillRef) -> bool {
@ -199,6 +210,9 @@ pub struct ManifestEntry {
/// Per-agent effort selection. Missing in older manifests means no override.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub effort: Option<EffortSelection>,
/// Whether this agent may be discovered/contacted from another project.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub contactable: bool,
}
impl ManifestEntry {
@ -238,6 +252,7 @@ impl ManifestEntry {
synced_template_version,
skills: Vec::new(),
effort: None,
contactable: false,
})
}
@ -264,6 +279,7 @@ impl ManifestEntry {
synced_template_version,
skills: agent.skills.clone(),
effort: agent.effort.clone(),
contactable: agent.contactable,
}
}
@ -290,7 +306,8 @@ impl ManifestEntry {
self.synchronized,
)?
.with_skills(self.skills.clone())
.with_effort(self.effort.clone()))
.with_effort(self.effort.clone())
.with_contactable(self.contactable))
}
}