Files
breakpilot-compliance/admin-compliance/lib/sdk/agents/soul-reader.ts
Benjamin Admin 560bdfb7fd
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
feat: Agent Management Modul — SOUL-Editor, Dashboard, Architektur-Doku
- 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>
2026-03-04 16:53:36 +01:00

132 lines
3.5 KiB
TypeScript

/**
* 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
}
}