feat: Agent Management Modul — SOUL-Editor, Dashboard, Architektur-Doku
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 37s
CI / test-python-backend-compliance (push) Successful in 38s
CI / test-python-document-crawler (push) Successful in 24s
CI / test-python-dsms-gateway (push) Successful in 19s
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 37s
CI / test-python-backend-compliance (push) Successful in 38s
CI / test-python-document-crawler (push) Successful in 24s
CI / test-python-dsms-gateway (push) Successful in 19s
- SOUL-Dateien: System-Prompts aus Chat-Routen extrahiert nach agent-core/soul/*.soul.md - soul-reader.ts: Lese-/Schreib-API mit 30s TTL-Cache und Backup-Versionierung - agent-registry.ts: Statische Konfiguration der 2 Compliance-Agenten - 5 API-Routen: /api/sdk/agents (Liste, Detail, SOUL GET/PUT, Sessions, Statistiken) - 5 Frontend-Seiten: Dashboard, Detail mit SOUL-Editor, Architektur, Sessions, Statistiken - Sidebar: "Agenten" Link nach Architektur eingefügt - Wire-Up: compliance-advisor + drafting-engine lesen SOUL-Datei mit Fallback - Dockerfile: agent-core wird in Production-Image kopiert Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
56
admin-compliance/lib/sdk/agents/agent-registry.ts
Normal file
56
admin-compliance/lib/sdk/agents/agent-registry.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Agent Registry — static configuration for the 2 compliance agents.
|
||||
*/
|
||||
|
||||
export interface AgentConfig {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
soulFile: string
|
||||
color: string
|
||||
icon: string
|
||||
status: 'active' | 'inactive' | 'error'
|
||||
version: string
|
||||
stats: {
|
||||
sessionsToday: number
|
||||
avgResponseTime: string
|
||||
successRate: string
|
||||
}
|
||||
}
|
||||
|
||||
export const COMPLIANCE_AGENTS: AgentConfig[] = [
|
||||
{
|
||||
id: 'compliance-advisor',
|
||||
name: 'Compliance Advisor',
|
||||
description: 'RAG-gestuetzter Chat-Agent fuer DSGVO, AI Act und Compliance-Fragen. Durchsucht 6 Sammlungen mit Multi-Collection-RAG und liefert quellenbasierte Antworten mit Streaming.',
|
||||
soulFile: 'compliance-advisor.soul.md',
|
||||
color: '#7c3aed', // purple-600
|
||||
icon: 'shield',
|
||||
status: 'active',
|
||||
version: '1.0.0',
|
||||
stats: {
|
||||
sessionsToday: 0,
|
||||
avgResponseTime: '—',
|
||||
successRate: '—',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'drafting-agent',
|
||||
name: 'Drafting Agent',
|
||||
description: '4-Modi Dokumenten-Agent (Explain, Ask, Draft, Validate). Entwirft DSGVO-konforme Compliance-Dokumente, erkennt Luecken und prueft Cross-Dokument-Konsistenz.',
|
||||
soulFile: 'drafting-agent.soul.md',
|
||||
color: '#2563eb', // blue-600
|
||||
icon: 'pencil',
|
||||
status: 'active',
|
||||
version: '1.0.0',
|
||||
stats: {
|
||||
sessionsToday: 0,
|
||||
avgResponseTime: '—',
|
||||
successRate: '—',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export function getAgentById(id: string): AgentConfig | undefined {
|
||||
return COMPLIANCE_AGENTS.find(a => a.id === id)
|
||||
}
|
||||
131
admin-compliance/lib/sdk/agents/soul-reader.ts
Normal file
131
admin-compliance/lib/sdk/agents/soul-reader.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* SOUL File Reader — reads/writes .soul.md files from agent-core/soul/
|
||||
* with 30s TTL cache and backup support.
|
||||
*/
|
||||
|
||||
import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
|
||||
const AGENT_CORE_PATH = process.env.AGENT_CORE_PATH || path.join(process.cwd(), 'agent-core')
|
||||
const SOUL_DIR = path.join(AGENT_CORE_PATH, 'soul')
|
||||
const BACKUPS_DIR = path.join(SOUL_DIR, '.backups')
|
||||
|
||||
// 30s TTL cache
|
||||
const cache = new Map<string, { content: string; timestamp: number }>()
|
||||
const CACHE_TTL = 30_000
|
||||
|
||||
function getSoulFilePath(agentId: string): string {
|
||||
// Prevent path traversal
|
||||
const safe = agentId.replace(/[^a-z0-9-]/g, '')
|
||||
return path.join(SOUL_DIR, `${safe}.soul.md`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a SOUL file with 30s cache
|
||||
*/
|
||||
export async function readSoulFile(agentId: string): Promise<string | null> {
|
||||
const cached = cache.get(agentId)
|
||||
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
||||
return cached.content
|
||||
}
|
||||
|
||||
try {
|
||||
const filePath = getSoulFilePath(agentId)
|
||||
const content = await fs.readFile(filePath, 'utf-8')
|
||||
cache.set(agentId, { content, timestamp: Date.now() })
|
||||
return content
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a SOUL file — creates backup first, then writes new content
|
||||
*/
|
||||
export async function writeSoulFile(agentId: string, content: string): Promise<void> {
|
||||
const filePath = getSoulFilePath(agentId)
|
||||
|
||||
// Ensure backups dir exists
|
||||
await fs.mkdir(BACKUPS_DIR, { recursive: true })
|
||||
|
||||
// Create backup of current file
|
||||
try {
|
||||
const current = await fs.readFile(filePath, 'utf-8')
|
||||
const backupName = `${agentId}-${Date.now()}.soul.md`
|
||||
await fs.writeFile(path.join(BACKUPS_DIR, backupName), current, 'utf-8')
|
||||
} catch {
|
||||
// No existing file to backup
|
||||
}
|
||||
|
||||
// Write new content
|
||||
await fs.writeFile(filePath, content, 'utf-8')
|
||||
|
||||
// Invalidate cache
|
||||
cache.delete(agentId)
|
||||
}
|
||||
|
||||
/**
|
||||
* List backup versions for an agent
|
||||
*/
|
||||
export async function listSoulBackups(agentId: string): Promise<Array<{ filename: string; timestamp: number; size: number }>> {
|
||||
try {
|
||||
const files = await fs.readdir(BACKUPS_DIR)
|
||||
const prefix = `${agentId}-`
|
||||
const backups: Array<{ filename: string; timestamp: number; size: number }> = []
|
||||
|
||||
for (const file of files) {
|
||||
if (!file.startsWith(prefix) || !file.endsWith('.soul.md')) continue
|
||||
const tsStr = file.slice(prefix.length, -'.soul.md'.length)
|
||||
const ts = parseInt(tsStr, 10)
|
||||
if (isNaN(ts)) continue
|
||||
|
||||
const stat = await fs.stat(path.join(BACKUPS_DIR, file))
|
||||
backups.push({ filename: file, timestamp: ts, size: stat.size })
|
||||
}
|
||||
|
||||
return backups.sort((a, b) => b.timestamp - a.timestamp)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a specific backup file
|
||||
*/
|
||||
export async function readSoulBackup(filename: string): Promise<string | null> {
|
||||
try {
|
||||
// Prevent path traversal
|
||||
const safe = path.basename(filename)
|
||||
return await fs.readFile(path.join(BACKUPS_DIR, safe), 'utf-8')
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if SOUL file exists
|
||||
*/
|
||||
export async function soulFileExists(agentId: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.access(getSoulFilePath(agentId))
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SOUL file stats
|
||||
*/
|
||||
export async function getSoulFileStats(agentId: string): Promise<{ size: number; createdAt: string; updatedAt: string } | null> {
|
||||
try {
|
||||
const stat = await fs.stat(getSoulFilePath(agentId))
|
||||
return {
|
||||
size: stat.size,
|
||||
createdAt: stat.birthtime.toISOString(),
|
||||
updatedAt: stat.mtime.toISOString(),
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user