const crypto = require("crypto"); const { roleAllows } = require("./permissions"); class ToolRegistry { constructor(audit){ this.tools=new Map(); this.confirmations=new Map(); this.audit=audit; } register(def){ if(!def?.tool_id || !def.display_name || !def.description || !def.owning_plugin || !def.required_permission || !def.audit_category || typeof def.workflow_handler!=="function" || typeof def.permission_check!=="function" || !def.schema) throw new Error("Invalid AI tool definition."); if(this.tools.has(def.tool_id)) throw new Error(`AI tool ${def.tool_id} is already registered.`); this.tools.set(def.tool_id,{required_role:"user",confirmation_required:true,risk_level:"sensitive",...def}); return () => this.unregister(def.tool_id, def.owning_plugin); } unregister(toolId, owner = null){ const def=this.tools.get(toolId); if(!def || (owner && def.owning_plugin!==owner)) return false; this.tools.delete(toolId); for(const [id,pending] of this.confirmations){if(pending.def?.tool_id===toolId)this.confirmations.delete(id);} return true; } unregisterOwner(owner){ let removed=0; for(const [id,def] of this.tools){if(def.owning_plugin===owner && this.unregister(id,owner))removed+=1;} return removed; } list(role){ return [...this.tools.values()].filter(t=>roleAllows(role,t.required_role)).map(({workflow_handler,permission_check,...t})=>t); } validate(tool,args,role){ const def=this.tools.get(tool); if(!def) throw new Error("Tool is not registered."); if(!roleAllows(role,def.required_role)) throw new Error("Permission denied for this tool."); const schema=def.schema||{}; const clean={}; for(const [key,type] of Object.entries(schema)){ const value=args?.[key]; if(type==="integer" && !Number.isInteger(Number(value))) throw new Error(`${key} must be an integer.`); if(type==="string" && typeof value!=="string") throw new Error(`${key} must be a string.`); clean[key]=type==="integer"?Number(value):value; } return {def,args:clean}; } prepare({tool,args,user,role,sessionId}){ const checked=this.validate(tool,args,role); const allowed=checked.def.permission_check({user,arguments:checked.args,required_permission:checked.def.required_permission}); if(allowed && typeof allowed.then==="function")throw new Error("AI tool permission checks must be synchronous."); if(!allowed)throw new Error("The requesting user does not have permission for this action."); if(!checked.def.confirmation_required) return {execute:true,checked}; const id=crypto.randomUUID(); this.confirmations.set(id,{id,userId:user.id,sessionId,expiresAt:Date.now()+120000,...checked}); return {execute:false,confirmation:{id,display_name:checked.def.display_name,arguments:checked.args,expires_at:Date.now()+120000}}; } async execute({checked,user,requestId}){ const result=await checked.def.workflow_handler({arguments:checked.args,user,initiated_via_ai:true,ai_request_id:requestId}); this.audit({kind:"tool",status:"success",user_id:user.id,tool_requested:checked.def.tool_id,tool_executed:true}); return result; } async confirm({id,user,sessionId}){ const pending=this.confirmations.get(id); this.confirmations.delete(id); if(!pending || pending.expiresAt