Files
jarvis/api/endpoints/arc.php
T
myron b6c417948e Phase 7: Mission Ops — multi-step automated workflow engine
- DB: missions, mission_steps, mission_runs tables
- reactor.py v7.0.0: handle_run_mission, _execute_mission, mission_trigger_loop (schedule/guardian_event/email_keyword triggers), {{template}} substitution across steps, full CRUD REST endpoints
- arc.php: missions/mission_get/mission_runs/mission_create/mission_update/mission_delete/mission_run/mission_toggle actions
- admin/index.php: Mission Ops tab with visual workflow builder (trigger config, step cards with ↑↓, JSON payload editor, continue-on-failure flag), run history with step-level detail, enable/disable toggle
- index.html: MISSIONS tab with collapsible mission cards, RUN NOW button per mission, live run result feedback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 11:49:07 +00:00

299 lines
13 KiB
PHP

<?php
/**
* JARVIS Arc Reactor Bridge
* Proxies job requests to the Arc Reactor daemon at 127.0.0.1:7474
*/
define('ARC_REACTOR_URL', 'http://127.0.0.1:7474');
define('ARC_TIMEOUT', 8);
function arc_request(string $method, string $path, array $body = []): array {
$url = ARC_REACTOR_URL . $path;
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => ARC_TIMEOUT,
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
]);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
} elseif ($method === 'DELETE') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
}
$raw = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = curl_error($ch);
curl_close($ch);
if ($err || $raw === false) {
return ['error' => 'Arc Reactor unreachable: ' . $err, 'online' => false];
}
$decoded = json_decode($raw, true);
return $decoded ?? ['error' => 'Invalid response from Arc Reactor', 'raw' => substr($raw, 0, 200)];
}
// ── ROUTING ───────────────────────────────────────────────────────────────────
// arc action comes from query string or POST body (not the URL path segment)
global $data;
$action = $_GET['action'] ?? $data['action'] ?? '';
switch ($action) {
// GET /api/arc?action=status
case 'status':
$result = arc_request('GET', '/status');
if (!isset($result['online'])) $result['online'] = false;
echo json_encode($result);
break;
// POST /api/arc — create a job
case 'job_create':
$type = $data['type'] ?? '';
$payload = $data['payload'] ?? [];
$priority = (int)($data['priority'] ?? 5);
if (!$type) { http_response_code(400); echo json_encode(['error' => 'Missing job type']); break; }
$result = arc_request('POST', '/job', [
'type' => $type,
'payload' => $payload,
'priority' => $priority,
'created_by' => 'jarvis_ui',
]);
echo json_encode($result);
break;
// GET /api/arc?action=job_get&id=123
case 'job_get':
$id = (int)($data['id'] ?? $_GET['id'] ?? 0);
if (!$id) { http_response_code(400); echo json_encode(['error' => 'Missing job id']); break; }
echo json_encode(arc_request('GET', "/job/{$id}"));
break;
// GET /api/arc?action=jobs&status=done&limit=20
case 'jobs':
$status = $_GET['status'] ?? $data['status'] ?? '';
$limit = (int)($_GET['limit'] ?? $data['limit'] ?? 50);
$qs = http_build_query(array_filter(['status' => $status, 'limit' => $limit]));
echo json_encode(arc_request('GET', '/jobs' . ($qs ? "?{$qs}" : '')));
break;
// DELETE /api/arc?action=job_cancel&id=123
case 'job_cancel':
$id = (int)($data['id'] ?? $_GET['id'] ?? 0);
if (!$id) { http_response_code(400); echo json_encode(['error' => 'Missing job id']); break; }
echo json_encode(arc_request('DELETE', "/job/{$id}"));
break;
// DELETE /api/arc?action=purge
case 'purge':
echo json_encode(arc_request('DELETE', '/jobs/purge'));
break;
// Quick ping test
case 'ping':
$result = arc_request('POST', '/job', [
'type' => 'ping',
'payload' => [],
'priority' => 9,
'created_by' => 'jarvis_ping',
]);
echo json_encode($result);
break;
// GET /api/arc?action=triage&limit=50&filter=priority
// Returns email_triage rows for the COMMS tab
case 'triage':
$limit = min((int)($_GET['limit'] ?? 50), 100);
$filter = $_GET['filter'] ?? 'priority';
if ($filter === 'urgent') {
$sql = "SELECT id, account, from_name, from_email, subject, date_received,
category, priority, summary, draft_reply, action_taken, created_at
FROM email_triage
WHERE action_taken != 'dismissed' AND category = 'urgent'
ORDER BY priority DESC, created_at DESC LIMIT ?";
} elseif ($filter === 'action') {
$sql = "SELECT id, account, from_name, from_email, subject, date_received,
category, priority, summary, draft_reply, action_taken, created_at
FROM email_triage
WHERE action_taken != 'dismissed' AND category IN ('urgent','action','reply','meeting')
ORDER BY priority DESC, created_at DESC LIMIT ?";
} elseif ($filter === 'priority') {
$sql = "SELECT id, account, from_name, from_email, subject, date_received,
category, priority, summary, draft_reply, action_taken, created_at
FROM email_triage
WHERE action_taken != 'dismissed' AND category IN ('urgent','action','reply','meeting')
AND priority >= 5
ORDER BY priority DESC, created_at DESC LIMIT ?";
} else {
$sql = "SELECT id, account, from_name, from_email, subject, date_received,
category, priority, summary, draft_reply, action_taken, created_at
FROM email_triage
WHERE action_taken != 'dismissed'
ORDER BY priority DESC, created_at DESC LIMIT ?";
}
$rows = JarvisDB::query($sql, [$limit]);
echo json_encode($rows ?: []);
break;
// POST /api/arc?action=triage_action&id=123 body: { action: "dismissed"|"replied"|"done" }
case 'triage_action':
$id = (int)($_GET['id'] ?? $data['id'] ?? 0);
$actionTaken = $data['action'] ?? 'dismissed';
$allowed = ['dismissed', 'replied', 'done', 'snoozed'];
if (!$id || !in_array($actionTaken, $allowed)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid id or action']);
break;
}
JarvisDB::execute(
"UPDATE email_triage SET action_taken = ? WHERE id = ?",
[$actionTaken, $id]
);
echo json_encode(['ok' => true, 'id' => $id, 'action_taken' => $actionTaken]);
break;
// GET /api/arc?action=screenshots&limit=20&agent=hostname
case 'screenshots':
$limit = min((int)($_GET['limit'] ?? 20), 100);
$agent = $_GET['agent'] ?? '';
$url = 'http://127.0.0.1:7474/screenshots?' . http_build_query(array_filter(['limit' => $limit, 'agent' => $agent]));
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5]);
$raw = curl_exec($ch); curl_close($ch);
echo $raw ?: '[]';
break;
// GET /api/arc?action=screenshot_get&id=123
case 'screenshot_get':
$id = (int)($_GET['id'] ?? 0);
if (!$id) { http_response_code(400); echo json_encode(['error' => 'Missing id']); break; }
echo json_encode(arc_request('GET', "/screenshots/{$id}"));
break;
// DELETE /api/arc?action=screenshot_delete&id=123
case 'screenshot_delete':
$id = (int)($_GET['id'] ?? 0);
if (!$id) { http_response_code(400); echo json_encode(['error' => 'Missing id']); break; }
echo json_encode(arc_request('DELETE', "/screenshots/{$id}"));
break;
// ── GUARDIAN MODE ─────────────────────────────────────────────────────────
case 'guardian_status':
echo json_encode(arc_request('GET', '/guardian/status'));
break;
case 'guardian_events':
$limit = (int)($_GET['limit'] ?? 30);
$unread = !empty($_GET['unread']) ? 'true' : '';
$severity = $_GET['severity'] ?? '';
$since = $_GET['since'] ?? '';
$qs = http_build_query(array_filter([
'limit' => $limit,
'unread' => $unread,
'severity' => $severity,
'since' => $since,
]));
echo json_encode(arc_request('GET', '/guardian/events' . ($qs ? "?{$qs}" : '')));
break;
case 'guardian_ack':
$id = (int)($_GET['id'] ?? $data['id'] ?? 0);
if ($id) {
echo json_encode(arc_request('POST', "/guardian/events/{$id}/ack"));
} else {
echo json_encode(arc_request('POST', '/guardian/events/ack_all'));
}
break;
case 'guardian_chat':
$since = $_GET['since'] ?? '';
$qs = $since ? '?since=' . urlencode($since) : '';
echo json_encode(arc_request('GET', '/guardian/chat' . $qs));
break;
// ── COMMS v2 ──────────────────────────────────────────────────────────────
// GET /api/arc?action=comms_sent&limit=50&status=sent
case 'comms_sent':
$limit = min((int)($_GET['limit'] ?? 50), 200);
$status = $_GET['status'] ?? '';
$qs = http_build_query(array_filter(['limit' => $limit, 'status' => $status]));
echo json_encode(arc_request('GET', '/comms/sent' . ($qs ? "?{$qs}" : '')));
break;
// GET /api/arc?action=comms_sent_get&id=123
case 'comms_sent_get':
$id = (int)($_GET['id'] ?? 0);
if (!$id) { http_response_code(400); echo json_encode(['error' => 'Missing id']); break; }
echo json_encode(arc_request('GET', "/comms/sent/{$id}"));
break;
// DELETE /api/arc?action=comms_sent_delete&id=123
case 'comms_sent_delete':
$id = (int)($_GET['id'] ?? 0);
if (!$id) { http_response_code(400); echo json_encode(['error' => 'Missing id']); break; }
echo json_encode(arc_request('DELETE', "/comms/sent/{$id}"));
break;
// ── MISSION OPS ───────────────────────────────────────────────────────────
// GET /api/arc?action=missions
case 'missions':
echo json_encode(arc_request('GET', '/missions'));
break;
// GET /api/arc?action=mission_get&id=123
case 'mission_get':
$id = (int)($_GET['id'] ?? $data['id'] ?? 0);
if (!$id) { http_response_code(400); echo json_encode(['error' => 'Missing id']); break; }
echo json_encode(arc_request('GET', "/missions/{$id}"));
break;
// GET /api/arc?action=mission_runs&id=123
case 'mission_runs':
$id = (int)($_GET['id'] ?? $data['id'] ?? 0);
$limit = (int)($_GET['limit'] ?? 20);
if (!$id) { http_response_code(400); echo json_encode(['error' => 'Missing id']); break; }
echo json_encode(arc_request('GET', "/missions/{$id}/runs?limit={$limit}"));
break;
// POST /api/arc?action=mission_create — body: { name, description, trigger_type, trigger_config, steps }
case 'mission_create':
echo json_encode(arc_request('POST', '/missions', $data));
break;
// POST /api/arc?action=mission_update — body: { id, name, ... steps }
case 'mission_update':
$id = (int)($data['id'] ?? 0);
if (!$id) { http_response_code(400); echo json_encode(['error' => 'Missing id']); break; }
echo json_encode(arc_request('PUT', "/missions/{$id}", $data));
break;
// DELETE /api/arc?action=mission_delete&id=123
case 'mission_delete':
$id = (int)($_GET['id'] ?? $data['id'] ?? 0);
if (!$id) { http_response_code(400); echo json_encode(['error' => 'Missing id']); break; }
echo json_encode(arc_request('DELETE', "/missions/{$id}"));
break;
// POST /api/arc?action=mission_run&id=123
case 'mission_run':
$id = (int)($_GET['id'] ?? $data['id'] ?? 0);
if (!$id) { http_response_code(400); echo json_encode(['error' => 'Missing id']); break; }
echo json_encode(arc_request('POST', "/missions/{$id}/run", ['trigger_source' => 'manual']));
break;
// POST /api/arc?action=mission_toggle&id=123 body: { enabled: 1|0 }
case 'mission_toggle':
$id = (int)($data['id'] ?? 0);
$enabled = (int)($data['enabled'] ?? 0);
if (!$id) { http_response_code(400); echo json_encode(['error' => 'Missing id']); break; }
echo json_encode(arc_request('PUT', "/missions/{$id}", ['enabled' => $enabled]));
break;
default:
http_response_code(404);
echo json_encode(['error' => "Unknown arc action: {$action}"]);
}