From 8a2270e0a6d0cc68773c5ffb6874eb4b49e9d3ad Mon Sep 17 00:00:00 2001 From: Saravanakumar D Date: Sat, 30 May 2026 19:27:35 -0700 Subject: [PATCH] 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> --- agent-queue/dashboard.mjs | 63 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/agent-queue/dashboard.mjs b/agent-queue/dashboard.mjs index 40499f1..c5ea0fc 100644 --- a/agent-queue/dashboard.mjs +++ b/agent-queue/dashboard.mjs @@ -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 || '?'));