feat: add task queue with human-in-the-loop approval + work orders + UI bug fixes

Task Queue system:
- New /tasks page with three-column layout (Pending/Active/Completed)
- Full CRUD API at /api/tasks with approve/veto/modify/pause/cancel/retry
- SQLite persistence in task_queue table
- WebSocket live updates via ws_manager
- Create task modal with agent assignment and priority
- Auto-approve rules for low-risk tasks
- HTMX polling for real-time column updates
- HOME TASK buttons now link to task queue with agent pre-selected
- MARKET HIRE buttons link to task queue with agent pre-selected

Work Order system:
- External submission API for agents/users (POST /work-orders/submit)
- Risk scoring and configurable auto-execution thresholds
- Dashboard at /work-orders/queue with approve/reject/execute flow
- Integration with swarm task system for execution

UI & Dashboard bug fixes:
- EVENTS: add startup event so page is never empty
- LEDGER: fix empty filter params in URL
- MISSION CONTROL: LLM backend and model now read from /health
- MISSION CONTROL: agent count fallback to /swarm/agents
- SWARM: HTMX fallback loads initial data if WebSocket is slow
- MEMORY: add edit/delete buttons for personal facts
- UPGRADES: add empty state guidance with links
- BRIEFING: add regenerate button and POST /briefing/regenerate endpoint

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alexander Payne
2026-02-26 10:27:08 -05:00
parent 4e78f7102e
commit 5f9bbb8435
31 changed files with 3159 additions and 47 deletions

View File

@@ -187,16 +187,24 @@ async function loadHealth() {
try {
const response = await fetch('/health');
const data = await response.json();
// Format uptime
const uptime = data.uptime_seconds;
let uptimeStr;
if (uptime < 60) uptimeStr = Math.floor(uptime) + 's';
else if (uptime < 3600) uptimeStr = Math.floor(uptime / 60) + 'm';
else uptimeStr = Math.floor(uptime / 3600) + 'h ' + Math.floor((uptime % 3600) / 60) + 'm';
document.getElementById('metric-uptime').textContent = uptimeStr;
// LLM backend and model from /health response
if (data.llm_backend) {
document.getElementById('hb-backend').textContent = data.llm_backend;
}
if (data.llm_model) {
document.getElementById('hb-model').textContent = data.llm_model;
}
} catch (error) {
console.error('Failed to load health:', error);
}
@@ -207,11 +215,22 @@ async function loadSwarmStats() {
try {
const response = await fetch('/swarm');
const data = await response.json();
document.getElementById('metric-agents').textContent = data.agents || 0;
document.getElementById('metric-tasks').textContent =
var agentCount = data.agents || 0;
// Fallback: if /swarm returns 0, try /swarm/agents for a direct count
if (agentCount === 0) {
try {
const agentResp = await fetch('/swarm/agents');
const agentData = await agentResp.json();
if (Array.isArray(agentData.agents)) {
agentCount = agentData.agents.length;
}
} catch (e) { /* ignore fallback failure */ }
}
document.getElementById('metric-agents').textContent = agentCount;
document.getElementById('metric-tasks').textContent =
(data.tasks_pending || 0) + (data.tasks_running || 0);
} catch (error) {
console.error('Failed to load swarm stats:', error);
}
@@ -222,16 +241,12 @@ async function loadLightningStats() {
try {
const response = await fetch('/serve/status');
const data = await response.json();
document.getElementById('metric-earned').textContent = data.total_earned_sats || 0;
// Update heartbeat backend
document.getElementById('hb-backend').textContent = data.backend || '-';
document.getElementById('hb-model').textContent = 'llama3.2'; // From config
} catch (error) {
console.error('Failed to load lightning stats:', error);
document.getElementById('metric-earned').textContent = '-';
// /serve may not be running — default to 0 instead of '-'
document.getElementById('metric-earned').textContent = '0';
}
}