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

@@ -105,9 +105,13 @@
<div class="mc-facts-list">
{% if facts %}
<ul class="mc-fact-list">
<ul class="mc-fact-list" style="list-style: none; padding: 0;">
{% for fact in facts %}
<li class="memory-fact">{{ fact }}</li>
<li class="memory-fact" id="fact-{{ fact.id }}" style="display:flex; align-items:center; gap:8px; padding:6px 0; border-bottom:1px solid rgba(255,255,255,0.08);">
<span class="fact-content" style="flex:1;">{{ fact.content }}</span>
<button class="mc-btn mc-btn-small" onclick="editFact('{{ fact.id }}', this)" style="font-size:0.7rem; padding:2px 8px;">EDIT</button>
<button class="mc-btn mc-btn-small" onclick="deleteFact('{{ fact.id }}')" style="font-size:0.7rem; padding:2px 8px; color:#ef4444;">DEL</button>
</li>
{% endfor %}
</ul>
{% else %}
@@ -116,4 +120,40 @@
</div>
</div>
</div>
<script>
function deleteFact(id) {
if (!confirm('Delete this fact?')) return;
fetch('/memory/fact/' + id, { method: 'DELETE' })
.then(function(r) { if (r.ok) document.getElementById('fact-' + id).remove(); });
}
function editFact(id, btn) {
var li = document.getElementById('fact-' + id);
var span = li.querySelector('.fact-content');
var current = span.textContent.trim();
var input = document.createElement('input');
input.type = 'text'; input.value = current;
input.className = 'mc-input'; input.style.flex = '1';
span.replaceWith(input);
btn.textContent = 'SAVE';
btn.onclick = function() {
var val = input.value.trim();
if (!val) return;
fetch('/memory/fact/' + id, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content: val })
}).then(function(r) {
if (r.ok) {
var newSpan = document.createElement('span');
newSpan.className = 'fact-content'; newSpan.style.flex = '1';
newSpan.textContent = val;
input.replaceWith(newSpan);
btn.textContent = 'EDIT';
btn.onclick = function() { editFact(id, btn); };
}
});
};
}
</script>
{% endblock %}