date('c'), 'source'=>'opus5-plan-from-text']; $raw = file_get_contents('php://input'); $d = json_decode($raw, true) ?: []; $text = (string)($d['text'] ?? $d['description'] ?? ''); $auto_create = !empty($d['auto_create']); $auto_execute = !empty($d['auto_execute']); if (!$text) { http_response_code(400); echo json_encode(['err'=>'no_text']); exit; } // === STEP 1 : Tokenization + normalisation === $text_clean = trim(preg_replace('/\s+/', ' ', $text)); $R['input_text'] = $text_clean; // Split sur connecteurs séquentiels (puis, ensuite, après, et, ",") // Garder "en parallèle" comme signal pour depends_on vide $parallel_mode = preg_match('/parall[eè]le|simultan[eé]/iu', $text_clean) > 0; // Découpage phrases $chunks = preg_split('/\s+(?:puis|ensuite|apr[eè]s|après|then|et|and|,)\s+/iu', $text_clean); $chunks = array_values(array_filter(array_map('trim', $chunks), function($c) { return strlen($c) > 3; })); $R['chunks'] = $chunks; $R['chunks_count'] = count($chunks); $R['parallel_mode'] = $parallel_mode; // === STEP 2 : NER endpoints + actions === // Catalogue de patterns mappant NL → endpoints sûrs (whitelist-only) $action_catalog = [ // Regex → [name, url, method, payload] '/\b(non[\s-]?reg|nonreg|regression)\b/iu' => ['check_nonreg', '/api/nonreg-api.php?cat=all', 'GET', null], '/\b(l99|layers)\b/iu' => ['check_l99', '/api/l99-state.json', 'GET', null], '/\b(cache[\s-]?stat|predictive[\s-]?cache|cache)\b/iu' => ['check_cache', '/api/opus5-predictive-cache.php?action=stats', 'GET', null], '/\b(task[\s-]?list|task[\s-]?stream)\b/iu' => ['list_tasks', '/api/opus5-task-stream.php?path=list&limit=10', 'GET', null], '/\b(plan[\s-]?list|list[\s-]?plans|plans)\b/iu' => ['list_plans', '/api/opus5-plan-registry.php?action=list&limit=10', 'GET', null], '/\b(ethica[\s-]?stat|hcps|ethica)\b/iu' => ['check_ethica', '/api/ethica-stats-api.php', 'GET', null], '/\b(gpu[\s-]?grid|grid[\s-]?gpu|parallel)\b/iu' => ['check_gpu_grid', '/api/opus5-gpu-grid.php?action=health', 'GET', null], '/\b(ssh[\s-]?tmux|tmux|s95[\s-]?health)\b/iu' => ['check_ssh_tmux', '/api/opus5-ssh-tmux-stream.php?action=health', 'GET', null], '/\b(plugin[\s-]?store|plugin[\s-]?list|plugins)\b/iu' => ['list_plugins', '/api/opus5-plugin-store.php?action=list', 'GET', null], '/\b(knowledge[\s-]?graph|kg[\s-]?stat|graph[\s-]?stat)\b/iu' => ['check_kg', '/api/opus5-knowledge-graph.php?action=stats', 'GET', null], '/\b(orchestrator[\s-]?v3|orch[\s-]?v3|meta[\s-]?orch)\b/iu' => ['ping_orch_v3', '/api/opus5-autonomous-orchestrator-v3.php', 'POST', ['message'=>'ping','session'=>'plan-gen']], '/\b(n8n|workflow[\s-]?list)\b/iu' => ['list_n8n_workflows', '/api/opus5-n8n-generator.php?action=list', 'GET', null], '/\b(truth[\s-]?registry|truth[\s-]?table)\b/iu' => ['check_truth', '/api/wevia-truth-registry.json', 'GET', null], ]; $steps = []; foreach ($chunks as $idx => $chunk) { $chunk_lower = strtolower($chunk); $matched = false; foreach ($action_catalog as $pattern => $action) { if (preg_match($pattern, $chunk_lower)) { $step_def = [ 'name' => $action[0] . '_' . ($idx + 1), 'type' => 'http_check', 'url' => $action[1], 'method' => $action[2], ]; if ($action[3] !== null) { $step_def['payload'] = $action[3]; } // Dependencies : séquentiel par défaut, sauf si parallel_mode if (!$parallel_mode && $idx > 0) { $step_def['depends_on'] = [$idx]; // step_order précédent } // Metadata : source chunk $step_def['_source_chunk'] = $chunk; $step_def['_matched_pattern'] = $pattern; $steps[] = $step_def; $matched = true; break; } } if (!$matched) { $R['unmatched_chunks'][] = $chunk; } } $R['steps_generated'] = count($steps); $R['steps'] = $steps; if (count($steps) === 0) { $R['err'] = 'no_actions_recognized'; $R['hint'] = 'Reformule avec mots-cles: nonreg, cache, plans, hcps, tmux, grid, plugin, graph, workflow, etc.'; echo json_encode($R, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); exit; } // === STEP 3 : Auto-create dans registry si demandé === if ($auto_create) { $plan_name = substr("AutoPlan — " . $text_clean, 0, 255); $create_payload = [ 'name' => $plan_name, 'description' => $text_clean, 'priority' => 0, 'metadata' => [ 'generated_by' => 'opus5-plan-from-text', 'source_text' => $text_clean, 'parallel_mode' => $parallel_mode ], 'steps' => $steps ]; $ch = curl_init('http://127.0.0.1/api/opus5-plan-registry.php?action=create'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($create_payload), CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10 ]); $create_resp = curl_exec($ch); $create_http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $created = @json_decode((string)$create_resp, true); $R['created'] = [ 'http' => $create_http, 'plan_id' => $created['plan_id'] ?? null, 'steps_created' => $created['steps_created'] ?? 0 ]; $pid = $created['plan_id'] ?? null; // === STEP 4 : Auto-execute si demandé === if ($auto_execute && $pid) { $exec_payload = ['plan_id' => $pid, 'dry_run' => false, 'max_parallel' => 5]; $ch = curl_init('http://127.0.0.1/api/opus5-plan-orchestrator.php?action=execute'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($exec_payload), CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 60 ]); $exec_resp = curl_exec($ch); $exec_http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $executed = @json_decode((string)$exec_resp, true); $R['executed'] = [ 'http' => $exec_http, 'rounds' => $executed['rounds'] ?? null, 'done' => $executed['done_count'] ?? 0, 'failed' => $executed['failed_count'] ?? 0, 'final_status' => $executed['final_status'] ?? null ]; } } $R['doctrine'] = '89 — plan from text NL parser (patterns → steps with depends_on auto)'; echo json_encode($R, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);