diff --git a/backend/bin/boc b/backend/bin/boc
index f82ab6d7c..c92bd5daa 100755
Binary files a/backend/bin/boc and b/backend/bin/boc differ
diff --git a/backend/boc-api b/backend/boc-api
index f82ab6d7c..c92bd5daa 100755
Binary files a/backend/boc-api and b/backend/boc-api differ
diff --git a/backend/handlers/agent.go b/backend/handlers/agent.go
index f4a3dd71e..b6780f98a 100644
--- a/backend/handlers/agent.go
+++ b/backend/handlers/agent.go
@@ -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,
diff --git a/backend/handlers/agent_webhook.go b/backend/handlers/agent_webhook.go
new file mode 100644
index 000000000..0982adb90
--- /dev/null
+++ b/backend/handlers/agent_webhook.go
@@ -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,
+ },
+ })
+}
diff --git a/web-v2/src/pages/CompliancePage.tsx b/web-v2/src/pages/CompliancePage.tsx
index 2bf3dad90..6aba70982 100644
--- a/web-v2/src/pages/CompliancePage.tsx
+++ b/web-v2/src/pages/CompliancePage.tsx
@@ -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() {
)}
+
+ {/* Compliance Agent */}
+
)
}
diff --git a/web-v2/src/pages/ProjectsPage.tsx b/web-v2/src/pages/ProjectsPage.tsx
index 218490c94..24a8701a6 100644
--- a/web-v2/src/pages/ProjectsPage.tsx
+++ b/web-v2/src/pages/ProjectsPage.tsx
@@ -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() {
)}
+
+ {/* Projects Agent */}
+
)
}