mirror of
https://github.com/myronblair/jarvis
synced 2026-07-29 09:12:36 -05:00
Compare commits
8 Commits
90e4ded7c9
...
af03a2f2d8
| Author | SHA1 | Date | |
|---|---|---|---|
| af03a2f2d8 | |||
| dfc92a6791 | |||
| 05522edb1d | |||
| 435af8ccc9 | |||
| a9ea75db98 | |||
| 651455cb47 | |||
| 874f6e8c5c | |||
| 42a82c40cb |
@@ -18,13 +18,13 @@ define(chr(39)+'CLAUDE_MODEL'.chr(39), chr(39)+'claude-sonnet-4-6'.chr(39))
|
|||||||
define(chr(39)+'CLAUDE_MAX_TOKENS'.chr(39), 1024);
|
define(chr(39)+'CLAUDE_MAX_TOKENS'.chr(39), 1024);
|
||||||
|
|
||||||
define(chr(39)+'GROQ_API_KEY'.chr(39), chr(39)+'gsk_...'.chr(39));
|
define(chr(39)+'GROQ_API_KEY'.chr(39), chr(39)+'gsk_...'.chr(39));
|
||||||
define(chr(39)+'GROQ_MODEL_SEARCH'.chr(39), chr(39)+'groq/compound-mini'.chr(39));
|
define(chr(39)+'GROQ_MODEL_SEARCH'.chr(39), chr(39)+'compound-beta-mini'.chr(39));
|
||||||
define(chr(39)+'GROQ_MODEL_GENERAL'.chr(39), chr(39)+'llama-3.3-70b-versatile'.chr(39));
|
define(chr(39)+'GROQ_MODEL_GENERAL'.chr(39), chr(39)+'llama-3.3-70b-versatile'.chr(39));
|
||||||
define(chr(39)+'GROQ_TIMEOUT'.chr(39), 30);
|
define(chr(39)+'GROQ_TIMEOUT'.chr(39), 30);
|
||||||
|
|
||||||
define(chr(39)+'OLLAMA_HOST'.chr(39), chr(39)+'http://10.48.200.95:11434'.chr(39));
|
define(chr(39)+'OLLAMA_HOST'.chr(39), chr(39)+'http://10.48.200.210:11434'.chr(39));
|
||||||
define(chr(39)+'OLLAMA_MODEL_PRIMARY'.chr(39), chr(39)+'llama3.2:1b'.chr(39));
|
define(chr(39)+'OLLAMA_MODEL_PRIMARY'.chr(39), chr(39)+'llama3.1:8b'.chr(39));
|
||||||
define(chr(39)+'OLLAMA_MODEL_HEAVY'.chr(39), chr(39)+'llama3.1:70b'.chr(39));
|
define(chr(39)+'OLLAMA_MODEL_HEAVY'.chr(39), chr(39)+'llama3.1:8b'.chr(39));
|
||||||
define(chr(39)+'OLLAMA_TIMEOUT'.chr(39), 90);
|
define(chr(39)+'OLLAMA_TIMEOUT'.chr(39), 90);
|
||||||
|
|
||||||
define(chr(39)+'LOCAL_SUBNET'.chr(39), chr(39)+'10.48.200'.chr(39));
|
define(chr(39)+'LOCAL_SUBNET'.chr(39), chr(39)+'10.48.200'.chr(39));
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ function collect_all(): array {
|
|||||||
"jarvis" => "http://127.0.0.1",
|
"jarvis" => "http://127.0.0.1",
|
||||||
'tomsjavajive' => 'https://tomsjavajive.com',
|
'tomsjavajive' => 'https://tomsjavajive.com',
|
||||||
'epictravelexp'=> 'https://epictravelexpeditions.com',
|
'epictravelexp'=> 'https://epictravelexpeditions.com',
|
||||||
'parkersling' => 'https://parkerslingshotrentals.com',
|
'parkerslingshotrentals' => 'https://parkerslingshotrentals.com',
|
||||||
'orbishosting' => 'https://orbishosting.com',
|
'orbishosting' => 'https://orbishosting.com',
|
||||||
'orbisportal' => 'https://orbis.orbishosting.com',
|
'orbisportal' => 'https://orbis.orbishosting.com',
|
||||||
'tomtomgames' => 'https://tomtomgames.com',
|
'tomtomgames' => 'https://tomtomgames.com',
|
||||||
|
|||||||
@@ -441,4 +441,41 @@ CREATE TABLE `users` (
|
|||||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `guardian_config`
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `guardian_config` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`key_name` varchar(64) NOT NULL,
|
||||||
|
`value` varchar(255) NOT NULL DEFAULT '',
|
||||||
|
`updated_at` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_key` (`key_name`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `guardian_events`
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `guardian_events` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`event_type` varchar(64) NOT NULL,
|
||||||
|
`severity` enum('info','warning','critical') NOT NULL DEFAULT 'info',
|
||||||
|
`agent_id` varchar(64) NOT NULL DEFAULT '',
|
||||||
|
`hostname` varchar(128) NOT NULL DEFAULT '',
|
||||||
|
`metric` varchar(64) NOT NULL DEFAULT '',
|
||||||
|
`value` float NOT NULL DEFAULT 0,
|
||||||
|
`threshold` float NOT NULL DEFAULT 0,
|
||||||
|
`message` text NOT NULL,
|
||||||
|
`ai_analysis` text NOT NULL DEFAULT '',
|
||||||
|
`acknowledged` tinyint(1) NOT NULL DEFAULT 0,
|
||||||
|
`created_at` datetime NOT NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_severity` (`severity`),
|
||||||
|
KEY `idx_ack` (`acknowledged`),
|
||||||
|
KEY `idx_created` (`created_at`),
|
||||||
|
KEY `idx_agent` (`agent_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
-- Dump completed on 2026-06-29 23:15:44
|
-- Dump completed on 2026-06-29 23:15:44
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=JARVIS Arc Reactor
|
||||||
|
After=network-online.target mysql.service
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=/opt/jarvis-arc
|
||||||
|
ExecStart=/opt/jarvis-arc/venv/bin/python3 /opt/jarvis-arc/reactor.py
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
User=root
|
||||||
|
StandardOutput=append:/home/jarvis.orbishosting.com/logs/arc_reactor.log
|
||||||
|
StandardError=append:/home/jarvis.orbishosting.com/logs/arc_reactor.log
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
+35
-4
@@ -80,11 +80,42 @@ while IFS= read -r path; do
|
|||||||
systemctl reload lsws 2>/dev/null || systemctl restart lsws 2>/dev/null
|
systemctl reload lsws 2>/dev/null || systemctl restart lsws 2>/dev/null
|
||||||
log "OLS reloaded for JARVIS deploy"
|
log "OLS reloaded for JARVIS deploy"
|
||||||
|
|
||||||
# Sync reactor.py to runtime location if it changed
|
# Patch live config.php with correct Ollama host/model and Groq search model
|
||||||
if echo "$CHANGED" | grep -q 'deploy/reactor.py'; then
|
CONFIG="$path/api/config.php"
|
||||||
|
if [ -f "$CONFIG" ]; then
|
||||||
|
sed -i "s|http://10\.48\.200\.95:11434|http://10.48.200.210:11434|g" "$CONFIG"
|
||||||
|
sed -i "s|'llama3\.2:1b'|'llama3.1:8b'|g" "$CONFIG"
|
||||||
|
sed -i "s|\"llama3\.2:1b\"|\"llama3.1:8b\"|g" "$CONFIG"
|
||||||
|
sed -i "s|'llama3\.1:70b'|'llama3.1:8b'|g" "$CONFIG"
|
||||||
|
sed -i "s|\"llama3\.1:70b\"|\"llama3.1:8b\"|g" "$CONFIG"
|
||||||
|
sed -i "s|'groq/compound-mini'|'compound-beta-mini'|g;s|\"groq/compound-mini\"|\"compound-beta-mini\"|g" "$CONFIG"
|
||||||
|
log "Patched config.php: Ollama IP→.210, model→llama3.1:8b, Groq search→compound-beta-mini"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Self-install the deploy script on every run
|
||||||
|
cp "$path/deploy/jarvis-deploy.sh" /usr/local/bin/jarvis-deploy.sh 2>/dev/null && chmod +x /usr/local/bin/jarvis-deploy.sh
|
||||||
|
|
||||||
|
# Sync reactor.py + service file to runtime location if they changed
|
||||||
|
if echo "$CHANGED" | grep -q 'deploy/reactor.py\|deploy/jarvis-arc.service\|deploy/requirements.txt'; then
|
||||||
|
mkdir -p /opt/jarvis-arc /home/jarvis.orbishosting.com/logs
|
||||||
cp "$path/deploy/reactor.py" /opt/jarvis-arc/reactor.py
|
cp "$path/deploy/reactor.py" /opt/jarvis-arc/reactor.py
|
||||||
systemctl restart jarvis-arc
|
cp "$path/deploy/requirements.txt" /opt/jarvis-arc/requirements.txt 2>/dev/null
|
||||||
log "Arc Reactor updated and restarted (reactor.py changed)"
|
# Bootstrap venv if it doesn't exist
|
||||||
|
if [ ! -f /opt/jarvis-arc/venv/bin/activate ]; then
|
||||||
|
log "Arc Reactor venv missing — creating and installing packages"
|
||||||
|
python3 -m venv /opt/jarvis-arc/venv
|
||||||
|
/opt/jarvis-arc/venv/bin/pip install -q -r /opt/jarvis-arc/requirements.txt
|
||||||
|
log "Arc Reactor venv ready"
|
||||||
|
fi
|
||||||
|
if echo "$CHANGED" | grep -q 'deploy/jarvis-arc.service'; then
|
||||||
|
cp "$path/deploy/jarvis-arc.service" /etc/systemd/system/jarvis-arc.service
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable jarvis-arc
|
||||||
|
log "Arc Reactor service file installed and enabled"
|
||||||
|
fi
|
||||||
|
systemctl restart jarvis-arc 2>/dev/null || \
|
||||||
|
bash -c "pkill -f reactor.py 2>/dev/null; sleep 1; cd /opt/jarvis-arc && source venv/bin/activate && nohup python3 reactor.py >> /home/jarvis.orbishosting.com/logs/arc_reactor.log 2>&1 &"
|
||||||
|
log "Arc Reactor updated and restarted"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+108
-52
@@ -49,8 +49,8 @@ CLAUDE_API_KEY = "sk-ant-api03-JL6vjFeyEfajQmaTOmsT6AfLLPs2icrIAvvJ0hdi4DuMi0155
|
|||||||
CLAUDE_MODEL = "claude-sonnet-4-6"
|
CLAUDE_MODEL = "claude-sonnet-4-6"
|
||||||
GROQ_API_KEY = "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"
|
GROQ_API_KEY = "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"
|
||||||
GROQ_MODEL = "llama-3.3-70b-versatile"
|
GROQ_MODEL = "llama-3.3-70b-versatile"
|
||||||
OLLAMA_HOST = "http://10.48.200.95:11434"
|
OLLAMA_HOST = "http://10.48.200.210:11434"
|
||||||
OLLAMA_MODEL = "llama3.2:1b"
|
OLLAMA_MODEL = "llama3.1:8b"
|
||||||
|
|
||||||
GMAIL_USER = "myronblair@gmail.com"
|
GMAIL_USER = "myronblair@gmail.com"
|
||||||
GMAIL_PASS = "demsvdylwweacbcx"
|
GMAIL_PASS = "demsvdylwweacbcx"
|
||||||
@@ -175,6 +175,16 @@ async def _ollama_call(messages: list, system: str = "") -> str:
|
|||||||
data = await resp.json()
|
data = await resp.json()
|
||||||
return data.get("response", "")
|
return data.get("response", "")
|
||||||
|
|
||||||
|
async def _ollama_vision_call(image_b64: str, prompt: str) -> str:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.post(
|
||||||
|
f"{OLLAMA_HOST}/api/generate",
|
||||||
|
json={"model": "llava:7b", "prompt": prompt, "images": [image_b64], "stream": False},
|
||||||
|
timeout=aiohttp.ClientTimeout(total=120),
|
||||||
|
) as resp:
|
||||||
|
data = await resp.json()
|
||||||
|
return data.get("response", "").strip()
|
||||||
|
|
||||||
async def handle_llm(payload: dict) -> dict:
|
async def handle_llm(payload: dict) -> dict:
|
||||||
message = payload.get("message", "")
|
message = payload.get("message", "")
|
||||||
system = payload.get("system", "You are JARVIS, an Iron Man-style AI assistant.")
|
system = payload.get("system", "You are JARVIS, an Iron Man-style AI assistant.")
|
||||||
@@ -658,38 +668,44 @@ async def handle_screenshot(payload: dict) -> dict:
|
|||||||
height = result.get("height", 0)
|
height = result.get("height", 0)
|
||||||
file_size = result.get("file_size", 0)
|
file_size = result.get("file_size", 0)
|
||||||
|
|
||||||
# Run Claude vision analysis if we have an image
|
# Run vision analysis if we have an image — llava first, Claude fallback
|
||||||
analysis = ""
|
analysis = ""
|
||||||
provider_used = ""
|
provider_used = ""
|
||||||
if do_analyze and image_b64:
|
if do_analyze and image_b64:
|
||||||
try:
|
try:
|
||||||
import anthropic
|
analysis = await _ollama_vision_call(image_b64, analyze_prompt)
|
||||||
client = anthropic.AsyncAnthropic(api_key=CLAUDE_API_KEY)
|
provider_used = "ollama:llava"
|
||||||
msg = await client.messages.create(
|
log.info(f"[VISION] llava analysis complete ({len(analysis)} chars)")
|
||||||
model="claude-opus-4-8-20251101",
|
|
||||||
max_tokens=1024,
|
|
||||||
messages=[{
|
|
||||||
"role": "user",
|
|
||||||
"content": [
|
|
||||||
{"type": "image", "source": {"type": "base64",
|
|
||||||
"media_type": "image/png", "data": image_b64}},
|
|
||||||
{"type": "text", "text": analyze_prompt},
|
|
||||||
],
|
|
||||||
}],
|
|
||||||
)
|
|
||||||
analysis = msg.content[0].text if msg.content else ""
|
|
||||||
provider_used = "claude"
|
|
||||||
log.info(f"[VISION] Claude analysis complete ({len(analysis)} chars)")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warning(f"[VISION] Claude vision failed: {e}")
|
log.warning(f"[VISION] llava failed: {e} — falling back to Claude")
|
||||||
analysis = f"Vision analysis unavailable: {e}"
|
try:
|
||||||
|
import anthropic
|
||||||
|
client = anthropic.AsyncAnthropic(api_key=CLAUDE_API_KEY)
|
||||||
|
msg = await client.messages.create(
|
||||||
|
model="claude-opus-4-8-20251101",
|
||||||
|
max_tokens=1024,
|
||||||
|
messages=[{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{"type": "image", "source": {"type": "base64",
|
||||||
|
"media_type": "image/png", "data": image_b64}},
|
||||||
|
{"type": "text", "text": analyze_prompt},
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
)
|
||||||
|
analysis = msg.content[0].text if msg.content else ""
|
||||||
|
provider_used = "claude"
|
||||||
|
log.info(f"[VISION] Claude fallback analysis complete ({len(analysis)} chars)")
|
||||||
|
except Exception as e2:
|
||||||
|
log.warning(f"[VISION] Claude fallback also failed: {e2}")
|
||||||
|
analysis = f"Vision analysis unavailable: {e2}"
|
||||||
elif do_analyze and not image_b64 and result.get("snapshot_type") == "text":
|
elif do_analyze and not image_b64 and result.get("snapshot_type") == "text":
|
||||||
# Text-only sysinfo snapshot — summarize with LLM
|
# Text-only sysinfo snapshot — summarize with Ollama
|
||||||
try:
|
try:
|
||||||
snap_text = json.dumps(result, indent=2)[:3000]
|
snap_text = json.dumps(result, indent=2)[:3000]
|
||||||
prompt = f"Summarize this server system snapshot for JARVIS. Highlight any concerns:\n\n{snap_text}"
|
prompt = f"Summarize this server system snapshot for JARVIS. Highlight any concerns:\n\n{snap_text}"
|
||||||
analysis = await llm_call([{"role": "user", "content": prompt}], "groq")
|
analysis = await llm_call([{"role": "user", "content": prompt}], "ollama")
|
||||||
provider_used = "groq"
|
provider_used = "ollama"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
analysis = f"Analysis unavailable: {e}"
|
analysis = f"Analysis unavailable: {e}"
|
||||||
|
|
||||||
@@ -742,32 +758,47 @@ async def handle_vision(payload: dict) -> dict:
|
|||||||
if not image_b64:
|
if not image_b64:
|
||||||
raise ValueError("No image data provided")
|
raise ValueError("No image data provided")
|
||||||
|
|
||||||
log.info(f"[VISION] Analysis: screenshot_id={screenshot_id} agent={hostname}")
|
log.info(f"[VISION] Analysis: screenshot_id={screenshot_id} agent={hostname} provider={provider}")
|
||||||
|
|
||||||
try:
|
analysis = ""
|
||||||
import anthropic
|
provider_used = provider
|
||||||
client = anthropic.AsyncAnthropic(api_key=CLAUDE_API_KEY)
|
|
||||||
msg = await client.messages.create(
|
if provider == "ollama" or provider == "llava":
|
||||||
model="claude-opus-4-8-20251101",
|
analysis = await _ollama_vision_call(image_b64, prompt)
|
||||||
max_tokens=2048,
|
provider_used = "ollama:llava"
|
||||||
messages=[{
|
else:
|
||||||
"role": "user",
|
# Default: llava first, Claude fallback
|
||||||
"content": [
|
try:
|
||||||
{"type": "image", "source": {"type": "base64",
|
analysis = await _ollama_vision_call(image_b64, prompt)
|
||||||
"media_type": "image/png", "data": image_b64}},
|
provider_used = "ollama:llava"
|
||||||
{"type": "text", "text": prompt},
|
log.info(f"[VISION] llava analysis complete ({len(analysis)} chars)")
|
||||||
],
|
except Exception as e:
|
||||||
}],
|
log.warning(f"[VISION] llava failed: {e} — falling back to Claude")
|
||||||
)
|
try:
|
||||||
analysis = msg.content[0].text if msg.content else ""
|
import anthropic
|
||||||
except Exception as e:
|
client = anthropic.AsyncAnthropic(api_key=CLAUDE_API_KEY)
|
||||||
raise RuntimeError(f"Vision analysis failed: {e}")
|
msg = await client.messages.create(
|
||||||
|
model="claude-opus-4-8-20251101",
|
||||||
|
max_tokens=2048,
|
||||||
|
messages=[{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{"type": "image", "source": {"type": "base64",
|
||||||
|
"media_type": "image/png", "data": image_b64}},
|
||||||
|
{"type": "text", "text": prompt},
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
)
|
||||||
|
analysis = msg.content[0].text if msg.content else ""
|
||||||
|
provider_used = "claude"
|
||||||
|
except Exception as e2:
|
||||||
|
raise RuntimeError(f"Vision analysis failed (llava: {e}, claude: {e2})")
|
||||||
|
|
||||||
# Update stored screenshot if we have an ID
|
# Update stored screenshot if we have an ID
|
||||||
if screenshot_id:
|
if screenshot_id:
|
||||||
await db_execute(
|
await db_execute(
|
||||||
"UPDATE agent_screenshots SET vision_analysis=%s, vision_provider=%s WHERE id=%s",
|
"UPDATE agent_screenshots SET vision_analysis=%s, vision_provider=%s WHERE id=%s",
|
||||||
(analysis, "claude", int(screenshot_id))
|
(analysis, provider_used, int(screenshot_id))
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -775,7 +806,7 @@ async def handle_vision(payload: dict) -> dict:
|
|||||||
"screenshot_id": screenshot_id,
|
"screenshot_id": screenshot_id,
|
||||||
"prompt": prompt,
|
"prompt": prompt,
|
||||||
"analysis": analysis,
|
"analysis": analysis,
|
||||||
"provider": "claude",
|
"provider": provider_used,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1022,7 +1053,7 @@ async def guardian_loop() -> None:
|
|||||||
"for Myron. Be direct about severity and what action to take. "
|
"for Myron. Be direct about severity and what action to take. "
|
||||||
"No markdown, no headers."
|
"No markdown, no headers."
|
||||||
)
|
)
|
||||||
ai_msg = await llm_call([{"role": "user", "content": ai_prompt}], "groq")
|
ai_msg = await llm_call([{"role": "user", "content": ai_prompt}], "ollama")
|
||||||
# Update the most recent guardian event with AI analysis
|
# Update the most recent guardian event with AI analysis
|
||||||
await db_execute(
|
await db_execute(
|
||||||
"""UPDATE guardian_events SET ai_analysis=%s
|
"""UPDATE guardian_events SET ai_analysis=%s
|
||||||
@@ -1051,7 +1082,7 @@ async def _guardian_inject_chat(message: str) -> None:
|
|||||||
"""Write a proactive JARVIS message into the conversations table so the HUD picks it up."""
|
"""Write a proactive JARVIS message into the conversations table so the HUD picks it up."""
|
||||||
try:
|
try:
|
||||||
await db_execute(
|
await db_execute(
|
||||||
"""INSERT INTO conversations (session_id, role, message, created_at)
|
"""INSERT INTO conversations (session_id, role, content, created_at)
|
||||||
VALUES ('guardian', 'assistant', %s, NOW())""",
|
VALUES ('guardian', 'assistant', %s, NOW())""",
|
||||||
(message,)
|
(message,)
|
||||||
)
|
)
|
||||||
@@ -1065,7 +1096,7 @@ async def handle_sitrep(payload: dict) -> dict:
|
|||||||
payload: { detail: brief|full, provider: groq }
|
payload: { detail: brief|full, provider: groq }
|
||||||
"""
|
"""
|
||||||
detail = payload.get("detail", "full")
|
detail = payload.get("detail", "full")
|
||||||
provider = payload.get("provider", "groq")
|
provider = payload.get("provider", "ollama")
|
||||||
|
|
||||||
log.info(f"[GUARDIAN] SITREP requested (detail={detail})")
|
log.info(f"[GUARDIAN] SITREP requested (detail={detail})")
|
||||||
|
|
||||||
@@ -1806,7 +1837,7 @@ Keep the tone confident and action-oriented. Format with clear sections. Under 4
|
|||||||
|
|
||||||
# Store in conversations so HUD can surface it
|
# Store in conversations so HUD can surface it
|
||||||
await db_execute(
|
await db_execute(
|
||||||
"INSERT INTO conversations (session_id, role, message, created_at) VALUES ('guardian','assistant',%s,NOW())",
|
"INSERT INTO conversations (session_id, role, content, created_at) VALUES ('guardian','assistant',%s,NOW())",
|
||||||
(review_text,)
|
(review_text,)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2209,6 +2240,31 @@ async def lifespan(app: FastAPI):
|
|||||||
log.info(f"◈ JARVIS Arc Reactor v{VERSION} starting on {HOST}:{PORT}")
|
log.info(f"◈ JARVIS Arc Reactor v{VERSION} starting on {HOST}:{PORT}")
|
||||||
await get_pool()
|
await get_pool()
|
||||||
await db_execute("UPDATE arc_status SET started_at=NOW(), last_heartbeat=NOW(), version=%s WHERE id=1", (VERSION,))
|
await db_execute("UPDATE arc_status SET started_at=NOW(), last_heartbeat=NOW(), version=%s WHERE id=1", (VERSION,))
|
||||||
|
await db_execute("""CREATE TABLE IF NOT EXISTS guardian_config (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
key_name VARCHAR(64) NOT NULL,
|
||||||
|
value VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY uk_key (key_name)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci""")
|
||||||
|
await db_execute("""CREATE TABLE IF NOT EXISTS guardian_events (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
event_type VARCHAR(64) NOT NULL,
|
||||||
|
severity ENUM('info','warning','critical') NOT NULL DEFAULT 'info',
|
||||||
|
agent_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
hostname VARCHAR(128) NOT NULL DEFAULT '',
|
||||||
|
metric VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
value FLOAT NOT NULL DEFAULT 0,
|
||||||
|
threshold FLOAT NOT NULL DEFAULT 0,
|
||||||
|
message TEXT NOT NULL,
|
||||||
|
ai_analysis TEXT NOT NULL DEFAULT '',
|
||||||
|
acknowledged TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
KEY idx_severity (severity),
|
||||||
|
KEY idx_ack (acknowledged),
|
||||||
|
KEY idx_created (created_at),
|
||||||
|
KEY idx_agent (agent_id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci""")
|
||||||
asyncio.create_task(job_poller())
|
asyncio.create_task(job_poller())
|
||||||
asyncio.create_task(heartbeat_loop())
|
asyncio.create_task(heartbeat_loop())
|
||||||
asyncio.create_task(guardian_loop())
|
asyncio.create_task(guardian_loop())
|
||||||
@@ -2728,13 +2784,13 @@ async def guardian_chat_events(since: str = ""):
|
|||||||
"""Return proactive guardian messages injected into conversations."""
|
"""Return proactive guardian messages injected into conversations."""
|
||||||
if since:
|
if since:
|
||||||
rows = await db_fetchall(
|
rows = await db_fetchall(
|
||||||
"SELECT id, message, created_at FROM conversations "
|
"SELECT id, content AS message, created_at FROM conversations "
|
||||||
"WHERE session_id='guardian' AND created_at > %s ORDER BY created_at ASC",
|
"WHERE session_id='guardian' AND created_at > %s ORDER BY created_at ASC",
|
||||||
(since,)
|
(since,)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
rows = await db_fetchall(
|
rows = await db_fetchall(
|
||||||
"SELECT id, message, created_at FROM conversations "
|
"SELECT id, content AS message, created_at FROM conversations "
|
||||||
"WHERE session_id='guardian' ORDER BY created_at DESC LIMIT 10"
|
"WHERE session_id='guardian' ORDER BY created_at DESC LIMIT 10"
|
||||||
)
|
)
|
||||||
return rows or []
|
return rows or []
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
fastapi
|
||||||
|
uvicorn[standard]
|
||||||
|
aiomysql
|
||||||
|
aiohttp
|
||||||
|
anthropic
|
||||||
|
trafilatura
|
||||||
@@ -550,8 +550,22 @@ if ($action) {
|
|||||||
j(['ok'=>true,'msg'=>ucwords(str_replace('_',' ',$wId)).' triggered']);
|
j(['ok'=>true,'msg'=>ucwords(str_replace('_',' ',$wId)).' triggered']);
|
||||||
} else { bad('Unknown cron worker'); }
|
} else { bad('Unknown cron worker'); }
|
||||||
} elseif ($wType === 'daemon' && $wId === 'arc_reactor' && $wAction === 'restart') {
|
} elseif ($wType === 'daemon' && $wId === 'arc_reactor' && $wAction === 'restart') {
|
||||||
shell_exec('pkill -f reactor.py 2>/dev/null; sleep 1; cd /opt/jarvis-arc && source venv/bin/activate && nohup python3 reactor.py >> /home/jarvis.orbishosting.com/logs/arc_reactor.log 2>&1 &');
|
shell_exec('bash -c "mkdir -p /home/jarvis.orbishosting.com/logs; systemctl restart jarvis-arc 2>/dev/null || (pkill -f reactor.py 2>/dev/null; sleep 1; cd /opt/jarvis-arc && source venv/bin/activate && nohup python3 reactor.py >> /home/jarvis.orbishosting.com/logs/arc_reactor.log 2>&1 &)"');
|
||||||
j(['ok'=>true,'msg'=>'Arc Reactor restarting']);
|
j(['ok'=>true,'msg'=>'Arc Reactor restarting']);
|
||||||
|
} elseif ($wType === 'daemon' && $wId === 'arc_reactor' && $wAction === 'setup') {
|
||||||
|
$setupLog = '/home/jarvis.orbishosting.com/logs/arc_reactor.log';
|
||||||
|
$cmd = 'bash -c "'.
|
||||||
|
'mkdir -p /opt/jarvis-arc /home/jarvis.orbishosting.com/logs && '.
|
||||||
|
'cp /var/www/jarvis/deploy/reactor.py /opt/jarvis-arc/reactor.py && '.
|
||||||
|
'cp /var/www/jarvis/deploy/requirements.txt /opt/jarvis-arc/requirements.txt && '.
|
||||||
|
'if [ ! -d /opt/jarvis-arc/venv ]; then python3 -m venv /opt/jarvis-arc/venv; fi && '.
|
||||||
|
'/opt/jarvis-arc/venv/bin/pip install -q -r /opt/jarvis-arc/requirements.txt && '.
|
||||||
|
'pkill -f reactor.py 2>/dev/null; sleep 1 && '.
|
||||||
|
'cd /opt/jarvis-arc && source venv/bin/activate && '.
|
||||||
|
'nohup python3 reactor.py >> '.$setupLog.' 2>&1 &'.
|
||||||
|
'" >> '.$setupLog.' 2>&1';
|
||||||
|
shell_exec($cmd);
|
||||||
|
j(['ok'=>true,'msg'=>'Arc Reactor setup started — check log in ~30s then restart']);
|
||||||
} else { bad('Invalid worker action'); }
|
} else { bad('Invalid worker action'); }
|
||||||
break;
|
break;
|
||||||
case 'arc_status':
|
case 'arc_status':
|
||||||
@@ -2332,7 +2346,10 @@ async function loadWorkers() {
|
|||||||
<td class="ts">jarvis-do :7474</td>
|
<td class="ts">jarvis-do :7474</td>
|
||||||
<td>${rdot}${ron?'<span style="color:var(--green)">ONLINE</span>':'<span style="color:var(--red)">OFFLINE</span>'}</td>
|
<td>${rdot}${ron?'<span style="color:var(--green)">ONLINE</span>':'<span style="color:var(--red)">OFFLINE</span>'}</td>
|
||||||
<td class="ts">${rinfo}</td>
|
<td class="ts">${rinfo}</td>
|
||||||
<td><button onclick="workerAction('daemon','arc_reactor','restart')" style="${wBtn('red')}">↻ RESTART</button></td>
|
<td style="display:flex;gap:4px">
|
||||||
|
<button onclick="workerAction('daemon','arc_reactor','restart')" style="${wBtn('red')}">↻ RESTART</button>
|
||||||
|
<button onclick="workerAction('daemon','arc_reactor','setup')" style="${wBtn('orange')}">⚙ SETUP</button>
|
||||||
|
</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
}
|
}
|
||||||
async function loadDashboard() {
|
async function loadDashboard() {
|
||||||
|
|||||||
+4
-15
@@ -13,8 +13,8 @@ if (!defined('WEBHOOK_SECRET')) {
|
|||||||
echo json_encode(['error' => 'Webhook not configured']);
|
echo json_encode(['error' => 'Webhook not configured']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
define('DEPLOY_QUEUE', '/tmp/jarvis-deploy-queue.txt');
|
define('DEPLOY_QUEUE', '/tmp/jarvis-deploy-queue.txt');
|
||||||
define('DEPLOY_LOG', '/var/www/jarvis/logs/deploy.log');
|
define('DEPLOY_LOG', '/var/www/jarvis/logs/deploy.log');
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
@@ -50,20 +50,9 @@ if (!isset($repoMap[$repo])) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$path = $repoMap[$repo];
|
$path = $repoMap[$repo];
|
||||||
|
$ts = date('Y-m-d H:i:s');
|
||||||
// NovaCPX lives on a private VM — the VM polls GitHub every minute via cron
|
|
||||||
// This webhook receipt confirms GitHub delivered the push notification
|
|
||||||
if ($path === '__NOVACPX_VM__') {
|
|
||||||
$commit = $data['after'] ?? 'HEAD';
|
|
||||||
$msg = "[" . date('Y-m-d H:i:s') . "] NovaCPX push by $pusher (commit: $commit) — VM will deploy within 1 min";
|
|
||||||
file_put_contents(DEPLOY_LOG, $msg . "\n", FILE_APPEND | LOCK_EX);
|
|
||||||
echo json_encode(['ok' => true, 'queued' => 'novacpx', 'commit' => $commit]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
file_put_contents(DEPLOY_QUEUE, $path . "\n", FILE_APPEND | LOCK_EX);
|
file_put_contents(DEPLOY_QUEUE, $path . "\n", FILE_APPEND | LOCK_EX);
|
||||||
|
file_put_contents(DEPLOY_LOG, "[$ts] Queued deploy: $repo by $pusher -> $path\n", FILE_APPEND | LOCK_EX);
|
||||||
$msg = "[" . date('Y-m-d H:i:s') . "] Queued deploy: $repo by $pusher -> $path";
|
|
||||||
file_put_contents(DEPLOY_LOG, $msg . "\n", FILE_APPEND | LOCK_EX);
|
|
||||||
|
|
||||||
echo json_encode(['ok' => true, 'queued' => $repo, 'path' => $path]);
|
echo json_encode(['ok' => true, 'queued' => $repo, 'path' => $path]);
|
||||||
|
|||||||
Reference in New Issue
Block a user