feat(agent): add Projects + Compliance agents, webhook notifications
BOC CI/CD / Test (push) Failing after 2s
BOC CI/CD / Security Scan (push) Has been skipped
BOC CI/CD / Build & Push (push) Has been skipped
BOC CI/CD / Deploy to Staging (push) Has been skipped
BOC CI/CD / Deploy to Production (push) Has been skipped

- Add AgentFAB to ProjectsPage and CompliancePage
- Add AgentWebhookNotifier for agent events (activated, message, escalation, error)
- Webhook URL configurable via AGENT_WEBHOOK_URL env var
- Build fresh web-v2 dist
This commit is contained in:
Bernt
2026-08-12 09:42:37 +00:00
parent f259610f8c
commit 8cb26cdb15
6 changed files with 137 additions and 0 deletions
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+11
View File
@@ -38,6 +38,7 @@ type AgentOrchestrator struct {
anthropicKey string
apiEndpoint string
dataProvider *AgentDataProvider
webhook *AgentWebhookNotifier
}
// NewAgentOrchestrator creates a new agent orchestrator
@@ -51,6 +52,7 @@ func NewAgentOrchestrator(db, ledgerDB *sql.DB) *AgentOrchestrator {
anthropicKey: key,
apiEndpoint: "https://api.anthropic.com/v1/messages",
dataProvider: NewAgentDataProvider(db, ledgerDB),
webhook: NewAgentWebhookNotifier(os.Getenv("AGENT_WEBHOOK_URL")),
}
}
@@ -100,6 +102,10 @@ func (o *AgentOrchestrator) HandleAgentChat(w http.ResponseWriter, r *http.Reque
svar, err := o.callAnthropic(enhancedPrompt, req.Meddelanden)
if err != nil {
log.Error().Err(err).Str("rum", req.Rum).Msg("Agent chat failed")
// Notify error
if o.webhook != nil {
o.webhook.NotifyAgentError(req.Rum, "", err.Error())
}
// Fallback to mock
mockSvar := generateMockResponse(req.Rum, req.Meddelanden)
respondWithJSON(w, AgentChatResponse{
@@ -110,6 +116,11 @@ func (o *AgentOrchestrator) HandleAgentChat(w http.ResponseWriter, r *http.Reque
return
}
// Notify message sent
if o.webhook != nil {
o.webhook.NotifyAgentMessage(req.Rum, "", "response")
}
respondWithJSON(w, AgentChatResponse{
Svar: svar,
Rum: req.Rum,
+118
View File
@@ -0,0 +1,118 @@
package handlers
import (
"bytes"
"encoding/json"
"net/http"
"time"
"github.com/rs/zerolog/log"
)
// AgentWebhookEvent representerar en agent-händelse
type AgentWebhookEvent struct {
EventType string `json:"event_type"`
AgentRum string `json:"agent_rum"`
Timestamp string `json:"timestamp"`
UserID string `json:"user_id,omitempty"`
Payload map[string]interface{} `json:"payload"`
}
// AgentWebhookNotifier skickar agent-händelser till konfigurerade webhooks
type AgentWebhookNotifier struct {
webhookURL string
client *http.Client
enabled bool
}
// NewAgentWebhookNotifier skapar en ny notifier
func NewAgentWebhookNotifier(webhookURL string) *AgentWebhookNotifier {
return &AgentWebhookNotifier{
webhookURL: webhookURL,
client: &http.Client{Timeout: 10 * time.Second},
enabled: webhookURL != "",
}
}
// Notify skickar en händelse till webhook
func (n *AgentWebhookNotifier) Notify(event AgentWebhookEvent) {
if !n.enabled {
return
}
event.Timestamp = time.Now().Format(time.RFC3339)
jsonData, err := json.Marshal(event)
if err != nil {
log.Error().Err(err).Str("event", event.EventType).Msg("Failed to marshal webhook event")
return
}
go func() {
resp, err := n.client.Post(n.webhookURL, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
log.Warn().Err(err).Str("url", n.webhookURL).Msg("Webhook delivery failed")
return
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
log.Warn().Int("status", resp.StatusCode).Str("url", n.webhookURL).Msg("Webhook returned error")
}
}()
}
// NotifyAgentActivated skickas när en agent aktiveras
func (n *AgentWebhookNotifier) NotifyAgentActivated(rum, userID string) {
n.Notify(AgentWebhookEvent{
EventType: "agent.activated",
AgentRum: rum,
UserID: userID,
Payload: map[string]interface{}{
"rum": rum,
"user_id": userID,
},
})
}
// NotifyAgentMessage skickas när en agent skickar ett meddelande
func (n *AgentWebhookNotifier) NotifyAgentMessage(rum, userID, messageType string) {
n.Notify(AgentWebhookEvent{
EventType: "agent.message",
AgentRum: rum,
UserID: userID,
Payload: map[string]interface{}{
"rum": rum,
"user_id": userID,
"message_type": messageType,
},
})
}
// NotifyAgentEscalation skickas när en agent eskalerar
func (n *AgentWebhookNotifier) NotifyAgentEscalation(rum, userID, reason string) {
n.Notify(AgentWebhookEvent{
EventType: "agent.escalation",
AgentRum: rum,
UserID: userID,
Payload: map[string]interface{}{
"rum": rum,
"user_id": userID,
"reason": reason,
},
})
}
// NotifyAgentError skickas när en agent stöter på ett fel
func (n *AgentWebhookNotifier) NotifyAgentError(rum, userID, errorMsg string) {
n.Notify(AgentWebhookEvent{
EventType: "agent.error",
AgentRum: rum,
UserID: userID,
Payload: map[string]interface{}{
"rum": rum,
"user_id": userID,
"error": errorMsg,
},
})
}
+4
View File
@@ -14,6 +14,7 @@ import {
Clock,
TrendingUp,
} from 'lucide-react'
import { AgentFAB } from '@/components/agent/AgentFAB'
export function CompliancePage() {
const [activeTab, setActiveTab] = useState('iso')
@@ -514,6 +515,9 @@ export function CompliancePage() {
</Card>
</div>
)}
{/* Compliance Agent */}
<AgentFAB rum="compliance" />
</div>
)
}
+4
View File
@@ -16,6 +16,7 @@ import {
X,
GripVertical,
} from 'lucide-react'
import { AgentFAB } from '@/components/agent/AgentFAB'
interface Task {
id: string
@@ -562,6 +563,9 @@ export function ProjectsPage() {
</div>
</div>
)}
{/* Projects Agent */}
<AgentFAB rum="projects" />
</div>
)
}