From 6cf421dbc731520e035d4aad0a4f12f26632ff53 Mon Sep 17 00:00:00 2001 From: vegard Date: Fri, 20 Mar 2026 06:18:53 +0000 Subject: [PATCH] Agent-trigger: permanent deltaker vs @bot-nevnelse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To spor for agent-respons: 1. Permanent: agent er member_of → svar på alle meldinger 2. @bot-nevnelse: bruker skriver @bot → engangs-svar, agent trenger ikke være deltaker Femkompis-problemet løst: @bot i en gruppechat gir ett svar, ikke evig bot-loop. Dedikerte bot-chatter (member_of) fungerer som før. Co-Authored-By: Claude Opus 4.6 (1M context) --- maskinrommet/src/agent.rs | 8 ++++ maskinrommet/src/intentions.rs | 84 ++++++++++++++++++++-------------- 2 files changed, 58 insertions(+), 34 deletions(-) diff --git a/maskinrommet/src/agent.rs b/maskinrommet/src/agent.rs index 7275182..5f6be44 100644 --- a/maskinrommet/src/agent.rs +++ b/maskinrommet/src/agent.rs @@ -203,6 +203,14 @@ async fn load_agent_config(db: &PgPool, agent_node_id: Uuid) -> Result Result, sqlx::Error> { + sqlx::query_scalar( + "SELECT node_id FROM agent_identities WHERE is_active = true ORDER BY created_at ASC LIMIT 1", + ).fetch_optional(db).await +} + +/// Finn agent som er permanent deltaker i en kommunikasjonsnode. pub async fn find_agent_participant(db: &PgPool, communication_id: Uuid) -> Result, sqlx::Error> { // Finn agent-deltaker uansett is_active — kill switch sjekkes i handleren. // Dette sikrer at jobben opprettes slik at eksterne handlers (Claude Code) kan plukke den. diff --git a/maskinrommet/src/intentions.rs b/maskinrommet/src/intentions.rs index 2af475a..31cf157 100644 --- a/maskinrommet/src/intentions.rs +++ b/maskinrommet/src/intentions.rs @@ -808,46 +808,62 @@ pub async fn create_node( None }; - // -- Agent-trigger: sjekk om kommunikasjonsnoden har en agent-deltaker -- + // -- Agent-trigger: to spor -- + // 1. Permanent deltaker: agent er member_of → svar på alle meldinger + // 2. @bot-nevnelse: bruker nevner @bot i teksten → engangs-svar if let Some(ctx_id) = req.context_id { let db_clone = state.db.clone(); let user_node_id = user.node_id; let created_node_id = node_id; + let message_content = content.clone(); tokio::spawn(async move { - match crate::agent::find_agent_participant(&db_clone, ctx_id).await { - Ok(Some(agent_id)) if agent_id != effective_identity => { - // Agent funnet, og melding er ikke fra agenten selv - let payload = serde_json::json!({ - "communication_id": ctx_id.to_string(), - "message_id": created_node_id.to_string(), - "agent_node_id": agent_id.to_string(), - "sender_node_id": user_node_id.to_string() - }); - match crate::jobs::enqueue(&db_clone, "agent_respond", payload, None, 8).await { - Ok(job_id) => { - tracing::info!( - job_id = %job_id, - communication_id = %ctx_id, - agent_node_id = %agent_id, - "agent_respond-jobb lagt i kø" - ); - } - Err(e) => { - tracing::error!( - communication_id = %ctx_id, - error = %e, - "Kunne ikke legge agent_respond-jobb i kø" - ); - } - } - } - Ok(_) => {} // Ingen agent, eller melding fra agenten selv + // Spor 1: Permanent deltaker (agent er member_of i chatten) + let permanent_agent = match crate::agent::find_agent_participant(&db_clone, ctx_id).await { + Ok(Some(agent_id)) if agent_id != effective_identity => Some(agent_id), + Ok(_) => None, Err(e) => { - tracing::error!( - communication_id = %ctx_id, - error = %e, - "Feil ved agent-deltaker-sjekk" - ); + tracing::error!(communication_id = %ctx_id, error = %e, "Feil ved agent-deltaker-sjekk"); + None + } + }; + + // Spor 2: @bot-nevnelse (agent er IKKE deltaker, men nevnes i teksten) + let mention_agent = if permanent_agent.is_none() && message_content.contains("@bot") { + match crate::agent::find_default_agent(&db_clone).await { + Ok(Some(agent_id)) if agent_id != effective_identity => Some(agent_id), + _ => None, + } + } else { + None + }; + + // Trigger jobb for den agenten som matcher + if let Some(agent_id) = permanent_agent.or(mention_agent) { + let trigger = if permanent_agent.is_some() { "permanent" } else { "mention" }; + let payload = serde_json::json!({ + "communication_id": ctx_id.to_string(), + "message_id": created_node_id.to_string(), + "agent_node_id": agent_id.to_string(), + "sender_node_id": user_node_id.to_string(), + "trigger": trigger + }); + match crate::jobs::enqueue(&db_clone, "agent_respond", payload, None, 8).await { + Ok(job_id) => { + tracing::info!( + job_id = %job_id, + communication_id = %ctx_id, + agent_node_id = %agent_id, + trigger = trigger, + "agent_respond-jobb lagt i kø" + ); + } + Err(e) => { + tracing::error!( + communication_id = %ctx_id, + error = %e, + "Kunne ikke legge agent_respond-jobb i kø" + ); + } } } });