11import { Hono } from 'hono' ;
22import { db } from '../db/client.js' ;
3- import { agents , tasks , escrowTransactions , transactions , agentRatings , disputes } from '../db/schema.js' ;
3+ import { agents , tasks , escrowTransactions , transactions , agentRatings , disputes , anomalyEvents , agentReputation } from '../db/schema.js' ;
44import { eq , sql , count , desc , and } from 'drizzle-orm' ;
55import { createMiddleware } from 'hono/factory' ;
66import { HTTPException } from 'hono/http-exception' ;
@@ -16,6 +16,7 @@ import {
1616} from '@swarmdock/shared' ;
1717import { releaseEscrow , refundEscrow } from '../services/escrow.js' ;
1818import { selectTribunalJudges } from '../services/tribunal.js' ;
19+ import { unsuspendAgent } from '../services/anomaly.js' ;
1920import { eventBus } from '../lib/events.js' ;
2021
2122const adminAuth = createMiddleware ( async ( c , next ) => {
@@ -262,4 +263,88 @@ app.post('/disputes/:id/resolve', adminAuth, async (c) => {
262263 } ) ;
263264} ) ;
264265
266+ // GET /api/v1/admin/anomalies — List anomaly events
267+ app . get ( '/anomalies' , adminAuth , async ( c ) => {
268+ const type = c . req . query ( 'type' ) ;
269+ const severity = c . req . query ( 'severity' ) ;
270+ const agentId = c . req . query ( 'agentId' ) ;
271+ const limit = Math . max ( 1 , Math . min ( parseInt ( c . req . query ( 'limit' ) ?? '50' , 10 ) || 50 , 200 ) ) ;
272+ const offset = Math . max ( 0 , parseInt ( c . req . query ( 'offset' ) ?? '0' , 10 ) || 0 ) ;
273+
274+ const conditions = [ ] ;
275+ if ( type ) conditions . push ( eq ( anomalyEvents . type , type ) ) ;
276+ if ( severity ) conditions . push ( eq ( anomalyEvents . severity , severity ) ) ;
277+ if ( agentId ) conditions . push ( eq ( anomalyEvents . agentId , agentId ) ) ;
278+
279+ const whereClause = conditions . length > 0 ? and ( ...conditions ) : undefined ;
280+
281+ const rows = await db
282+ . select ( )
283+ . from ( anomalyEvents )
284+ . where ( whereClause )
285+ . orderBy ( desc ( anomalyEvents . createdAt ) )
286+ . limit ( limit )
287+ . offset ( offset ) ;
288+
289+ const [ { total } ] = await db . select ( { total : count ( ) } ) . from ( anomalyEvents ) . where ( whereClause ) ;
290+
291+ return c . json ( { anomalies : rows , limit, offset, total } ) ;
292+ } ) ;
293+
294+ // GET /api/v1/admin/agents/:id/risk — Agent risk profile
295+ app . get ( '/agents/:id/risk' , adminAuth , async ( c ) => {
296+ const id = c . req . param ( 'id' ) ;
297+
298+ const [ agent ] = await db
299+ . select ( { id : agents . id , displayName : agents . displayName , status : agents . status , trustLevel : agents . trustLevel } )
300+ . from ( agents )
301+ . where ( eq ( agents . id , id ) )
302+ . limit ( 1 ) ;
303+
304+ if ( ! agent ) return c . json ( { error : 'Agent not found' } , 404 ) ;
305+
306+ const anomalies = await db
307+ . select ( )
308+ . from ( anomalyEvents )
309+ . where ( eq ( anomalyEvents . agentId , id ) )
310+ . orderBy ( desc ( anomalyEvents . createdAt ) )
311+ . limit ( 20 ) ;
312+
313+ const reputation = await db
314+ . select ( )
315+ . from ( agentReputation )
316+ . where ( eq ( agentReputation . agentId , id ) ) ;
317+
318+ const highCount = anomalies . filter ( ( a ) => a . severity === 'high' ) . length ;
319+ const mediumCount = anomalies . filter ( ( a ) => a . severity === 'medium' ) . length ;
320+
321+ return c . json ( {
322+ agent : { id : agent . id , displayName : agent . displayName , status : agent . status , trustLevel : agent . trustLevel } ,
323+ anomalySummary : { total : anomalies . length , high : highCount , medium : mediumCount } ,
324+ recentAnomalies : anomalies ,
325+ reputation,
326+ } ) ;
327+ } ) ;
328+
329+ // POST /api/v1/admin/agents/:id/unsuspend — Lift suspension
330+ app . post ( '/agents/:id/unsuspend' , adminAuth , async ( c ) => {
331+ const id = c . req . param ( 'id' ) ;
332+
333+ const [ agent ] = await db
334+ . select ( { status : agents . status } )
335+ . from ( agents )
336+ . where ( eq ( agents . id , id ) )
337+ . limit ( 1 ) ;
338+
339+ if ( ! agent ) return c . json ( { error : 'Agent not found' } , 404 ) ;
340+ if ( agent . status !== AGENT_STATUS . SUSPENDED ) {
341+ return c . json ( { error : 'Agent is not suspended' } , 400 ) ;
342+ }
343+
344+ const body = await c . req . json ( ) . catch ( ( ) => ( { } ) ) as { note ?: string } ;
345+ await unsuspendAgent ( id , body . note ?? 'Admin unsuspend' ) ;
346+
347+ return c . json ( { unsuspended : true , agentId : id } ) ;
348+ } ) ;
349+
265350export default app ;
0 commit comments