nyk
feat(refactor): ready for manual QA after main sync (#274)
b6ecafa unverified
Raw
History Blame Contribute Delete
5.99 kB
import { NextRequest, NextResponse } from 'next/server'
import { existsSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'
import { join } from 'node:path'
import { requireRole } from '@/lib/auth'
import { config } from '@/lib/config'
import { isHermesInstalled, isHermesGatewayRunning, scanHermesSessions } from '@/lib/hermes-sessions'
import { getHermesTasks } from '@/lib/hermes-tasks'
import { getHermesMemory } from '@/lib/hermes-memory'
import { logger } from '@/lib/logger'
const HERMES_HOME = join(config.homeDir, '.hermes')
const HOOK_DIR = join(HERMES_HOME, 'hooks', 'mission-control')
export async function GET(request: NextRequest) {
const auth = requireRole(request, 'viewer')
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
try {
const installed = isHermesInstalled()
const gatewayRunning = installed ? isHermesGatewayRunning() : false
const hookInstalled = existsSync(join(HOOK_DIR, 'HOOK.yaml'))
const activeSessions = installed ? scanHermesSessions(50).filter(s => s.isActive).length : 0
const cronJobCount = installed ? getHermesTasks().cronJobs.length : 0
const memoryEntries = installed ? getHermesMemory().agentMemoryEntries : 0
return NextResponse.json({
installed,
gatewayRunning,
hookInstalled,
activeSessions,
cronJobCount,
memoryEntries,
hookDir: HOOK_DIR,
})
} catch (err) {
logger.error({ err }, 'Hermes status check failed')
return NextResponse.json({ error: 'Failed to check hermes status' }, { status: 500 })
}
}
export async function POST(request: NextRequest) {
const auth = requireRole(request, 'admin')
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
try {
const body = await request.json()
const { action } = body
if (action === 'install-hook') {
if (!isHermesInstalled()) {
return NextResponse.json({ error: 'Hermes is not installed (~/.hermes/ not found)' }, { status: 400 })
}
mkdirSync(HOOK_DIR, { recursive: true })
// Write HOOK.yaml
writeFileSync(join(HOOK_DIR, 'HOOK.yaml'), HOOK_YAML, 'utf8')
// Write handler.py
writeFileSync(join(HOOK_DIR, 'handler.py'), HANDLER_PY, 'utf8')
logger.info('Installed Mission Control hook for Hermes Agent')
return NextResponse.json({ success: true, message: 'Hook installed', hookDir: HOOK_DIR })
}
if (action === 'uninstall-hook') {
if (existsSync(HOOK_DIR)) {
rmSync(HOOK_DIR, { recursive: true, force: true })
}
logger.info('Uninstalled Mission Control hook for Hermes Agent')
return NextResponse.json({ success: true, message: 'Hook uninstalled' })
}
return NextResponse.json({ error: 'Invalid action. Must be: install-hook, uninstall-hook' }, { status: 400 })
} catch (err: any) {
logger.error({ err }, 'Hermes hook management failed')
return NextResponse.json({ error: err.message || 'Hook operation failed' }, { status: 500 })
}
}
// ---------------------------------------------------------------------------
// Hook file contents
// ---------------------------------------------------------------------------
const HOOK_YAML = `name: mission-control
description: Reports agent telemetry to Mission Control
version: "1.0"
events:
- agent:start
- agent:end
- session:start
`
const HANDLER_PY = `"""
Mission Control hook for Hermes Agent.
Reports session telemetry to the MC /api/sessions endpoint.
Configuration (via ~/.hermes/.env or environment):
MC_URL - Mission Control base URL (default: http://localhost:3000)
MC_API_KEY - API key for authentication (optional)
"""
import os
import logging
from datetime import datetime, timezone
logger = logging.getLogger("hooks.mission-control")
MC_URL = os.environ.get("MC_URL", "http://localhost:3000")
MC_API_KEY = os.environ.get("MC_API_KEY", "")
def _headers():
h = {"Content-Type": "application/json"}
if MC_API_KEY:
h["X-Api-Key"] = MC_API_KEY
return h
async def handle_event(event_name: str, payload: dict) -> None:
"""
Called by the Hermes hook registry on matching events.
Fire-and-forget with a short timeout — never blocks the agent.
"""
try:
import httpx
except ImportError:
logger.debug("httpx not available, skipping MC telemetry")
return
try:
if event_name == "agent:start":
await _report_agent_start(payload)
elif event_name == "agent:end":
await _report_agent_end(payload)
elif event_name == "session:start":
await _report_session_start(payload)
except Exception as exc:
logger.debug("MC hook error (%s): %s", event_name, exc)
async def _report_agent_start(payload: dict) -> None:
import httpx
data = {
"name": payload.get("agent_name", "hermes"),
"role": "Hermes Agent",
"status": "active",
"source": "hermes-hook",
}
async with httpx.AsyncClient(timeout=2.0) as client:
await client.post(f"{MC_URL}/api/agents", json=data, headers=_headers())
async def _report_agent_end(payload: dict) -> None:
import httpx
data = {
"name": payload.get("agent_name", "hermes"),
"status": "idle",
"source": "hermes-hook",
}
async with httpx.AsyncClient(timeout=2.0) as client:
await client.post(f"{MC_URL}/api/agents", json=data, headers=_headers())
async def _report_session_start(payload: dict) -> None:
import httpx
data = {
"event": "session:start",
"session_id": payload.get("session_id", ""),
"source": payload.get("source", "cli"),
"timestamp": datetime.now(timezone.utc).isoformat(),
}
async with httpx.AsyncClient(timeout=2.0) as client:
await client.post(f"{MC_URL}/api/hermes/events", json=data, headers=_headers())
`
export const dynamic = 'force-dynamic'