*/ public static array $listens = [ AdminPanelBooting::class => 'onAdminPanel', ApiRoutesRegistering::class => 'onApiRoutes', ConsoleBooting::class => 'onConsole', McpToolsRegistering::class => 'onMcpTools', ]; public function boot(): void { $this->loadMigrationsFrom(__DIR__.'/Migrations'); $this->loadTranslationsFrom(__DIR__.'/Lang', 'agentic'); $this->configureRateLimiting(); $this->scheduleCommands(); } /** * Register all scheduled commands. */ protected function scheduleCommands(): void { $this->app->booted(function (): void { $schedule = $this->app->make(Schedule::class); $schedule->command('agentic:plan-cleanup')->daily(); // Forgejo pipeline — only active when a token is configured if (config('agentic.forge_token') !== '') { $schedule->command('agentic:scan')->everyFiveMinutes(); $schedule->command('agentic:dispatch')->everyTwoMinutes(); $schedule->command('agentic:pr-manage')->everyFiveMinutes(); } }); } /** * Configure rate limiting for agentic endpoints. */ protected function configureRateLimiting(): void { // Rate limit for the for-agents.json endpoint // Allow 60 requests per minute per IP RateLimiter::for('agentic-api', function (Request $request) { return Limit::perMinute(60)->by($request->ip()); }); } public function register(): void { $this->mergeConfigFrom( __DIR__.'/config.php', 'mcp' ); $this->mergeConfigFrom( __DIR__.'/agentic.php', 'agentic' ); // Register the dedicated brain database connection. // Falls back to the app's default DB when no BRAIN_DB_* env vars are set. $brainDb = config('mcp.brain.database'); if (is_array($brainDb) && ! empty($brainDb['host'])) { config(['database.connections.brain' => $brainDb]); } $this->app->singleton(\Core\Mod\Agentic\Services\AgenticManager::class); $this->app->singleton(\Core\Mod\Agentic\Services\AgentToolRegistry::class); $this->app->singleton(\Core\Mod\Agentic\Services\ForgejoService::class, function ($app) { return new \Core\Mod\Agentic\Services\ForgejoService( baseUrl: (string) config('agentic.forge_url', 'https://forge.lthn.ai'), token: (string) config('agentic.forge_token', ''), ); }); $this->app->singleton(\Core\Mod\Agentic\Services\BrainService::class, function ($app) { $ollamaUrl = config('mcp.brain.ollama_url', 'http://localhost:11434'); $qdrantUrl = config('mcp.brain.qdrant_url', 'http://localhost:6334'); // Skip TLS verification for non-public TLDs (self-signed certs behind Traefik) $hasLocalTld = static fn (string $url): bool => (bool) preg_match( '/\.(lan|lab|local|test)(?:[:\/]|$)/', parse_url($url, PHP_URL_HOST) ?? '' ); $verifySsl = ! ($hasLocalTld($ollamaUrl) || $hasLocalTld($qdrantUrl)); return new \Core\Mod\Agentic\Services\BrainService( ollamaUrl: $ollamaUrl, qdrantUrl: $qdrantUrl, collection: config('mcp.brain.collection', 'openbrain'), embeddingModel: config('mcp.brain.embedding_model', 'nomic-embed-text'), verifySsl: $verifySsl, ); }); } // ------------------------------------------------------------------------- // Event-driven handlers (for lazy loading once event system is integrated) // ------------------------------------------------------------------------- /** * Handle API routes registration event. * * Registers REST API endpoints for go-agentic Client consumption. * Routes at /v1/* — Go client uses BaseURL + "/v1/plans" directly. */ public function onApiRoutes(ApiRoutesRegistering $event): void { // Register agent API auth middleware alias $event->middleware('agent.auth', Middleware\AgentApiAuth::class); // Scoped via event — only loaded for API requests if (file_exists(__DIR__.'/Routes/api.php')) { $event->routes(fn () => require __DIR__.'/Routes/api.php'); } } /** * Handle admin panel booting event. */ public function onAdminPanel(AdminPanelBooting $event): void { $event->views($this->moduleName, __DIR__.'/View/Blade'); // Register admin routes if (file_exists(__DIR__.'/Routes/admin.php')) { $event->routes(fn () => require __DIR__.'/Routes/admin.php'); } // Register Livewire components $event->livewire('agentic.admin.dashboard', View\Modal\Admin\Dashboard::class); $event->livewire('agentic.admin.plans', View\Modal\Admin\Plans::class); $event->livewire('agentic.admin.plan-detail', View\Modal\Admin\PlanDetail::class); $event->livewire('agentic.admin.sessions', View\Modal\Admin\Sessions::class); $event->livewire('agentic.admin.session-detail', View\Modal\Admin\SessionDetail::class); $event->livewire('agentic.admin.tool-analytics', View\Modal\Admin\ToolAnalytics::class); $event->livewire('agentic.admin.tool-calls', View\Modal\Admin\ToolCalls::class); $event->livewire('agentic.admin.api-keys', View\Modal\Admin\ApiKeys::class); $event->livewire('agentic.admin.templates', View\Modal\Admin\Templates::class); // Note: Navigation is registered via AdminMenuProvider interface // in the existing boot() method until we migrate to pure event-driven nav. } /** * Handle console booting event. */ public function onConsole(ConsoleBooting $event): void { // Register middleware alias for CLI context (artisan route:list) $event->middleware('agent.auth', Middleware\AgentApiAuth::class); $event->command(Console\Commands\TaskCommand::class); $event->command(Console\Commands\PlanCommand::class); $event->command(Console\Commands\AgenticGenerateCommand::class); $event->command(Console\Commands\GenerateCommand::class); $event->command(Console\Commands\PlanRetentionCommand::class); $event->command(Console\Commands\BrainSeedMemoryCommand::class); $event->command(Console\Commands\BrainIngestCommand::class); $event->command(Console\Commands\ScanCommand::class); $event->command(Console\Commands\DispatchCommand::class); $event->command(Console\Commands\PrManageCommand::class); $event->command(Console\Commands\PrepWorkspaceCommand::class); } /** * Handle MCP tools registration event. * * Note: Agent tools (plan_create, session_start, etc.) are implemented in * the Mcp module at Mod\Mcp\Tools\Agent\* and registered via AgentToolRegistry. * Brain tools are registered here as they belong to the Agentic module. */ public function onMcpTools(McpToolsRegistering $event): void { $registry = $this->app->make(Services\AgentToolRegistry::class); $toolClasses = [ Mcp\Tools\Agent\Brain\BrainRemember::class, Mcp\Tools\Agent\Brain\BrainRecall::class, Mcp\Tools\Agent\Brain\BrainForget::class, Mcp\Tools\Agent\Brain\BrainList::class, Mcp\Tools\Agent\Messaging\AgentSend::class, Mcp\Tools\Agent\Messaging\AgentInbox::class, Mcp\Tools\Agent\Messaging\AgentConversation::class, Mcp\Tools\Agent\Plan\PlanCreate::class, Mcp\Tools\Agent\Plan\PlanGet::class, Mcp\Tools\Agent\Plan\PlanList::class, Mcp\Tools\Agent\Plan\PlanUpdateStatus::class, Mcp\Tools\Agent\Plan\PlanArchive::class, Mcp\Tools\Agent\Phase\PhaseGet::class, Mcp\Tools\Agent\Phase\PhaseUpdateStatus::class, Mcp\Tools\Agent\Phase\PhaseAddCheckpoint::class, Mcp\Tools\Agent\Session\SessionStart::class, Mcp\Tools\Agent\Session\SessionEnd::class, Mcp\Tools\Agent\Session\SessionLog::class, Mcp\Tools\Agent\Session\SessionHandoff::class, Mcp\Tools\Agent\Session\SessionResume::class, Mcp\Tools\Agent\Session\SessionReplay::class, Mcp\Tools\Agent\Session\SessionContinue::class, Mcp\Tools\Agent\Session\SessionArtifact::class, Mcp\Tools\Agent\Session\SessionList::class, Mcp\Tools\Agent\State\StateSet::class, Mcp\Tools\Agent\State\StateGet::class, Mcp\Tools\Agent\State\StateList::class, Mcp\Tools\Agent\Task\TaskUpdate::class, Mcp\Tools\Agent\Task\TaskToggle::class, Mcp\Tools\Agent\Template\TemplateList::class, Mcp\Tools\Agent\Template\TemplatePreview::class, Mcp\Tools\Agent\Template\TemplateCreatePlan::class, Mcp\Tools\Agent\Content\ContentGenerate::class, Mcp\Tools\Agent\Content\ContentBatchGenerate::class, Mcp\Tools\Agent\Content\ContentBriefCreate::class, Mcp\Tools\Agent\Content\ContentBriefGet::class, Mcp\Tools\Agent\Content\ContentBriefList::class, Mcp\Tools\Agent\Content\ContentStatus::class, Mcp\Tools\Agent\Content\ContentUsageStats::class, Mcp\Tools\Agent\Content\ContentFromPlan::class, ]; $registry->registerMany(array_map( static fn (string $toolClass) => new $toolClass(), $toolClasses, )); } }