Agent handler_mode: internal/external/paused — robust meldingsruting
Tre moduser i agent_identities.handler_mode: - internal: maskinrommet kjører synops-respond (eksternt API) - external: jobb settes til 'deferred', forblir urørt for Claude Code - paused: svar bruker med "AI utilgjengelig", marker done Jobbkøen overskriver ikke deferred-status (sjekker result.status). Ny job_status 'deferred' i PG enum. Scripts: vaktmester-poll.sh (finn deferred jobber), vaktmester-complete.sh (marker behandlet). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
db7a8f4a15
commit
956fbc124c
4 changed files with 93 additions and 10 deletions
|
|
@ -55,23 +55,57 @@ pub async fn handle_agent_respond(
|
||||||
|
|
||||||
let config = load_agent_config(db, agent_node_id).await?;
|
let config = load_agent_config(db, agent_node_id).await?;
|
||||||
|
|
||||||
// --- Sikkerhetskontroller (forblir i maskinrommet) ---
|
// --- Handler-modus og sikkerhetskontroller ---
|
||||||
|
|
||||||
// Kill switch
|
// Sjekk handler_mode: internal, external, disabled
|
||||||
let is_active: bool = sqlx::query_scalar(
|
let (is_active, handler_mode): (bool, String) = sqlx::query_as::<_, (bool, String)>(
|
||||||
"SELECT is_active FROM agent_identities WHERE node_id = $1",
|
"SELECT is_active, handler_mode FROM agent_identities WHERE node_id = $1",
|
||||||
).bind(agent_node_id).fetch_optional(db).await
|
).bind(agent_node_id).fetch_optional(db).await
|
||||||
.map_err(|e| format!("DB-feil: {e}"))?.unwrap_or(false);
|
.map_err(|e| format!("DB-feil: {e}"))?
|
||||||
if !is_active {
|
.unwrap_or((false, "disabled".to_string()));
|
||||||
// La jobben ligge for ekstern handler (Claude Code polling)
|
|
||||||
sqlx::query("UPDATE job_queue SET status = 'pending', started_at = NULL WHERE id = $1")
|
// Disabled: dropp jobben
|
||||||
|
if !is_active || handler_mode == "disabled" {
|
||||||
|
return Ok(serde_json::json!({"status": "skipped", "reason": "agent_disabled"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// External: la jobben ligge for ekstern handler (Claude Code / synops-agent)
|
||||||
|
if handler_mode == "external" {
|
||||||
|
sqlx::query("UPDATE job_queue SET status = 'deferred'::job_status, started_at = NULL WHERE id = $1")
|
||||||
.bind(job.id)
|
.bind(job.id)
|
||||||
.execute(db)
|
.execute(db)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("DB-feil ved tilbakestilling: {e}"))?;
|
.map_err(|e| format!("DB-feil ved tilbakestilling: {e}"))?;
|
||||||
return Ok(serde_json::json!({"status": "deferred", "reason": "agent_inactive_external_handler"}));
|
tracing::info!(
|
||||||
|
job_id = %job.id,
|
||||||
|
"agent_respond deferred til ekstern handler (handler_mode=external)"
|
||||||
|
);
|
||||||
|
return Ok(serde_json::json!({"status": "deferred", "reason": "external_handler"}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Paused: svar brukeren at AI er utilgjengelig
|
||||||
|
if handler_mode == "paused" {
|
||||||
|
let pause_msg = "AI-assistenten er midlertidig utilgjengelig. Meldingen din er registrert.";
|
||||||
|
// Skriv svar i chatten
|
||||||
|
sqlx::query(
|
||||||
|
"WITH new_node AS (
|
||||||
|
INSERT INTO nodes (id, node_kind, content, visibility, created_by)
|
||||||
|
VALUES (gen_random_uuid(), 'content', $1, 'hidden', $2)
|
||||||
|
RETURNING id
|
||||||
|
)
|
||||||
|
INSERT INTO edges (id, source_id, target_id, edge_type)
|
||||||
|
SELECT gen_random_uuid(), id, $3, 'belongs_to' FROM new_node"
|
||||||
|
)
|
||||||
|
.bind(pause_msg)
|
||||||
|
.bind(agent_node_id)
|
||||||
|
.bind(communication_id)
|
||||||
|
.execute(db).await
|
||||||
|
.map_err(|e| format!("DB-feil ved pause-svar: {e}"))?;
|
||||||
|
return Ok(serde_json::json!({"status": "paused", "reason": "agent_paused"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal: fortsett med synops-respond (under)
|
||||||
|
|
||||||
// Rate limiting
|
// Rate limiting
|
||||||
let count: i64 = sqlx::query_scalar::<_, Option<i64>>(
|
let count: i64 = sqlx::query_scalar::<_, Option<i64>>(
|
||||||
"SELECT COUNT(*) FROM ai_usage_log WHERE agent_node_id = $1 AND created_at > now() - interval '1 hour'",
|
"SELECT COUNT(*) FROM ai_usage_log WHERE agent_node_id = $1 AND created_at > now() - interval '1 hour'",
|
||||||
|
|
|
||||||
|
|
@ -882,7 +882,11 @@ pub fn start_worker(
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(Ok(res)) => {
|
Ok(Ok(res)) => {
|
||||||
if let Err(e) = complete_job(&db2, job.id, res).await {
|
// Deferred-jobber eies av ekstern handler — ikke marker completed
|
||||||
|
let is_deferred = res.get("status").and_then(|s| s.as_str()) == Some("deferred");
|
||||||
|
if is_deferred {
|
||||||
|
tracing::info!(job_id = %job.id, "Jobb deferred til ekstern handler");
|
||||||
|
} else if let Err(e) = complete_job(&db2, job.id, res).await {
|
||||||
tracing::error!(job_id = %job.id, error = %e, "Kunne ikke markere jobb som fullført");
|
tracing::error!(job_id = %job.id, error = %e, "Kunne ikke markere jobb som fullført");
|
||||||
} else {
|
} else {
|
||||||
tracing::info!(job_id = %job.id, "Jobb fullført");
|
tracing::info!(job_id = %job.id, "Jobb fullført");
|
||||||
|
|
|
||||||
13
scripts/vaktmester-complete.sh
Executable file
13
scripts/vaktmester-complete.sh
Executable file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Marker en deferred jobb som completed etter at Claude Code har svart.
|
||||||
|
# Bruk: ./scripts/vaktmester-complete.sh <job-id>
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
JOB_ID="${1:?Mangler job-id}"
|
||||||
|
|
||||||
|
docker exec sidelinja-postgres-1 psql -U sidelinja -d synops -q -c "
|
||||||
|
UPDATE job_queue SET status = 'completed', completed_at = now(),
|
||||||
|
result = '{\"status\":\"completed\",\"handler\":\"claude-code\"}'::jsonb
|
||||||
|
WHERE id = '$JOB_ID';
|
||||||
|
"
|
||||||
|
echo "Jobb $JOB_ID markert som completed"
|
||||||
32
scripts/vaktmester-poll.sh
Executable file
32
scripts/vaktmester-poll.sh
Executable file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Poll for deferred agent_respond-jobber og returner info for Claude Code.
|
||||||
|
# Brukes av Claude Code sin cron-loop.
|
||||||
|
#
|
||||||
|
# Output: JSON med jobb-info hvis det finnes en jobb, ellers "none"
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CHAT_ID="abe2edfd-986b-45ba-8c2e-4461a8a7e480"
|
||||||
|
|
||||||
|
# Hent eldste deferred jobb
|
||||||
|
JOB=$(docker exec sidelinja-postgres-1 psql -U sidelinja -d synops -t -A -c "
|
||||||
|
SELECT row_to_json(t) FROM (
|
||||||
|
SELECT j.id as job_id, j.payload, j.created_at,
|
||||||
|
n.content as message,
|
||||||
|
n.created_by as sender_id,
|
||||||
|
(SELECT title FROM nodes WHERE id = n.created_by) as sender_name
|
||||||
|
FROM job_queue j
|
||||||
|
JOIN nodes n ON n.id = (j.payload->>'message_id')::uuid
|
||||||
|
WHERE j.job_type = 'agent_respond'
|
||||||
|
AND j.status = 'deferred'
|
||||||
|
ORDER BY j.created_at ASC
|
||||||
|
LIMIT 1
|
||||||
|
) t;
|
||||||
|
" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -z "$JOB" ] || [ "$JOB" = "" ]; then
|
||||||
|
echo "none"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$JOB"
|
||||||
Loading…
Add table
Reference in a new issue