feat(dashboard): surface manifest tags (priority/profile/caps/tracker) on the board
Render a per-job tags line on the RUNNING workers and JOBS lists showing the routing inputs operators care about: priority, profile, capabilities, and the tracker-item reference. Tags come from the launched meta, falling back to the job's .md frontmatter for never-launched inbox jobs (new readManifest parser). The tracker-item becomes a clickable terminal hyperlink when AQ_TRACKER_WEB is set. Also renders the new budget_exceeded result as a failed RECENT row. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
75653b7c6e
commit
8a2270e0a6
@ -16,6 +16,8 @@
|
||||
//
|
||||
// Usage: node dashboard.mjs [--interval 2] [--root /path/to/queue]
|
||||
// AGENT_QUEUE_ROOT=/path node dashboard.mjs
|
||||
// AQ_TRACKER_WEB=https://tracker.example.com node dashboard.mjs
|
||||
// (makes job tracker-item tags clickable terminal hyperlinks)
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
@ -84,6 +86,34 @@ const insightsTag = (m) => {
|
||||
return parts.join(' ');
|
||||
};
|
||||
|
||||
// Manifest tags (read-only): the routing inputs an operator cares about when
|
||||
// scanning the board — priority, profile, capabilities, and a tracker-item
|
||||
// reference. Rendered from a job's meta (launched jobs) or, for never-launched
|
||||
// inbox jobs, parsed from the .md frontmatter (see readManifest). The
|
||||
// tracker-item becomes a real terminal hyperlink when AQ_TRACKER_WEB is set.
|
||||
const TRACKER_WEB = (process.env.AQ_TRACKER_WEB || '').replace(/\/+$/, '');
|
||||
const osc8 = (url, label) => `\x1b]8;;${url}\x07${label}\x1b]8;;\x07`;
|
||||
const trackerTag = (id) => {
|
||||
if (!id) return '';
|
||||
const label = `⎘ ${id}`;
|
||||
return TRACKER_WEB ? osc8(`${TRACKER_WEB}/${encodeURIComponent(id)}`, label) : label;
|
||||
};
|
||||
const PRIORITY_COLOR = { critical: 'red', high: 'yellow', medium: 'gray', low: 'gray' };
|
||||
const manifestTags = (m) => {
|
||||
if (!m) return '';
|
||||
const parts = [];
|
||||
if (m.priority && m.priority !== 'medium') {
|
||||
parts.push(c(PRIORITY_COLOR[m.priority] || 'gray', `⚑${m.priority}`));
|
||||
}
|
||||
if (m.profile) parts.push(c('blue', `◆${m.profile}`));
|
||||
if (m.capabilities) {
|
||||
const caps = String(m.capabilities).replace(/^\[|\]$/g, '').trim();
|
||||
if (caps) parts.push(c('gray', `caps ${trunc(caps, 36)}`));
|
||||
}
|
||||
if (m.tracker_item) parts.push(c('cyan', trackerTag(m.tracker_item)));
|
||||
return parts.join(' ');
|
||||
};
|
||||
|
||||
const pidAlive = (pid) => {
|
||||
if (!pid) return false;
|
||||
try { process.kill(Number(pid), 0); return true; } catch { return false; }
|
||||
@ -124,6 +154,31 @@ const readMetas = () => {
|
||||
return files.map((f) => parseMeta(path.join(DIRS.state, f)));
|
||||
};
|
||||
|
||||
// readManifest(stage, job) — manifest tags for a job that has no launched meta
|
||||
// yet (e.g. queued in inbox/). Parses the leading --- frontmatter block of the
|
||||
// job's .md and maps the few fields manifestTags renders. Never throws.
|
||||
const FM_TAG_KEYS = {
|
||||
priority: 'priority', profile: 'profile',
|
||||
capabilities: 'capabilities', 'tracker-item': 'tracker_item',
|
||||
};
|
||||
const readManifest = (stage, job) => {
|
||||
const out = {};
|
||||
try {
|
||||
const lines = fs.readFileSync(path.join(DIRS[stage], `${job}.md`), 'utf8').split('\n');
|
||||
if ((lines[0] || '').trim() !== '---') return out;
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
if (lines[i].trim() === '---') break;
|
||||
const line = lines[i].replace(/^\s+/, '');
|
||||
const ci = line.indexOf(':');
|
||||
if (ci <= 0) continue;
|
||||
const key = line.slice(0, ci).trim();
|
||||
if (!FM_TAG_KEYS[key]) continue;
|
||||
out[FM_TAG_KEYS[key]] = line.slice(ci + 1).trim().replace(/^["']|["']$/g, '');
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
return out;
|
||||
};
|
||||
|
||||
// ── agent-queue.sh control (single source of truth) ─────────────────
|
||||
const AQ = path.join(__dirname, 'agent-queue.sh');
|
||||
const stripAnsi = (s) => (s || '').replace(/\x1b\[[0-9;]*m/g, '');
|
||||
@ -235,6 +290,7 @@ const ENGINE_COLOR = { devin: 'cyan', claude: 'yellow', codex: 'green' };
|
||||
|
||||
function drawBoard() {
|
||||
const metas = readMetas();
|
||||
const metaByJob = Object.fromEntries(metas.filter((m) => m.job).map((m) => [m.job, m]));
|
||||
const running = metas.filter((m) => !m.ended && pidAlive(m.pid));
|
||||
const finished = metas
|
||||
.filter((m) => m.ended)
|
||||
@ -289,6 +345,8 @@ function drawBoard() {
|
||||
`${stalled ? ' ' + c('red', '⚠ stalled') : ''}`
|
||||
);
|
||||
out.push(` ${c('dim', trunc(shortPath(m.cwd || ''), 70))}`);
|
||||
const mtags = manifestTags(m);
|
||||
if (mtags) out.push(` ${mtags}`);
|
||||
const last = lastLogLine(m.job);
|
||||
if (last) out.push(` ${c('cyan', '› ')}${c('dim', trunc(last, 70))}`);
|
||||
}
|
||||
@ -307,6 +365,8 @@ function drawBoard() {
|
||||
const tag = (STAGE_TAG[it.stage] || (() => `[${it.stage}]`))();
|
||||
const name = sel ? `${C.bold}${trunc(it.job, 46)}${C.reset}` : trunc(it.job, 46);
|
||||
out.push(` ${ptr} ${num} ${tag} ${name}`);
|
||||
const jtags = manifestTags(metaByJob[it.job] || readManifest(it.stage, it.job));
|
||||
if (jtags) out.push(` ${jtags}`);
|
||||
});
|
||||
}
|
||||
out.push('');
|
||||
@ -321,7 +381,7 @@ function drawBoard() {
|
||||
const res = m.result || '';
|
||||
const failedRes = res === 'failed' || res === 'timeout' || res === 'verify_failed' ||
|
||||
res === 'rejected' || res === 'retries_exhausted' || res === 'capability_mismatch' ||
|
||||
res === 'no_engine';
|
||||
res === 'budget_exceeded' || res === 'no_engine';
|
||||
const mark = failedRes ? c('red', '✕') : c('green', '▣');
|
||||
const when = m.ended ? new Date(Number(m.ended) * 1000).toLocaleTimeString() : '';
|
||||
let label;
|
||||
@ -330,6 +390,7 @@ function drawBoard() {
|
||||
else if (res === 'review') label = c('cyan', 'review');
|
||||
else if (res === 'verify_failed') label = c('red', 'verify failed');
|
||||
else if (res === 'timeout') label = c('red', 'timeout');
|
||||
else if (res === 'budget_exceeded') label = c('red', 'budget exceeded');
|
||||
else if (res === 'rejected') label = c('red', 'rejected');
|
||||
else if (res === 'retries_exhausted') label = c('red', 'retries exhausted');
|
||||
else if (res === 'failed') label = c('red', 'failed rc=' + (m.exit || '?'));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user