rename agents/main→assistant, role-based default entry agent
Nightly Build / build (push) Successful in 6m31s

This commit is contained in:
2026-07-21 21:40:06 +01:00
parent 17f5769e0d
commit 8ff64cbddc
22 changed files with 204 additions and 44 deletions
+5 -2
View File
@@ -30,7 +30,7 @@ use super::{ApiError, guard::AuthUser, require_context};
pub const PROJECT_SOURCE_PREFIX: &str = "project-";
/// Agent that drives interactive project-chat sessions.
const PROJECT_COORDINATOR_AGENT: &str = "project-coordinator";
pub(crate) const PROJECT_COORDINATOR_AGENT: &str = "project-coordinator";
// ── Request/Response types ────────────────────────────────────────────────────
@@ -339,7 +339,10 @@ pub async fn provisioning_for_source(
.strip_prefix(PROJECT_SOURCE_PREFIX)
.and_then(|s| s.parse::<i64>().ok())
else {
return Ok(("main".to_string(), None));
// Non-project source → the caller's role-assigned entry agent (same resolver
// the per-user hub uses, so explicit-create and lazy-create never diverge).
let agent = skald_core::db::roles::default_chat_agent_for_user(skald.db(), user_id).await;
return Ok((agent, None));
};
let (project, _can_write) = require_member(skald, id, user_id).await?;
+25 -1
View File
@@ -3,16 +3,38 @@ use std::sync::Arc;
use axum::{Json, extract::{Path, State}};
use serde::Deserialize;
use skald_core::db::roles::{self, ADMIN_ROLE_ID, Role};
use skald_core::agents::{self, AgentType};
use skald_core::db::roles::{self, ADMIN_ROLE_ID, RoleAttrs, Role};
use skald_core::skald::Skald;
use super::ApiError;
use super::projects::PROJECT_COORDINATOR_AGENT;
pub async fn list(State(skald): State<Arc<Skald>>) -> Result<Json<Vec<Role>>, ApiError> {
let roles = roles::list(skald.db()).await?;
Ok(Json(roles))
}
/// If the role's `attrs.chat_agent` is set, it must name an existing **chat** agent
/// other than the source-bound `project-coordinator` (which needs a project run-context
/// and is never a sensible personal default). Empty/absent is fine — the resolver falls
/// back to `DEFAULT_CHAT_AGENT`. Shared by create + update so the two can't drift.
fn validate_chat_agent(attrs: &Option<String>) -> Result<(), ApiError> {
let Some(agent) = RoleAttrs::from_opt(attrs).chat_agent.filter(|s| !s.trim().is_empty()) else {
return Ok(());
};
if agent == PROJECT_COORDINATOR_AGENT {
return Err(ApiError::bad_request(
"project-coordinator is source-driven and cannot be a role's default assistant",
));
}
match agents::load_meta(&agent) {
Ok(meta) if meta.agent_type == AgentType::Chat => Ok(()),
Ok(_) => Err(ApiError::bad_request(format!("agent '{agent}' is not a chat agent"))),
Err(_) => Err(ApiError::bad_request(format!("unknown agent '{agent}'"))),
}
}
#[derive(Deserialize)]
pub struct CreateBody {
pub id: String,
@@ -35,6 +57,7 @@ pub async fn create(
if body.permission_group.trim().is_empty() {
return Err(ApiError::bad_request("permission group must not be empty"));
}
validate_chat_agent(&body.attrs)?;
roles::insert(skald.db(), id, body.label.trim(), &body.permission_group, body.attrs.as_deref())
.await?;
// Seed the standard self-service Connector capabilities (§14): a new role can
@@ -66,6 +89,7 @@ pub async fn update(
if body.permission_group.trim().is_empty() {
return Err(ApiError::bad_request("permission group must not be empty"));
}
validate_chat_agent(&body.attrs)?;
let ok = roles::update(skald.db(), &id, body.label.trim(), &body.permission_group, body.attrs.as_deref())
.await?;
if !ok {