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

@ -24,16 +24,16 @@ use application::{
ListResumableAgentsInput, ListSkillsInput, ListSlashCommandsInput, LiveSessions,
LoadLayoutInput, McpRuntime, MutateLayoutInput, OpenPluginLayoutWindowInput, OpenProjectInput,
ReadAgentContextInput, ReadConversationPageInput, ReadMcpToolPermissionsInput,
ReadMemoryIndexInput, ReadProjectContextInput, RecallMemoryInput, ReconcileLayoutsInput,
ReconcileLiveStateInput, RenameDeviceInput, RenameLayoutInput, ResolveAgentPermissionsInput,
ResolveAgentSystemPermissionsInput, ResolveMemoryLinksInput, RevokeDeviceInput,
RotateConversationLogInput, SaveChatDraftInput, SetActiveLayoutInput, SlashCommandEffect,
SnapshotRunningAgentsInput, StopLiveAgentInput, SyncAgentWithTemplateInput,
ReadMemoryIndexInput, ReadProjectContextInput, ReadTemplateInput, RecallMemoryInput,
ReconcileLayoutsInput, ReconcileLiveStateInput, RenameDeviceInput, RenameLayoutInput,
ResolveAgentPermissionsInput, ResolveAgentSystemPermissionsInput, ResolveMemoryLinksInput,
RevokeDeviceInput, RotateConversationLogInput, SaveChatDraftInput, SetActiveLayoutInput,
SlashCommandEffect, SnapshotRunningAgentsInput, StopLiveAgentInput, SyncAgentWithTemplateInput,
UnassignSkillFromAgentInput, UpdateAgentContextInput, UpdateAgentEffortInput,
UpdateAgentMcpToolPermissionsInput, UpdateAgentPermissionsInput,
UpdateAgentSystemPermissionsInput, UpdateMemoryInput, UpdateProjectContextInput,
UpdateProjectMcpToolPermissionsInput, UpdateProjectPermissionsInput,
UpdateProjectSystemPermissionsInput, UpdateSkillInput,
UpdateProjectSystemPermissionsInput, UpdateSkillInput, UpdateTemplateInput,
};
use backend::stream::OutputSink;
use domain::ports::ModelServerRuntime;
@ -91,7 +91,7 @@ use crate::embedded_server::{
use crate::pty::{PtyBridge, PtyChunk};
use crate::state::{AppState, FocusedProjectDto};
use crate::stream::TauriChannelSink;
use domain::{DeviceId, SkillRef, SkillScope};
use domain::{AgentOrigin, DeviceId, SkillRef, SkillScope};
use uuid::Uuid;
/// `health` — trivial command validating the full IPC pipeline
@ -492,12 +492,83 @@ pub(crate) async fn open_project_for_adapter(
// live-state : les Running sans handle vivant deviennent terminales, les
// complétions WakeOwner non livrées repassent dans l'inbox unifiée.
let _ = state.reconcile_background_tasks.execute(id).await;
let _ = backfill_agent_templates_from_project(&output.project, state).await;
// (Re)start the orchestrator watcher for this project (idempotent, §14.3).
state.ensure_orchestrator_watch(&output.project);
state.reconcile_claude_run_dirs(&output.project).await;
Ok(ProjectDto::from(output))
}
async fn backfill_agent_templates_from_project(
project: &domain::Project,
state: &AppState,
) -> Result<(), AppError> {
let agents = state
.list_agents
.execute(ListAgentsInput {
project: project.clone(),
})
.await?
.agents;
let system_permissions = state
.get_project_system_permissions
.execute(GetProjectSystemPermissionsInput {
project: project.clone(),
})
.await?
.permissions;
let mcp_tool_permissions = state
.read_mcp_tool_permissions
.execute(ReadMcpToolPermissionsInput {
project: project.clone(),
})
.await?
.permissions;
for agent in agents {
let AgentOrigin::FromTemplate { template_id, .. } = agent.origin else {
continue;
};
let template = match state
.read_template
.execute(ReadTemplateInput { template_id })
.await
{
Ok(out) => out.template,
Err(err) if err.code() == "NOT_FOUND" => continue,
Err(err) => return Err(err),
};
let default_system_permissions = system_permissions.agent_permissions(agent.id).cloned();
let default_mcp_tool_policy = mcp_tool_permissions
.agents
.iter()
.find(|entry| entry.agent_id == agent.id)
.map(|entry| entry.policy.clone());
if template.default_agent_name.as_deref() == Some(agent.name.as_str())
&& template.default_profile_id == agent.profile_id
&& template.default_system_permissions == default_system_permissions
&& template.default_mcp_tool_policy == default_mcp_tool_policy
{
continue;
}
state
.update_template
.execute(UpdateTemplateInput {
template_id,
content: None,
name: None,
default_profile_id: Some(agent.profile_id),
default_agent_name: Some(Some(agent.name)),
default_system_permissions: Some(default_system_permissions),
default_mcp_tool_policy: Some(default_mcp_tool_policy),
})
.await?;
}
Ok(())
}
/// `close_project` — persist state and release resources for a project.
///
/// # Errors