#!/usr/bin/env php getMessage()); exit(1); } _log($logFile, '=== Worker completato ==='); exit(0); // ── Funzioni ────────────────────────────────────────────────────────────── function _processTicket(array $ticket, string $password, string $logFile): void { $id = $ticket['id']; _log($logFile, "Processing ticket #{$id} [{$ticket['tipo']}]: " . substr($ticket['descrizione'], 0, 80) . '…'); // Costruisci prompt per Claude Code $prompt = _buildPrompt($ticket); // Scrivi prompt su file temporaneo $promptFile = "/tmp/nis2-feedback-prompt-{$id}.txt"; file_put_contents($promptFile, $prompt); // Esegui Claude Code nel container devenv $dockerCmd = sprintf( 'docker exec -u developer %s bash -c %s', escapeshellarg(DEVENV_CONTAINER), escapeshellarg( 'cd /projects/nis2-agile && ' . 'timeout ' . CLAUDE_TIMEOUT . ' ' . 'claude --dangerously-skip-permissions --output-format stream-json ' . '-p "$(cat ' . $promptFile . ')" 2>&1' ) ); $output = []; $exitCode = 0; exec($dockerCmd, $output, $exitCode); @unlink($promptFile); $outputText = implode("\n", $output); _log($logFile, "Ticket #{$id} — Claude exit_code={$exitCode}, output=" . substr($outputText, 0, 200)); if ($exitCode !== 0) { _log($logFile, "Ticket #{$id} — risoluzione fallita (exit {$exitCode})."); // Rimane in in_lavorazione per il prossimo ciclo return; } // Chiama API interna per marcare come risolto $resolved = _resolveViaApi($id, $password, $logFile); if ($resolved) { _log($logFile, "Ticket #{$id} — risolto e broadcast inviato."); } } function _buildPrompt(array $ticket): string { $suggerimento = $ticket['ai_suggerimento'] ?? 'Nessun suggerimento AI disponibile.'; return << $password]); // Ottieni un token JWT admin per la chiamata interna $token = _getAdminToken($logFile); if (!$token) { _log($logFile, "Ticket #{$reportId} — impossibile ottenere token admin per risoluzione."); return false; } $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . $token, ], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch); curl_close($ch); if ($curlError || $httpCode < 200 || $httpCode >= 300) { _log($logFile, "Ticket #{$reportId} — API resolve fallita [{$httpCode}]: {$curlError}"); return false; } $data = json_decode($response, true); return $data['success'] ?? false; } function _getAdminToken(string $logFile): ?string { static $cachedToken = null; if ($cachedToken !== null) return $cachedToken; $adminEmail = Env::get('FEEDBACK_WORKER_ADMIN_EMAIL', ''); $adminPass = Env::get('FEEDBACK_WORKER_ADMIN_PASS', ''); if (!$adminEmail || !$adminPass) { _log($logFile, 'FEEDBACK_WORKER_ADMIN_EMAIL / FEEDBACK_WORKER_ADMIN_PASS non configurate.'); return null; } $ch = curl_init(APP_URL_INTERNAL . '/api/auth/login'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode(['email' => $adminEmail, 'password' => $adminPass]), CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200) { _log($logFile, "Login admin fallito [{$httpCode}]."); return null; } $data = json_decode($response, true); $cachedToken = $data['data']['access_token'] ?? null; return $cachedToken; } function _log(string $logFile, string $message): void { $line = '[' . date('Y-m-d H:i:s') . '] ' . $message . PHP_EOL; $dir = dirname($logFile); if (!is_dir($dir)) { @mkdir($dir, 0755, true); } @file_put_contents($logFile, $line, FILE_APPEND | LOCK_EX); echo $line; }