feat(templates): nom d'agent par défaut + permissions système et MCP outils sur AgentTemplate — #242

AgentTemplate étendu (defaultAgentName, systemPermissions, mcpToolPermissions),
CreateTemplate/UpdateTemplate/CreateAgentFromTemplate projetés vers l'agent créé,
DTO/MCP tools et persistance store complétés. Correctif modèle par défaut:
UpdateTemplate ne persistait pas defaultProfileId (profil stale à la création
d'agent). Backfill best-effort des templates existants depuis les agents du
projet au open_project, overrides explicites uniquement.
This commit is contained in:
2026-08-23 10:59:55 +02:00
parent ed5c5f73d6
commit d6c80be56d
11 changed files with 809 additions and 47 deletions

View File

@ -9,6 +9,7 @@ use domain::ids::{ProfileId, TemplateId};
use domain::markdown::MarkdownDoc;
use domain::ports::{FileSystem, RemotePath, StoreError, TemplateStore};
use domain::template::AgentTemplate;
use domain::{McpToolPolicy, NetworkPolicy, SystemPermissionSet};
use infrastructure::{FsTemplateStore, LocalFileSystem};
use uuid::Uuid;
@ -60,6 +61,17 @@ async fn save_then_get_and_list_roundtrip() {
let store = store(&tmp);
let t = template(tid(1), "Backend", "# Backend template");
let t = t
.with_metadata(
"Backend".to_owned(),
pid(2),
Some("Backend Agent".to_owned()),
Some(SystemPermissionSet::new(Some(NetworkPolicy::Allow))),
Some(McpToolPolicy {
allowed_tools: vec!["idea_template_read".to_owned()],
}),
)
.unwrap();
store.save(&t).await.unwrap();
assert_eq!(store.get(tid(1)).await.unwrap(), t);
@ -116,10 +128,18 @@ async fn delete_removes_from_index() {
async fn index_is_camelcase_with_content_hash() {
let tmp = TempDir::new();
let store = store(&tmp);
store
.save(&template(tid(1), "Backend", "hello"))
.await
let template = template(tid(1), "Backend", "hello")
.with_metadata(
"Backend".to_owned(),
pid(2),
Some("Backend Agent".to_owned()),
Some(SystemPermissionSet::new(Some(NetworkPolicy::Allow))),
Some(McpToolPolicy {
allowed_tools: vec!["idea_template_read".to_owned()],
}),
)
.unwrap();
store.save(&template).await.unwrap();
let fs = LocalFileSystem::new();
let bytes = fs.read(&tmp.child("templates/index.json")).await.unwrap();
@ -131,6 +151,26 @@ async fn index_is_camelcase_with_content_hash() {
"camelCase contentHash present"
);
assert!(entry.get("defaultProfileId").is_some());
assert_eq!(
entry.get("defaultAgentName").and_then(|v| v.as_str()),
Some("Backend Agent")
);
assert_eq!(
entry
.get("defaultSystemPermissions")
.and_then(|v| v.get("network"))
.and_then(|v| v.as_str()),
Some("allow")
);
assert_eq!(
entry
.get("defaultMcpToolPermissions")
.and_then(|v| v.get("allowedTools"))
.and_then(|v| v.as_array())
.and_then(|values| values.first())
.and_then(|v| v.as_str()),
Some("idea_template_read")
);
assert!(entry.get("content_hash").is_none(), "no snake_case leak");
}