mirror of
https://github.com/myronblair/jarvis
synced 2026-06-30 17:50:23 -05:00
Phase 8: Mission Directives — OKR/goal tracking with AI review
- DB: directives, directive_key_results, directive_links tables - reactor.py v8.0.0: directive_review handler — fetches active directives + KRs + links, Claude generates executive progress briefing, injects into conversations - directives.php: new API endpoint (list/get/save/delete/key_result_update/link/summary) - api.php: routes directives/* endpoint - admin/index.php: Directives nav + tab — objective cards with progress bars, editor with multi-KR builder (title/current/target/unit), AI Review button per directive and global - index.html: DIRECTIVES tab — collapsible objective cards with progress bars, KR counts, AI Review button, link to admin - chat.php: Tier 0.9i directive review detection; daily briefing now includes active directive progress % Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -593,6 +593,111 @@ if ($action) {
|
||||
$raw = curl_exec($ch); curl_close($ch);
|
||||
j(json_decode($raw, true) ?: ['error' => 'Arc Reactor unreachable']);
|
||||
|
||||
// ── DIRECTIVES ───────────────────────────────────────────────────────
|
||||
case 'directive_list':
|
||||
$status = $_GET['status'] ?? 'active';
|
||||
$category = $_GET['category'] ?? '';
|
||||
$where = '1=1'; $params = [];
|
||||
if ($status && $status !== 'all') { $where .= ' AND d.status=?'; $params[] = $status; }
|
||||
if ($category) { $where .= ' AND d.category=?'; $params[] = $category; }
|
||||
$rows = JarvisDB::query(
|
||||
"SELECT d.*,
|
||||
COUNT(kr.id) AS kr_count,
|
||||
COALESCE(SUM(kr.current_value),0) AS kr_current_sum,
|
||||
COALESCE(SUM(kr.target_value),0) AS kr_target_sum,
|
||||
(SELECT COUNT(*) FROM directive_links dl WHERE dl.directive_id=d.id) AS link_count
|
||||
FROM directives d
|
||||
LEFT JOIN directive_key_results kr ON kr.directive_id=d.id
|
||||
WHERE {$where}
|
||||
GROUP BY d.id
|
||||
ORDER BY d.priority DESC, d.target_date ASC, d.created_at DESC",
|
||||
$params
|
||||
) ?: [];
|
||||
foreach ($rows as &$r) {
|
||||
$r['progress'] = ($r['kr_target_sum'] > 0)
|
||||
? (float)round($r['kr_current_sum'] / $r['kr_target_sum'] * 100, 1)
|
||||
: 0;
|
||||
}
|
||||
j(['directives' => $rows]);
|
||||
|
||||
case 'directive_get':
|
||||
$id = (int)($_GET['id'] ?? 0); if (!$id) bad('Missing id');
|
||||
$d = JarvisDB::single("SELECT * FROM directives WHERE id=?", [$id]);
|
||||
if (!$d) bad('Not found', 404);
|
||||
$krs = JarvisDB::query("SELECT * FROM directive_key_results WHERE directive_id=? ORDER BY id", [$id]) ?: [];
|
||||
$links = JarvisDB::query(
|
||||
"SELECT dl.*, COALESCE(t.title,a.title) AS linked_title
|
||||
FROM directive_links dl
|
||||
LEFT JOIN tasks t ON dl.link_type='task' AND t.id=dl.link_id
|
||||
LEFT JOIN appointments a ON dl.link_type='appointment' AND a.id=dl.link_id
|
||||
WHERE dl.directive_id=? ORDER BY dl.created_at DESC",
|
||||
[$id]
|
||||
) ?: [];
|
||||
$cur = array_sum(array_column($krs,'current_value'));
|
||||
$tgt = array_sum(array_column($krs,'target_value'));
|
||||
$d['progress'] = $tgt > 0 ? round($cur/$tgt*100,1) : 0;
|
||||
$d['key_results'] = $krs;
|
||||
$d['links'] = $links;
|
||||
j($d);
|
||||
|
||||
case 'directive_save':
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$body = file_get_contents('php://input');
|
||||
$data_in = json_decode($body, true) ?: [];
|
||||
$title = trim($data_in['title'] ?? '');
|
||||
$description = trim($data_in['description'] ?? '');
|
||||
$category = $data_in['category'] ?? 'work';
|
||||
$status = $data_in['status'] ?? 'active';
|
||||
$priority = (int)($data_in['priority'] ?? 5);
|
||||
$target_date = $data_in['target_date'] ?? null;
|
||||
$krs = $data_in['key_results'] ?? [];
|
||||
if (!$title) bad('Title required');
|
||||
if ($id) {
|
||||
JarvisDB::execute(
|
||||
"UPDATE directives SET title=?,description=?,category=?,status=?,priority=?,target_date=?,updated_at=NOW() WHERE id=?",
|
||||
[$title,$description,$category,$status,$priority,$target_date?:null,$id]
|
||||
);
|
||||
} else {
|
||||
$id = JarvisDB::insert(
|
||||
"INSERT INTO directives (title,description,category,status,priority,target_date) VALUES (?,?,?,?,?,?)",
|
||||
[$title,$description,$category,$status,$priority,$target_date?:null]
|
||||
);
|
||||
}
|
||||
if (is_array($krs)) {
|
||||
JarvisDB::execute("DELETE FROM directive_key_results WHERE directive_id=?", [$id]);
|
||||
foreach ($krs as $kr) {
|
||||
$krt = trim($kr['title'] ?? ''); if (!$krt) continue;
|
||||
JarvisDB::execute(
|
||||
"INSERT INTO directive_key_results (directive_id,title,current_value,target_value,unit) VALUES (?,?,?,?,?)",
|
||||
[$id,$krt,(float)($kr['current_value']??0),(float)($kr['target_value']??100),$kr['unit']??'%']
|
||||
);
|
||||
}
|
||||
}
|
||||
j(['ok' => true, 'id' => $id]);
|
||||
|
||||
case 'directive_delete':
|
||||
$id = (int)($_GET['id'] ?? 0); if (!$id) bad('Missing id');
|
||||
JarvisDB::execute("DELETE FROM directive_key_results WHERE directive_id=?", [$id]);
|
||||
JarvisDB::execute("DELETE FROM directive_links WHERE directive_id=?", [$id]);
|
||||
JarvisDB::execute("DELETE FROM directives WHERE id=?", [$id]);
|
||||
j(['ok' => true]);
|
||||
|
||||
case 'arc_action':
|
||||
$body = file_get_contents('php://input');
|
||||
$d = json_decode($body, true) ?: [];
|
||||
$type = $d['action'] === 'job_create' ? ($d['type'] ?? '') : '';
|
||||
$payload = $d['payload'] ?? [];
|
||||
$pri = (int)($d['priority'] ?? 5);
|
||||
if (!$type) bad('Missing type');
|
||||
$ch = curl_init('http://127.0.0.1:7474/job');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>10, CURLOPT_POST=>true,
|
||||
CURLOPT_POSTFIELDS => json_encode(['type'=>$type,'payload'=>$payload,'priority'=>$pri,'created_by'=>'admin']),
|
||||
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
|
||||
]);
|
||||
$raw = curl_exec($ch); curl_close($ch);
|
||||
j(json_decode($raw, true) ?: ['error'=>'Arc Reactor unreachable']);
|
||||
|
||||
// ── MISSION OPS ──────────────────────────────────────────────────────
|
||||
case 'mission_list':
|
||||
$ch = curl_init('http://127.0.0.1:7474/missions');
|
||||
@@ -994,6 +1099,7 @@ select.filter-sel:focus{border-color:var(--cyan)}
|
||||
<div class="nav-item" data-tab="vision" onclick="nav(this)">◈ VISION PROTOCOL</div>
|
||||
<div class="nav-item" data-tab="guardian" onclick="nav(this)" id="nav-guardian">◈ GUARDIAN MODE</div>
|
||||
<div class="nav-item" data-tab="missions" onclick="nav(this)">◈ MISSION OPS</div>
|
||||
<div class="nav-item" data-tab="directives" onclick="nav(this)">◈ DIRECTIVES</div>
|
||||
<div class="nav-section">INFO</div>
|
||||
<div class="nav-item" data-tab="sites" onclick="nav(this)">SITES</div>
|
||||
<div class="nav-item" data-tab="users" onclick="nav(this)">USERS</div>
|
||||
@@ -1458,6 +1564,99 @@ select.filter-sel:focus{border-color:var(--cyan)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DIRECTIVES -->
|
||||
<div class="tab" id="tab-directives">
|
||||
<div class="page-title">◈ MISSION DIRECTIVES — OBJECTIVES & KEY RESULTS</div>
|
||||
<div style="display:flex;gap:10px;margin-bottom:16px;align-items:center;flex-wrap:wrap">
|
||||
<button class="btn btn-sm btn-green" onclick="directiveNew()">+ NEW DIRECTIVE</button>
|
||||
<button class="btn btn-sm" onclick="directiveReviewAI()">◈ AI REVIEW</button>
|
||||
<button class="btn btn-sm" onclick="loadDirectives()">↻ REFRESH</button>
|
||||
<select id="dir-status-filter" class="inp" style="width:auto;padding:4px 8px;font-size:0.65rem" onchange="loadDirectives()">
|
||||
<option value="active">ACTIVE</option>
|
||||
<option value="all">ALL</option>
|
||||
<option value="paused">PAUSED</option>
|
||||
<option value="complete">COMPLETE</option>
|
||||
</select>
|
||||
<select id="dir-cat-filter" class="inp" style="width:auto;padding:4px 8px;font-size:0.65rem" onchange="loadDirectives()">
|
||||
<option value="">ALL CATEGORIES</option>
|
||||
<option value="work">WORK</option>
|
||||
<option value="personal">PERSONAL</option>
|
||||
<option value="health">HEALTH</option>
|
||||
<option value="finance">FINANCE</option>
|
||||
<option value="home">HOME</option>
|
||||
</select>
|
||||
<div id="directives-count" style="margin-left:auto;font-family:var(--mono);font-size:0.65rem;color:var(--dim)"></div>
|
||||
</div>
|
||||
|
||||
<div id="directives-list"><div class="loading">LOADING DIRECTIVES...</div></div>
|
||||
|
||||
<!-- Directive editor panel -->
|
||||
<div id="directive-editor" style="display:none;margin-top:20px;border:1px solid var(--border);border-radius:6px;padding:16px;background:rgba(0,212,255,0.02)">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px">
|
||||
<div id="dir-editor-title" style="font-family:var(--mono);font-size:0.75rem;letter-spacing:2px;color:var(--cyan)">◈ DIRECTIVE EDITOR</div>
|
||||
<button class="btn btn-xs" onclick="document.getElementById('directive-editor').style.display='none'">✕ CLOSE</button>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-bottom:12px">
|
||||
<div style="grid-column:1/3">
|
||||
<div class="lbl">OBJECTIVE TITLE</div>
|
||||
<input id="dir-title" class="inp" placeholder="What do you want to achieve?">
|
||||
</div>
|
||||
<div>
|
||||
<div class="lbl">STATUS</div>
|
||||
<select id="dir-status" class="inp">
|
||||
<option value="active">Active</option>
|
||||
<option value="paused">Paused</option>
|
||||
<option value="complete">Complete</option>
|
||||
<option value="cancelled">Cancelled</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-bottom:12px">
|
||||
<div>
|
||||
<div class="lbl">CATEGORY</div>
|
||||
<select id="dir-category" class="inp">
|
||||
<option value="work">Work</option>
|
||||
<option value="personal">Personal</option>
|
||||
<option value="health">Health</option>
|
||||
<option value="finance">Finance</option>
|
||||
<option value="home">Home</option>
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<div class="lbl">PRIORITY (1-10)</div>
|
||||
<input id="dir-priority" class="inp" type="number" min="1" max="10" value="5">
|
||||
</div>
|
||||
<div>
|
||||
<div class="lbl">TARGET DATE</div>
|
||||
<input id="dir-target-date" class="inp" type="date">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom:14px">
|
||||
<div class="lbl">DESCRIPTION</div>
|
||||
<textarea id="dir-desc" class="inp" rows="2" placeholder="Context, why this matters..."></textarea>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom:14px">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
||||
<div class="lbl" style="margin:0">KEY RESULTS</div>
|
||||
<button class="btn btn-xs btn-green" onclick="dirAddKR()">+ ADD KEY RESULT</button>
|
||||
</div>
|
||||
<div id="dir-kr-list"></div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="dir-id" value="">
|
||||
<div style="display:flex;gap:8px">
|
||||
<button class="btn btn-sm btn-green" onclick="directiveSave()">◈ SAVE</button>
|
||||
<button id="dir-del-btn" class="btn btn-sm btn-red" style="display:none" onclick="directiveDelete()">✗ DELETE</button>
|
||||
</div>
|
||||
<div id="dir-save-status" style="font-family:var(--mono);font-size:0.6rem;color:var(--cyan);margin-top:6px;min-height:14px"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /content -->
|
||||
</div><!-- /main -->
|
||||
</div><!-- /app -->
|
||||
@@ -1572,6 +1771,7 @@ function loadTab(tab) {
|
||||
triage: loadTriage,
|
||||
outbox: loadOutbox,
|
||||
missions: loadMissions,
|
||||
directives: loadDirectives,
|
||||
vision: loadVision,
|
||||
guardian: loadGuardian,
|
||||
tasks: loadTasks,
|
||||
@@ -3244,6 +3444,193 @@ async function missionToggle(id, enabled) {
|
||||
else toast('Toggle failed', 'err');
|
||||
}
|
||||
|
||||
// ── DIRECTIVES ───────────────────────────────────────────────────────────────
|
||||
|
||||
let _dirKRs = [];
|
||||
let _dirKRIdx = 0;
|
||||
|
||||
const CAT_COLORS = {work:'var(--cyan)',personal:'#a78bfa',health:'#00ff88',finance:'#ffd700',home:'var(--orange)',other:'var(--text-dim)'};
|
||||
|
||||
async function loadDirectives() {
|
||||
const el = document.getElementById('directives-list');
|
||||
if (!el) return;
|
||||
const status = document.getElementById('dir-status-filter')?.value || 'active';
|
||||
const category = document.getElementById('dir-cat-filter')?.value || '';
|
||||
const params = {status};
|
||||
if (category) params.category = category;
|
||||
const d = await api('directive_list', params);
|
||||
const list = d.directives || [];
|
||||
document.getElementById('directives-count').textContent = list.length + ' DIRECTIVES';
|
||||
if (!list.length) {
|
||||
el.innerHTML = '<div class="loading">No directives found. Click + NEW DIRECTIVE to create one.</div>';
|
||||
return;
|
||||
}
|
||||
const rows = list.map(dir => {
|
||||
const pct = Math.min(100, Math.round(dir.progress || 0));
|
||||
const catColor = CAT_COLORS[dir.category] || 'var(--text-dim)';
|
||||
const daysLeft = dir.target_date
|
||||
? Math.ceil((new Date(dir.target_date) - new Date()) / 86400000)
|
||||
: null;
|
||||
const dueBadge = daysLeft !== null
|
||||
? `<span style="font-family:var(--mono);font-size:0.55rem;color:${daysLeft<0?'var(--red)':daysLeft<14?'var(--orange)':'var(--text-dim)'}">
|
||||
${daysLeft<0?'OVERDUE '+Math.abs(daysLeft)+'d':daysLeft+'d left'}</span>`
|
||||
: '';
|
||||
const statusBadge = dir.status !== 'active'
|
||||
? `<span style="font-size:0.55rem;color:var(--dim);margin-left:4px">[${dir.status.toUpperCase()}]</span>`
|
||||
: '';
|
||||
return `<tr>
|
||||
<td style="min-width:200px">
|
||||
<div style="font-size:0.7rem;font-family:var(--mono)">${esc(dir.title)}${statusBadge}</div>
|
||||
<div style="font-size:0.58rem;color:${catColor};margin-top:2px">${dir.category.toUpperCase()} · P${dir.priority}</div>
|
||||
</td>
|
||||
<td style="min-width:160px">
|
||||
<div style="display:flex;align-items:center;gap:6px">
|
||||
<div style="flex:1;height:6px;background:rgba(255,255,255,0.08);border-radius:3px">
|
||||
<div style="width:${pct}%;height:100%;background:${pct>=80?'var(--green)':pct>=40?'var(--orange)':'var(--red)'};border-radius:3px"></div>
|
||||
</div>
|
||||
<span style="font-family:var(--mono);font-size:0.6rem;min-width:32px">${pct}%</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>${dueBadge}</td>
|
||||
<td style="font-family:var(--mono);font-size:0.58rem;color:var(--dim)">${dir.kr_count||0} KRs · ${dir.link_count||0} links</td>
|
||||
<td style="white-space:nowrap">
|
||||
<button class="btn btn-xs btn-green" onclick="directiveEdit(${dir.id})">EDIT</button>
|
||||
<button class="btn btn-xs" onclick="directiveReviewSingle(${dir.id})">◈ AI REVIEW</button>
|
||||
</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
el.innerHTML = `<table><thead><tr><th>OBJECTIVE</th><th>PROGRESS</th><th>DUE</th><th>DETAILS</th><th>ACTIONS</th></tr></thead><tbody>${rows}</tbody></table>`;
|
||||
}
|
||||
|
||||
function directiveNew() {
|
||||
_dirKRs = []; _dirKRIdx = 0;
|
||||
document.getElementById('dir-id').value = '';
|
||||
document.getElementById('dir-title').value = '';
|
||||
document.getElementById('dir-desc').value = '';
|
||||
document.getElementById('dir-category').value = 'work';
|
||||
document.getElementById('dir-status').value = 'active';
|
||||
document.getElementById('dir-priority').value = 5;
|
||||
document.getElementById('dir-target-date').value = '';
|
||||
document.getElementById('dir-editor-title').textContent = '◈ NEW DIRECTIVE';
|
||||
document.getElementById('dir-del-btn').style.display = 'none';
|
||||
document.getElementById('dir-save-status').textContent = '';
|
||||
_renderDirKRs();
|
||||
document.getElementById('directive-editor').style.display = 'block';
|
||||
document.getElementById('directive-editor').scrollIntoView({behavior:'smooth'});
|
||||
}
|
||||
|
||||
async function directiveEdit(id) {
|
||||
const d = await api('directive_get', {id});
|
||||
if (d.error) { toast('Load failed: ' + d.error, 'err'); return; }
|
||||
document.getElementById('dir-id').value = d.id;
|
||||
document.getElementById('dir-title').value = d.title || '';
|
||||
document.getElementById('dir-desc').value = d.description || '';
|
||||
document.getElementById('dir-category').value = d.category || 'work';
|
||||
document.getElementById('dir-status').value = d.status || 'active';
|
||||
document.getElementById('dir-priority').value = d.priority || 5;
|
||||
document.getElementById('dir-target-date').value = d.target_date || '';
|
||||
document.getElementById('dir-editor-title').textContent = '◈ EDIT — ' + esc(d.title);
|
||||
document.getElementById('dir-del-btn').style.display = '';
|
||||
document.getElementById('dir-save-status').textContent = '';
|
||||
_dirKRs = (d.key_results || []).map(kr => ({
|
||||
id: ++_dirKRIdx, dbid: kr.id,
|
||||
title: kr.title, current_value: kr.current_value,
|
||||
target_value: kr.target_value, unit: kr.unit || '%',
|
||||
}));
|
||||
_renderDirKRs();
|
||||
document.getElementById('directive-editor').style.display = 'block';
|
||||
document.getElementById('directive-editor').scrollIntoView({behavior:'smooth'});
|
||||
}
|
||||
|
||||
function dirAddKR() {
|
||||
_dirKRIdx++;
|
||||
_dirKRs.push({id: _dirKRIdx, dbid: null, title:'', current_value:0, target_value:100, unit:'%'});
|
||||
_renderDirKRs();
|
||||
}
|
||||
|
||||
function dirRemoveKR(sid) {
|
||||
_dirKRs = _dirKRs.filter(k => k.id !== sid);
|
||||
_renderDirKRs();
|
||||
}
|
||||
|
||||
function _krUpdate(sid, field, val) {
|
||||
const k = _dirKRs.find(x => x.id === sid);
|
||||
if (k) k[field] = val;
|
||||
}
|
||||
|
||||
function _renderDirKRs() {
|
||||
const el = document.getElementById('dir-kr-list');
|
||||
if (!el) return;
|
||||
if (!_dirKRs.length) {
|
||||
el.innerHTML = '<div style="font-size:0.6rem;color:var(--dim)">No key results yet — click + ADD KEY RESULT</div>';
|
||||
return;
|
||||
}
|
||||
el.innerHTML = _dirKRs.map(k => `
|
||||
<div style="display:grid;grid-template-columns:2fr 80px 80px 60px 28px;gap:6px;align-items:center;margin-bottom:6px">
|
||||
<input class="inp" value="${esc(k.title)}" placeholder="Key result title" oninput="_krUpdate(${k.id},'title',this.value)">
|
||||
<input class="inp" type="number" step="0.1" value="${k.current_value}" placeholder="Current" title="Current value" oninput="_krUpdate(${k.id},'current_value',parseFloat(this.value)||0)">
|
||||
<input class="inp" type="number" step="0.1" value="${k.target_value}" placeholder="Target" title="Target value" oninput="_krUpdate(${k.id},'target_value',parseFloat(this.value)||1)">
|
||||
<input class="inp" value="${esc(k.unit)}" placeholder="Unit" title="Unit (%, $, hrs...)" oninput="_krUpdate(${k.id},'unit',this.value)">
|
||||
<button class="btn btn-xs btn-red" onclick="dirRemoveKR(${k.id})">✗</button>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
async function directiveSave() {
|
||||
const id = parseInt(document.getElementById('dir-id')?.value || 0) || null;
|
||||
const title = document.getElementById('dir-title')?.value.trim();
|
||||
const desc = document.getElementById('dir-desc')?.value.trim();
|
||||
const category = document.getElementById('dir-category')?.value;
|
||||
const status = document.getElementById('dir-status')?.value;
|
||||
const priority = parseInt(document.getElementById('dir-priority')?.value || 5);
|
||||
const target_date = document.getElementById('dir-target-date')?.value || '';
|
||||
const stat = document.getElementById('dir-save-status');
|
||||
if (!title) { if (stat) stat.textContent = '✗ Title required'; return; }
|
||||
const key_results = _dirKRs.map(k => ({
|
||||
title: k.title, current_value: parseFloat(k.current_value)||0,
|
||||
target_value: parseFloat(k.target_value)||1, unit: k.unit||'%',
|
||||
})).filter(k => k.title.trim());
|
||||
if (stat) stat.textContent = '◈ SAVING…';
|
||||
const d = await fetch(`admin?action=directive_save${id?'&id='+id:''}`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({title, description:desc, category, status, priority, target_date, key_results}),
|
||||
}).then(r => r.json()).catch(() => ({error: 'request failed'}));
|
||||
if (d.ok) {
|
||||
if (stat) stat.textContent = '◈ SAVED ✓';
|
||||
toast('Directive saved', 'ok');
|
||||
loadDirectives();
|
||||
} else {
|
||||
if (stat) stat.textContent = '✗ ' + (d.error || 'Save failed');
|
||||
toast('Save failed', 'err');
|
||||
}
|
||||
}
|
||||
|
||||
async function directiveDelete() {
|
||||
const id = parseInt(document.getElementById('dir-id')?.value || 0);
|
||||
if (!id || !confirm('Delete this directive and all its key results?')) return;
|
||||
const d = await api('directive_delete', {id});
|
||||
if (d.ok) {
|
||||
toast('Directive deleted', 'ok');
|
||||
document.getElementById('directive-editor').style.display = 'none';
|
||||
loadDirectives();
|
||||
} else toast('Delete failed', 'err');
|
||||
}
|
||||
|
||||
async function directiveReviewAI(id) {
|
||||
toast('◈ Dispatching AI directive review…', 'ok');
|
||||
const payload = id ? {directive_id: id, provider: 'claude'} : {provider: 'claude'};
|
||||
const res = await fetch('admin?action=arc_action', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({action:'job_create', type:'directive_review', payload, priority: 6}),
|
||||
}).then(r => r.json()).catch(() => ({}));
|
||||
if (res.job_id) toast('Review job #' + res.job_id + ' started — results will appear in JARVIS chat', 'ok');
|
||||
else toast('Failed: ' + (res.error||'Arc offline'), 'err');
|
||||
}
|
||||
|
||||
async function directiveReviewSingle(id) { return directiveReviewAI(id); }
|
||||
|
||||
// ── PLANNER ─────────────────────────────────────────────────────────────────
|
||||
const _PRI_COLOR = {urgent:'var(--red)',high:'var(--orange)',normal:'var(--text)',low:'var(--border2)'};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user