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:
+89
-1
@@ -948,6 +948,20 @@ body::after{
|
||||
.comms-compose-field{width:100%;background:rgba(0,212,255,0.04);border:1px solid var(--panel-border);border-radius:3px;padding:6px 8px;color:var(--text);font-family:var(--font-mono);font-size:0.6rem;box-sizing:border-box;margin-bottom:7px}
|
||||
.comms-compose-field:focus{outline:none;border-color:var(--cyan)}
|
||||
.comms-compose-actions{display:flex;gap:6px;margin-top:8px}
|
||||
/* ── DIRECTIVES HUD ──────────────────────────────────────────────── */
|
||||
.dir-card{background:rgba(0,212,255,0.03);border:1px solid var(--panel-border);border-radius:var(--r);margin-bottom:7px;overflow:hidden}
|
||||
.dir-card-head{display:flex;align-items:center;gap:8px;padding:8px 10px;cursor:pointer;user-select:none}
|
||||
.dir-card-head:hover{background:rgba(0,212,255,0.06)}
|
||||
.dir-card-title{font-family:var(--font-display);font-size:0.6rem;letter-spacing:1px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.dir-card-body{display:none;padding:0 10px 10px;border-top:1px solid var(--panel-border)}
|
||||
.dir-card.open .dir-card-body{display:block}
|
||||
.dir-progress-bar{height:5px;background:rgba(255,255,255,0.08);border-radius:3px;margin:6px 0}
|
||||
.dir-progress-fill{height:100%;border-radius:3px;transition:width 0.4s ease}
|
||||
.dir-kr-row{display:flex;align-items:center;gap:6px;margin:4px 0;font-family:var(--font-mono);font-size:0.52rem;color:var(--text-dim)}
|
||||
.dir-kr-bar{flex:1;height:3px;background:rgba(255,255,255,0.06);border-radius:2px}
|
||||
.dir-kr-fill{height:100%;border-radius:2px;background:rgba(0,212,255,0.5)}
|
||||
.dir-admin-btn{width:100%;background:rgba(0,212,255,0.06);border:1px solid rgba(0,212,255,0.3);border-radius:4px;padding:5px;color:var(--cyan);font-family:var(--font-display);font-size:0.52rem;letter-spacing:2px;cursor:pointer;margin-bottom:7px}
|
||||
.dir-admin-btn:hover{background:rgba(0,212,255,0.12)}
|
||||
/* ── MISSION OPS HUD ─────────────────────────────────────────────── */
|
||||
.mission-card{background:rgba(0,212,255,0.03);border:1px solid var(--panel-border);border-radius:var(--r);margin-bottom:7px;overflow:hidden}
|
||||
.mission-card-head{display:flex;align-items:center;gap:8px;padding:8px 10px;cursor:pointer;user-select:none}
|
||||
@@ -1193,6 +1207,7 @@ body::after{
|
||||
<div class="tab" id="tab-btn-comms" onclick="switchTab('comms')">COMMS</div>
|
||||
<div class="tab" id="tab-btn-guardian" onclick="switchTab('guardian')">GUARDIAN</div>
|
||||
<div class="tab" id="tab-btn-missions" onclick="switchTab('missions')">MISSIONS</div>
|
||||
<div class="tab" id="tab-btn-directives" onclick="switchTab('directives')">DIRECTIVES</div>
|
||||
</div>
|
||||
<div id="tab-vms" class="tab-pane" style="overflow-y:auto;flex:1">
|
||||
<div id="vm-list"><div class="loading-shimmer"></div></div>
|
||||
@@ -1223,6 +1238,9 @@ body::after{
|
||||
<div id="tab-missions" class="tab-pane" style="overflow-y:auto;flex:1;padding:4px 0">
|
||||
<div id="missions-hud"><div class="loading-shimmer"></div></div>
|
||||
</div>
|
||||
<div id="tab-directives" class="tab-pane" style="overflow-y:auto;flex:1;padding:4px 0">
|
||||
<div id="directives-hud"><div class="loading-shimmer"></div></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -3143,7 +3161,8 @@ function switchTab(name) {
|
||||
if (name === 'intel') loadIntel();
|
||||
if (name === 'comms') { loadComms(); loadCommsOutbox(); }
|
||||
if (name === 'guardian') loadGuardian();
|
||||
if (name === 'missions') loadMissionsHud();
|
||||
if (name === 'missions') loadMissionsHud();
|
||||
if (name === 'directives') loadDirectivesHud();
|
||||
if (name === 'alerts') loadAlerts();
|
||||
}
|
||||
|
||||
@@ -4272,6 +4291,75 @@ async function hudRunMission(id) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── DIRECTIVES HUD ────────────────────────────────────────────────────────────
|
||||
let _dirOpenCards = new Set();
|
||||
|
||||
async function loadDirectivesHud() {
|
||||
const el = document.getElementById('directives-hud');
|
||||
if (!el) return;
|
||||
try {
|
||||
const d = await api('directives/list?status=active');
|
||||
const list = (d.directives || []);
|
||||
|
||||
let html = '<button class="dir-admin-btn" onclick="window.open(\'/admin#directives\',\'_blank\')">◈ MANAGE IN ADMIN</button>';
|
||||
|
||||
if (!list.length) {
|
||||
html += '<div class="comms-empty">◈ NO ACTIVE DIRECTIVES<br><span style="opacity:0.5">Create objectives in Admin → Directives</span></div>';
|
||||
el.innerHTML = html;
|
||||
return;
|
||||
}
|
||||
|
||||
const catColors = {work:'var(--cyan)',personal:'#a78bfa',health:'#00ff88',finance:'#ffd700',home:'var(--panel-border)',other:'var(--text-dim)'};
|
||||
for (const dir of list) {
|
||||
const pct = Math.min(100, Math.round(dir.progress || 0));
|
||||
const isOpen = _dirOpenCards.has(dir.id);
|
||||
const color = catColors[dir.category] || 'var(--cyan)';
|
||||
const fillColor = pct >= 80 ? '#00ff88' : pct >= 40 ? '#ffd700' : '#ff6644';
|
||||
const daysLeft = dir.target_date
|
||||
? Math.ceil((new Date(dir.target_date) - new Date()) / 86400000) : null;
|
||||
const dueTxt = daysLeft !== null
|
||||
? (daysLeft < 0 ? `OVERDUE ${Math.abs(daysLeft)}d` : `${daysLeft}d left`)
|
||||
: '';
|
||||
const dueColor = daysLeft !== null && daysLeft < 0 ? '#ff2244' : daysLeft < 14 ? '#ffd700' : 'var(--text-dim)';
|
||||
|
||||
html += `<div class="dir-card${isOpen?' open':''}" id="dir-card-${dir.id}">
|
||||
<div class="dir-card-head" onclick="toggleDirCard(${dir.id})">
|
||||
<span style="font-family:var(--font-mono);font-size:0.55rem;color:${color};flex-shrink:0">${dir.category.toUpperCase()}</span>
|
||||
<span class="dir-card-title" style="color:${color}">${escHtml(dir.title)}</span>
|
||||
<span style="font-family:var(--font-mono);font-size:0.55rem;color:${fillColor};flex-shrink:0">${pct}%</span>
|
||||
${dueTxt ? `<span style="font-family:var(--font-mono);font-size:0.48rem;color:${dueColor};flex-shrink:0">${dueTxt}</span>` : ''}
|
||||
</div>
|
||||
<div class="dir-card-body">
|
||||
<div class="dir-progress-bar"><div class="dir-progress-fill" style="width:${pct}%;background:${fillColor}"></div></div>
|
||||
<div style="font-family:var(--font-mono);font-size:0.5rem;color:var(--text-dim);margin-bottom:6px">${dir.kr_count||0} KEY RESULTS · ${dir.link_count||0} LINKED ITEMS</div>
|
||||
<button onclick="hudDirectiveReview(${dir.id})" style="background:rgba(0,212,255,0.06);border:1px solid rgba(0,212,255,0.2);border-radius:3px;padding:3px 8px;color:var(--cyan);font-family:var(--font-display);font-size:0.48rem;letter-spacing:1px;cursor:pointer">◈ AI REVIEW</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
el.innerHTML = html;
|
||||
} catch(e) {
|
||||
if (el) el.innerHTML = '<div class="comms-empty">DIRECTIVES OFFLINE</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDirCard(id) {
|
||||
const card = document.getElementById('dir-card-' + id);
|
||||
if (!card) return;
|
||||
if (_dirOpenCards.has(id)) _dirOpenCards.delete(id);
|
||||
else _dirOpenCards.add(id);
|
||||
card.classList.toggle('open');
|
||||
}
|
||||
|
||||
async function hudDirectiveReview(id) {
|
||||
const res = await api('arc?action=job_create', 'POST', {
|
||||
type: 'directive_review', payload: {directive_id: id, provider: 'claude'}, priority: 6,
|
||||
});
|
||||
if (res.job_id) {
|
||||
addMessage('jarvis', `◈ DIRECTIVE REVIEW initiated (Job #${res.job_id}). Analyzing objectives and key results now. Results will appear here shortly.`);
|
||||
speak(`Directive review underway. I'll brief you on your progress in a moment.`);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAgents() {
|
||||
const [listData, metricsData] = await Promise.all([
|
||||
api('agent/list'),
|
||||
|
||||
Reference in New Issue
Block a user