Drei vom Architekten abgenommene Tasks, gebündelt als Checkpoint: - TSK-0242: Agent-Alias-Mapping (kimi-ah → kimi kanonisiert, Rollen → preferredAgent), reopenTask räumt claimedBy ab, fsWatch reindiziert direkte Datei-Edits, work-Default 300s → 50s, task_list mit Limit. - TSK-0245: zwei Agent-Klassen (dispatch loop|architect). Watchdog mahnt architekt-getriebene Agenten nur noch EINMAL statt im Minutentakt; `task dispatch` startet sie explizit, `task record` trägt extern erledigte Arbeit mit origin=external nach. - TSK-0249: Lifecycle wird serverseitig erzwungen (open→review scheitert mit klarer Meldung), claimedBy/doneBy überleben bis done, Presence pro Agent, Review-Watchdog, GET /architect/pulse (1.4 kB statt 34 kB, since-Cursor, omitted statt stillem Abschneiden), unbekannter Agent → 400 statt 500. Alle Punkte live am laufenden Hub nachgemessen, nicht aus Agenten-Logs übernommen. Tests: 242 → 279 grün, tsc sauber. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
100 lines
4.3 KiB
TypeScript
100 lines
4.3 KiB
TypeScript
import { throughputSeries, areaPath, dayStart } from './viewmodel.js';
|
|
|
|
/**
|
|
* Sidebar skeleton: budget card + live feed card. The budget card has no tab
|
|
* bar of its own — the Token/Kosten/Verlauf tabs live inside the donut card
|
|
* (rendered client-side by the ported v1 budget JS in v1Budget.ts). The
|
|
* subhead keeps the v1 mount points: [data-budget-mode] (Session/Gesamt),
|
|
* #budgetTotal, #budgetReset; #budgetRows is where the donut card mounts.
|
|
*/
|
|
export function sidebarHtml(): string {
|
|
return `
|
|
<aside class="b2-side">
|
|
<div class="b2-panel b2-glass" id="b2Budget">
|
|
<div class="b2-subhead">
|
|
<h6>Budget</h6>
|
|
<span class="seg" aria-label="Token insight range">
|
|
<button type="button" data-budget-mode="session" class="active">Session</button>
|
|
<button type="button" data-budget-mode="total">Gesamt</button>
|
|
</span>
|
|
<span class="total" id="budgetTotal"></span>
|
|
<button type="button" class="reset-btn" id="budgetReset">Reset</button>
|
|
</div>
|
|
<div id="budgetRows"><div class="budget-empty">no agent activity yet</div></div>
|
|
</div>
|
|
<div class="b2-panel b2-glass" id="b2AgentsPanel">
|
|
<h6>Agents</h6>
|
|
<div class="b2-agents" id="b2Agents"><div class="budget-empty">no agents yet</div></div>
|
|
</div>
|
|
<div class="b2-panel b2-glass">
|
|
<h6>Live</h6>
|
|
<div class="b2-feed" id="b2Feed"></div>
|
|
</div>
|
|
</aside>`;
|
|
}
|
|
|
|
/**
|
|
* Inline script: throughput area chart renderer (used by the Verlauf tab in
|
|
* the donut card) + live feed, followed by the ported v1 budget JS.
|
|
*/
|
|
export function sidebarJs(v1BudgetJs: string): string {
|
|
return `
|
|
var DAY_MS = 24 * 3600 * 1000;
|
|
${dayStart.toString()}
|
|
${throughputSeries.toString()}
|
|
${areaPath.toString()}
|
|
window.__b2RenderThroughput = function (tasks) {
|
|
var box = document.getElementById('b2Throughput');
|
|
var tip = document.getElementById('b2ThroughputTip');
|
|
if (!box) return; // Verlauf tab not mounted right now — nothing to update
|
|
var s = throughputSeries(tasks, 14);
|
|
var p = areaPath(s, 220, 80);
|
|
box.innerHTML = '<svg width="100%" height="80" viewBox="0 0 220 80" preserveAspectRatio="none">'
|
|
+ '<path fill="rgba(56,189,248,.25)" d="' + p.area + '"/>'
|
|
+ '<path class="b2-spark" fill="none" stroke="#38bdf8" stroke-width="1.6" d="' + p.line + '"/></svg>';
|
|
if (tip) {
|
|
var avg = s.reduce(function (a, b) { return a + b; }, 0) / s.length;
|
|
tip.textContent = 'Erledigte Tasks · 14 Tage · Ø ' + avg.toFixed(1) + '/Tag';
|
|
}
|
|
};
|
|
window.__b2FeedPush = function (html) {
|
|
var feed = document.getElementById('b2Feed');
|
|
if (!feed) return;
|
|
var div = document.createElement('div');
|
|
div.innerHTML = html;
|
|
feed.insertBefore(div, feed.firstChild);
|
|
while (feed.children.length > 5) feed.removeChild(feed.lastChild);
|
|
};
|
|
// ── Agent health traffic lights (GET /health) ─────────────────────────────
|
|
// active (green) <2min since last action · busy (blue, on TSK-X) ·
|
|
// idle (gray) · stale/offline (red, >10min with an open assignment).
|
|
window.__b2RenderAgents = function (report) {
|
|
var box = document.getElementById('b2Agents');
|
|
if (!box) return;
|
|
var agents = report && report.agents ? report.agents : [];
|
|
if (!agents.length) { box.innerHTML = '<div class="budget-empty">no agents yet</div>'; return; }
|
|
box.innerHTML = agents.map(function (a) {
|
|
var loop = a.dispatch === 'architect'
|
|
? (a.taskId ? 'architect started' : a.waitingTaskId ? 'wartet auf Architekten-Start' : 'architect dispatch')
|
|
: a.inLoop
|
|
? 'loop ' + compact(a.loopSinceAgoSec || 0)
|
|
: a.loopExitReason
|
|
? 'out ' + compact(a.loopExitAgoSec || 0) + ' · ' + a.loopExitReason
|
|
: 'loop ?';
|
|
var work = a.taskId ? a.taskId : a.waitingTaskId ? a.waitingTaskId + ' waiting' : '';
|
|
var detail = loop + (work ? ' · ' + work : '');
|
|
return '<div class="b2-agent-row" title="' + esc(a.name) + ': ' + esc(detail) + '">' +
|
|
'<span class="b2-light st-' + esc(a.state) + '" aria-hidden="true"></span>' +
|
|
'<span class="b2-agent-name">' + esc(a.name) + '</span>' +
|
|
'<span class="b2-agent-state">' + esc(detail) + '</span></div>';
|
|
}).join('');
|
|
};
|
|
async function refreshAgentHealth() {
|
|
try { window.__b2RenderAgents(await getJSON('/health')); } catch (_) {}
|
|
}
|
|
window.refreshAgentHealth = refreshAgentHealth;
|
|
refreshAgentHealth();
|
|
setInterval(refreshAgentHealth, 5000);
|
|
${v1BudgetJs}`;
|
|
}
|