From 9ea43c852b683619b9c54228f39864c8939c1150 Mon Sep 17 00:00:00 2001 From: Myron Blair Date: Thu, 11 Jun 2026 04:16:29 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Phase=202=20=E2=80=94=20Intel=20Protoco?= =?UTF-8?q?l=20+=20Iron=20Protocol?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Arc Reactor v2.0: - research handler: DDG search → async page fetch → trafilatura extraction → Claude synthesis - tool_loop handler: multi-step agent loop (up to 200 iter) with web_search, fetch_url, jarvis_agents, jarvis_alerts, current_time tools - llm handler: multi-provider router (Claude/Groq/Ollama) - /jobs/recent endpoint for HUD polling - Phase 1 handlers preserved (ping/echo/shell) chat.php — Tier 0.9 Intel Protocol (before KB intent engine): - Detects: research/investigate/deep-dive/look up/find out about → research job - Detects: step-by-step/figure out/analyze and report → tool_loop job - Returns arc_job ID in response for UI polling - Depth modifiers: quick/standard/deep index.html: - INTEL tab in right panel tab bar - Research result cards with expand/collapse, synthesis, sources, status - Live polling (4s) when INTEL tab is active + active jobs present - Auto-switches to INTEL tab when research is triggered from chat - intelPrompt() pre-fills chat input for new research Co-Authored-By: Claude Sonnet 4.6 --- api/endpoints/chat.php | 63 +++++++++++++++++ public_html/index.html | 156 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+) diff --git a/api/endpoints/chat.php b/api/endpoints/chat.php index 3d1b5b2..3f561bd 100644 --- a/api/endpoints/chat.php +++ b/api/endpoints/chat.php @@ -1063,6 +1063,67 @@ if (!$reply && preg_match('/\b(news|headlines|latest|what.?s happening|current e } } +// ── Tier 0.9: Intel Protocol — research & tool_loop detection ──────────── +$arcJobId = null; + +// Detect "research X", "look up X", "deep dive X", "investigate X", "find out about X" +$intelPatterns = [ + '/^(?:jarvis[,\s]+)?(?:research|investigate|deep[- ]dive|deep dive)\s+(.+)/i' => 'research', + '/^(?:jarvis[,\s]+)?(?:look\s+(?:up|into)|find\s+out\s+(?:about)?)\s+(.+)/i' => 'research', + '/^(?:jarvis[,\s]+)?(?:step[- ]by[- ]step|figure\s+out|analyze\s+and\s+report|work\s+through)\s+(.+)/i' => 'tool_loop', + '/^(?:jarvis[,\s]+)?(?:run\s+a\s+research\s+(?:job|task)\s+on)\s+(.+)/i' => 'research', +]; + +if (!$reply) { + foreach ($intelPatterns as $pattern => $jobType) { + if (preg_match($pattern, $message, $m)) { + $queryOrTask = trim($m[1]); + if (strlen($queryOrTask) < 3) break; + + $depth = 'standard'; + if (preg_match('/\b(?:quick|brief)\b/i', $message)) $depth = 'quick'; + if (preg_match('/\b(?:deep|thorough|comprehensive|full)\b/i', $message)) $depth = 'deep'; + + $jobPayload = $jobType === 'research' + ? ['query' => $queryOrTask, 'depth' => $depth, 'provider' => 'claude'] + : ['task' => $queryOrTask, 'max_iterations' => 12, 'provider' => 'claude']; + + // Submit to Arc Reactor + $arcCh = curl_init('http://127.0.0.1:7474/job'); + curl_setopt_array($arcCh, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => json_encode([ + 'type' => $jobType, + 'payload' => $jobPayload, + 'priority' => 7, + 'created_by' => 'chat:' . $sessionId, + ]), + CURLOPT_HTTPHEADER => ['Content-Type: application/json'], + CURLOPT_TIMEOUT => 5, + CURLOPT_CONNECTTIMEOUT => 3, + ]); + $arcRes = json_decode(curl_exec($arcCh), true); + curl_close($arcCh); + + if (isset($arcRes['job_id'])) { + $arcJobId = $arcRes['job_id']; + if ($jobType === 'research') { + $depthLabel = strtoupper($depth); + $reply = "◈ INTEL PROTOCOL ACTIVATED — Running {$depthLabel} research on **{$queryOrTask}** (Job #{$arcJobId}). I'm searching sources, extracting content, and synthesizing a briefing now, {$userAddr}. Switch to the INTEL tab to watch live progress."; + } else { + $reply = "◈ IRON PROTOCOL ACTIVATED — Multi-step analysis initiated for **{$queryOrTask}** (Job #{$arcJobId}). I'll work through this systematically using available tools, {$userAddr}. Results will appear in the INTEL tab."; + } + $source = "arc:{$jobType}"; + } else { + $reply = "Intel Protocol is offline, {$userAddr}. Arc Reactor may be unavailable — I'll try to answer directly."; + $source = 'arc:offline'; + } + break; + } + } +} + // ── Tier 1: Intent Engine (instant, no LLM) ─────────────────────────────── if (!$reply) { $matched = KBEngine::match($message); @@ -1193,6 +1254,7 @@ if (!$reply) { } } + // ── Tier 2: Ollama local LLM (fast local fallback) ─────────────────────── if (!$reply && defined('OLLAMA_HOST') && OLLAMA_HOST) { $ollamaHost = OLLAMA_HOST; @@ -1413,4 +1475,5 @@ echo json_encode([ 'source' => $source, 'session_id' => $sessionId, 'timestamp' => date('c'), + 'arc_job' => $arcJobId, ]); diff --git a/public_html/index.html b/public_html/index.html index 9a11825..e3ba141 100644 --- a/public_html/index.html +++ b/public_html/index.html @@ -875,6 +875,27 @@ body::after{ transition:filter 1.2s ease; } #app.sleeping #sleepOverlay{display:flex} +/* ── INTEL PROTOCOL — research result cards ──────────────────────── */ +.intel-card{background:rgba(0,212,255,0.04);border:1px solid var(--panel-border);border-radius:var(--r);margin-bottom:8px;overflow:hidden} +.intel-card-head{display:flex;align-items:center;gap:8px;padding:8px 10px;cursor:pointer;user-select:none} +.intel-card-head:hover{background:rgba(0,212,255,0.06)} +.intel-card-query{font-family:var(--font-display);font-size:0.6rem;letter-spacing:1px;color:var(--cyan);flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.intel-card-status{font-family:var(--font-mono);font-size:0.55rem;padding:2px 6px;border-radius:2px;flex-shrink:0} +.intel-card-status.running{color:#ffd700;border:1px solid rgba(255,215,0,0.4);animation:pulse 1.5s ease-in-out infinite} +.intel-card-status.done{color:var(--green);border:1px solid rgba(0,255,136,0.3)} +.intel-card-status.failed{color:var(--red);border:1px solid rgba(255,34,68,0.3)} +.intel-card-body{display:none;padding:0 10px 10px;border-top:1px solid var(--panel-border)} +.intel-card.open .intel-card-body{display:block} +.intel-card-body .synthesis{font-size:0.65rem;line-height:1.6;color:var(--text);margin:8px 0;white-space:pre-wrap} +.intel-sources{margin-top:8px} +.intel-source{font-size:0.58rem;color:var(--text-dim);padding:2px 0;border-bottom:1px solid rgba(0,212,255,0.06)} +.intel-source a{color:var(--cyan2);text-decoration:none} +.intel-source a:hover{text-decoration:underline} +.intel-empty{text-align:center;padding:24px 10px;font-family:var(--font-mono);font-size:0.6rem;color:var(--text-dim);letter-spacing:1px} +.intel-new-btn{width:100%;background:rgba(0,212,255,0.06);border:1px solid var(--panel-border);border-radius:4px;padding:5px;color:var(--cyan);font-family:var(--font-display);font-size:0.55rem;letter-spacing:2px;cursor:pointer;margin-bottom:8px} +.intel-new-btn:hover{background:rgba(0,212,255,0.12)} +@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}} + @@ -1077,6 +1098,7 @@ body::after{
NEWS
AGENTS
SITES
+
INTEL
@@ -1093,6 +1115,9 @@ body::after{
+
+
+
@@ -2997,6 +3022,7 @@ function switchTab(name) { if (pane) pane.classList.add('active'); if (name === 'news') loadNews(); if (name === 'agents') loadAgents(); + if (name === 'intel') loadIntel(); if (name === 'alerts') loadAlerts(); } @@ -3140,6 +3166,7 @@ async function sendMessage() { addMessage('jarvis', data.reply); speak(data.reply); } + if (data.arc_job) { onArcJobStarted(data.arc_job, data.source || ''); } } catch(e) { const bubble = document.getElementById('thinking-bubble'); if (bubble) bubble.remove(); @@ -3497,6 +3524,135 @@ async function arcWaitJob(jobId, onProgress) { } +// ── INTEL PROTOCOL — HUD panel ──────────────────────────────────────── +let _intelPollTimer = null; +let _intelActiveJobs = new Set(); +let _intelLastLoad = 0; + +async function loadIntel() { + const el = document.getElementById('intel-list'); + if (!el) return; + _intelLastLoad = Date.now(); + + try { + // Fetch recent research + tool_loop jobs + const [resJobs, toolJobs] = await Promise.all([ + api('arc?action=jobs&status=&limit=20').catch(() => []), + Promise.resolve([]), + ]); + const jobs = Array.isArray(resJobs) ? resJobs.filter(j => ['research','tool_loop','llm'].includes(j.job_type)) : []; + + if (!jobs.length) { + el.innerHTML = '
◈ NO INTEL JOBS
Say "research [topic]" to activate
'; + stopIntelPolling(); + return; + } + + // Check for active jobs + const hasActive = jobs.some(j => j.status === 'queued' || j.status === 'running'); + if (hasActive) startIntelPolling(); else stopIntelPolling(); + + let html = ''; + for (const job of jobs) { + const isOpen = _intelActiveJobs.has(job.id) || job.status === 'running'; + const statusClass = job.status === 'done' ? 'done' : job.status === 'failed' ? 'failed' : 'running'; + const statusLabel = job.status === 'queued' ? 'QUEUED' : job.status === 'running' ? '● ACTIVE' : job.status.toUpperCase(); + const typeLabel = job.job_type === 'research' ? '◈ INTEL' : job.job_type === 'tool_loop' ? '⚡ IRON' : '◈ LLM'; + + // Get result details if done + let bodyHtml = ''; + if (job.status === 'done' && job.result) { + let r = job.result; + if (typeof r === 'string') { try { r = JSON.parse(r); } catch(e) {} } + if (typeof r === 'object') { + const synthesis = (r.synthesis || r.result || r.response || '').trim(); + const sources = r.sources || []; + const query = r.query || r.task || ''; + const provider = r.provider || ''; + + bodyHtml = `
`; + if (provider) bodyHtml += `
PROVIDER: ${provider.toUpperCase()} · SOURCES: ${r.source_count||sources.length||'—'}
`; + if (synthesis) bodyHtml += `
${escHtml(synthesis.substring(0, 1500))}${synthesis.length>1500?' + +[...truncated — view in admin]':''}
`; + if (sources.length) { + bodyHtml += '
SOURCES
'; + sources.slice(0,5).forEach((s,i) => { + const title = escHtml((s.title||s.url||'').substring(0,60)); + const url = escHtml(s.url||''); + bodyHtml += ``; + }); + bodyHtml += '
'; + } + bodyHtml += '
'; + } + } else if (job.status === 'running' || job.status === 'queued') { + const typeMsg = job.job_type === 'research' ? 'Searching sources and extracting content...' : 'Executing tool loop...'; + bodyHtml = `
${typeMsg}
`; + } else if (job.status === 'failed' && job.error) { + bodyHtml = `
${escHtml(job.error.substring(0,200))}
`; + } + + const queryText = job.created_by ? job.created_by.replace('chat:', '').replace(/session.*/, '') : ''; + const ts = job.created_at ? new Date(job.created_at).toLocaleTimeString() : ''; + + html += `
+
+ ${typeLabel} + #${job.id} ${escHtml((job.created_by||'').replace('chat:','').substring(0,40))} + ${ts} + ${statusLabel} +
+ ${bodyHtml} +
`; + } + el.innerHTML = html; + + } catch(e) { + if (el) el.innerHTML = '
INTEL OFFLINE
'; + } +} + +function toggleIntelCard(id) { + const card = document.getElementById('intel-card-' + id); + if (!card) return; + if (_intelActiveJobs.has(id)) _intelActiveJobs.delete(id); + else _intelActiveJobs.add(id); + card.classList.toggle('open'); +} + +function startIntelPolling() { + if (_intelPollTimer) return; + _intelPollTimer = setInterval(() => { + if (document.getElementById('tab-intel')?.classList.contains('active')) { + loadIntel(); + } + }, 4000); +} + +function stopIntelPolling() { + if (_intelPollTimer) { clearInterval(_intelPollTimer); _intelPollTimer = null; } +} + +function escHtml(s) { + return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); +} + +function intelPrompt() { + const input = document.getElementById('textInput'); + if (input) { input.value = 'research '; input.focus(); } +} + +// Called from chat.js when arc_job is returned +function onArcJobStarted(jobId, jobType) { + _intelActiveJobs.add(jobId); + // Auto-switch to INTEL tab + const intelTab = document.querySelector('[onclick*="switchTab(\'intel\')"]'); + if (intelTab) intelTab.click(); + startIntelPolling(); +} + + async function loadAgents() { const [listData, metricsData] = await Promise.all([ api('agent/list'),