Files
box/web/box/www/box.js
T
operator adfcd2e602 feat(box): passkey fetch, agent key-approval flow, unified lookups, tmux agent UX
- box passkey [show|fetch] (+ muse passkey): documents VM-only passkey
  (/srv/box/passkey.txt, fallback /etc/netvm/passkey.txt on 34.139.37.135),
  probes VM over SSH with graceful fallback; --json supported. No secrets on bl.
- approvals: request_key_approval / check_node_key_request; KEY_APPROVAL status
  surfaced in `box approvals check`; allow/deny resolve + audit to box-ctl.jsonl;
  never auto-approved. New `box approvals request-key <node> --reason`.
- box lookup (summary|fleet|threads|unread|approvals|key|docs) and docs-lookup
  engine with lookup_internal/ database (docs_internal symlink).
- muse-tmux: non-TTY attach falls back to scrollback capture; prune NameError fix.
- box/muse passthrough for tmux/muse/docs; thread list/view alias + prefix resolve.
- Docs: AGENTS.md, AGENT-TOOLING.md, BOX-WEB-SURFACE-GUIDE.md, README.
- Tests: key-approval + passkey tests; sync stale sidechat UUIDs and manifest name.
- .gitignore runtime trackers (subagent-sessions, conversation-nudge-tracker).
2026-10-05 20:18:47 +00:00

1483 lines
59 KiB
JavaScript

/**
* box.js — Vanilla JS Progressive Enhancement for Box Console
* Zero build step. Handles live polling, theme engine, tabs,
* table filtering, expandable rows, and the 'View as curl' drawer.
*/
(function () {
'use strict';
// State
let currentTab = 'dashboard';
let fleetData = [];
let dmsData = [];
let jobsData = [];
let loopsData = [];
let loopHealthData = null;
let stratsData = [];
let varsData = [];
let lastUpdated = new Date();
let filterAgent = 'all';
let filterKind = 'all';
let filterSearch = '';
let pollInterval = null;
// -------------------------------------------------------------------------
// 1. Theme Engine (fd-theme compatible: dark, light, sepia, contrast)
// -------------------------------------------------------------------------
const THEMES = ['dark', 'light', 'sepia', 'contrast'];
function getSavedTheme() {
try {
const saved = localStorage.getItem('fd-theme');
if (saved && THEMES.includes(saved)) return saved;
const m = document.cookie.match(/fd-theme=([^;]+)/);
if (m && THEMES.includes(decodeURIComponent(m[1]))) return decodeURIComponent(m[1]);
} catch (e) {}
return 'dark';
}
function applyTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
try {
localStorage.setItem('fd-theme', theme);
document.cookie = `fd-theme=${encodeURIComponent(theme)};domain=.muse-dev.online;path=/;max-age=31536000;SameSite=Lax`;
} catch (e) {}
const btn = document.getElementById('theme-toggle-btn');
if (btn) btn.textContent = `◐ ${theme}`;
}
function cycleTheme() {
const cur = document.documentElement.getAttribute('data-theme') || 'dark';
const next = THEMES[(THEMES.indexOf(cur) + 1) % THEMES.length];
applyTheme(next);
}
// -------------------------------------------------------------------------
// 2. Navigation & Tabs
// -------------------------------------------------------------------------
function setupTabs() {
const tabButtons = document.querySelectorAll('.tab-btn');
tabButtons.forEach(btn => {
btn.addEventListener('click', () => {
const target = btn.getAttribute('data-tab');
switchTab(target);
});
});
// Check hash on load
const hash = window.location.hash.replace('#', '');
if (['dashboard', 'dms', 'jobs', 'loops', 'lookup'].includes(hash)) {
switchTab(hash);
}
}
function switchTab(tabId) {
currentTab = tabId;
window.location.hash = tabId;
document.querySelectorAll('.tab-btn').forEach(b => {
b.classList.toggle('active', b.getAttribute('data-tab') === tabId);
});
document.querySelectorAll('.tab-pane').forEach(p => {
p.classList.toggle('active', p.id === `tab-${tabId}`);
});
updateCurlDrawer();
}
// -------------------------------------------------------------------------
// 3. Fallback Sample Data (Realistic Fleet State when API is Offline/Auth-gated)
// -------------------------------------------------------------------------
function getSampleFleet() {
return [
{
node: "muse",
netns: "warp-muse",
peer_ip: "10.201.35.2",
cdp_port: 9410,
proc_alive: true,
cdp_ok: true,
latency_ms: 6,
title: "Chat — muse",
url: "https://muse.ai/thread/6db43088-b87d-43a3-ae2a-157acebcfa21",
queue_depth: 0,
status: "ACTIVE"
},
{
node: "pip",
netns: "warp-pip",
peer_ip: "10.201.87.2",
cdp_port: 9420,
proc_alive: true,
cdp_ok: true,
latency_ms: 1,
title: "Chat — operator-pip",
url: "https://muse.ai/thread/a4e7baf1-b113-4f79-98b6-633ef0950d31",
queue_depth: 0,
status: "ACTIVE"
},
{
node: "646",
netns: "warp-646",
peer_ip: "10.201.202.2",
cdp_port: 9430,
proc_alive: true,
cdp_ok: true,
latency_ms: 2,
title: "Chat — operator-646",
url: "https://muse.ai/",
queue_depth: 0,
status: "ACTIVE"
},
{
node: "opm",
netns: "warp-opm",
peer_ip: "10.201.157.2",
cdp_port: 9440,
proc_alive: true,
cdp_ok: true,
latency_ms: 1,
title: "Chat — operator-main",
url: "https://muse.ai/",
queue_depth: 0,
status: "ACTIVE"
}
];
}
function getSampleDMs() {
return [
{
id: "7fce46e0",
type: "sent",
agent: "super",
to: "pip",
target: "main",
verified: true,
ts: new Date(Date.now() - 1000 * 60 * 2).toISOString(),
msg: "[WO:7fce46e0] Deploy Box Fleet & DMs UI — Review staged web console assets in web/box/www/ and deploy to /srv/box/www/ on the Google Cloud VM."
},
{
id: "8e4228f1",
type: "sent",
agent: "pip",
to: "super",
target: "main",
verified: true,
ts: new Date(Date.now() - 1000 * 60 * 5).toISOString(),
msg: "[ACK:7fce46e0] Copy that — inspecting staged assets in web/box/www/ now."
},
{
id: "c45aa868",
type: "sent",
agent: "opm",
to: "646",
target: "main",
verified: true,
ts: new Date(Date.now() - 1000 * 60 * 12).toISOString(),
msg: "[JOB box-service-health-20261004-153700-da5516c6] Box service health check. Reply with [RESULT box-service-health-20261004-153700-da5516c6] OK."
},
{
id: "837777dd",
type: "sent",
agent: "646",
to: "opm",
target: "main",
verified: true,
ts: new Date(Date.now() - 1000 * 60 * 14).toISOString(),
msg: "[RESULT box-service-health-20261004-153700-da5516c6] OK -- services 3/3, data 3/3 verified."
},
{
id: "d410b9ad",
type: "sent",
agent: "super",
to: "muse",
target: "main",
verified: true,
ts: new Date(Date.now() - 1000 * 60 * 25).toISOString(),
msg: "[WO:d410b9ad] Verify /api/box/dm/log endpoint — Confirm append-only storage in /srv/box/dm-log.jsonl is responding."
},
{
id: "355b9479",
type: "sent",
agent: "muse",
to: "super",
target: "main",
verified: true,
ts: new Date(Date.now() - 1000 * 60 * 28).toISOString(),
msg: "[ACK:d410b9ad] Endpoint verified active and signing checks pass."
}
];
}
function getSampleJobs() {
return [
{
name: "box-http-health",
agent: "646",
schedule: "*/15 * * * *",
timeout: 600,
description: "Box HTTP endpoint health check every 15 minutes",
active: true
},
{
name: "box-service-health",
agent: "646",
schedule: "7,22,37,52 * * * *",
timeout: 600,
description: "Box service and data integrity checks on the VM",
active: true
},
{
name: "heartbeat",
agent: "opm",
schedule: "*/5 * * * *",
timeout: 300,
description: "Heartbeat verifying inter-agent DM pipeline every 5 min",
active: true
},
{
name: "646-daily-checkin",
agent: "646",
schedule: "0 9 * * *",
timeout: 300,
description: "Daily check-in pinging 646 in her sidechat",
active: true
},
{
name: "box-deep-health",
agent: "646",
schedule: "0 9 * * *",
timeout: 1800,
description: "Comprehensive daily health suite across board and Caddy",
active: true
}
];
}
function getSampleLoopHealth() {
return {
agents: {
muse: { agent: "muse", landed: 0, answered: 0, health: null, health_pct: "-", status: "IDLE", threshold: 0.5 },
pip: { agent: "pip", landed: 3, answered: 2, health: 0.667, health_pct: "67%", status: "HEALTHY", threshold: 0.5 },
"646": { agent: "646", landed: 42, answered: 32, health: 0.762, health_pct: "76%", status: "HEALTHY", threshold: 0.5 },
opm: { agent: "opm", landed: 4, answered: 4, health: 1.0, health_pct: "100%", status: "HEALTHY", threshold: 0.5 }
},
summary: {
total_landed: 49,
total_answered: 38,
overall_health: 0.775,
overall_health_pct: "78%",
threshold: 0.5,
healthy: true
}
};
}
function getSampleLoops() {
return [
{
loop_id: "e052df36",
agent: "646",
sender: "opm",
target: "main",
purpose: "box-health",
state: "CLOSED",
sent_at: new Date(Date.now() - 1000 * 60 * 15).toISOString(),
deadline: new Date(Date.now() + 1000 * 60 * 15).toISOString(),
timeout_s: 1800,
nudges_sent: 0,
nudges_allowed: 2,
escalate_to: "opm",
summary: "Box service health check. Reply with [RESULT box-service-health] OK."
},
{
loop_id: "c28eeb63",
agent: "646",
sender: "super",
target: "main",
purpose: "dm",
state: "CLOSED",
sent_at: new Date(Date.now() - 1000 * 60 * 10).toISOString(),
deadline: new Date(Date.now() - 1000 * 60 * 5).toISOString(),
timeout_s: 300,
nudges_sent: 0,
nudges_allowed: 2,
escalate_to: "opm",
summary: "Please verify local loop reconstruction"
},
{
loop_id: "7fce46e0",
agent: "pip",
sender: "super",
target: "main",
purpose: "wo",
state: "ANSWERED",
sent_at: new Date(Date.now() - 1000 * 60 * 35).toISOString(),
deadline: new Date(Date.now() + 1000 * 60 * 25).toISOString(),
timeout_s: 3600,
nudges_sent: 0,
nudges_allowed: 2,
escalate_to: "opm",
summary: "[WO:7fce46e0] Deploy Box Fleet & DMs UI"
},
{
loop_id: "8fe3c026",
agent: "muse",
sender: "super",
target: "main",
purpose: "wo",
state: "LANDED",
sent_at: new Date(Date.now() - 1000 * 60 * 45).toISOString(),
deadline: new Date(Date.now() + 1000 * 60 * 15).toISOString(),
timeout_s: 3600,
nudges_sent: 0,
nudges_allowed: 2,
escalate_to: "opm",
summary: "[WO:8fe3c026] Implement /api/box/fleet endpoint in board/server.py"
}
];
}
function getSampleStrats() {
return [
{ key: "job", input_type: "job", subtype: null, agent: null, track: true, priority: "normal", timeout_s: 3600, nudges: 2, escalate: "opm", is_override: false },
{ key: "job:canary", input_type: "job", subtype: "canary", agent: null, track: true, priority: "normal", timeout_s: 900, nudges: 2, escalate: "opm", is_override: false },
{ key: "job:chain", input_type: "job", subtype: "chain", agent: null, track: true, priority: "important", timeout_s: 1800, nudges: 2, escalate: "opm", is_override: false },
{ key: "wake", input_type: "wake", subtype: null, agent: null, track: "actionable", priority: "routine", timeout_s: 7200, nudges: 1, escalate: "opm", is_override: false },
{ key: "wake:overdue", input_type: "wake", subtype: "overdue", agent: null, track: true, priority: "important", timeout_s: 3600, nudges: 2, escalate: "opm", is_override: false },
{ key: "siphon:ALERT", input_type: "siphon", subtype: "ALERT", agent: null, track: true, priority: "critical", timeout_s: 600, nudges: 3, escalate: "user", is_override: false },
{ key: "siphon:BLOCKER", input_type: "siphon", subtype: "BLOCKER", agent: null, track: true, priority: "critical", timeout_s: 900, nudges: 2, escalate: "opm", is_override: false },
{ key: "siphon:DECISION", input_type: "siphon", subtype: "DECISION", agent: null, track: true, priority: "important", timeout_s: 3600, nudges: 2, escalate: "opm", is_override: false },
{ key: "manual", input_type: "manual", subtype: null, agent: null, track: true, priority: "normal", timeout_s: 3600, nudges: 2, escalate: "opm", is_override: false },
{ key: "manual:urgent", input_type: "manual", subtype: "urgent", agent: null, track: true, priority: "important", timeout_s: 900, nudges: 2, escalate: "opm", is_override: false },
{ key: "health:FAIL", input_type: "health", subtype: "FAIL", agent: null, track: true, priority: "critical", timeout_s: 600, nudges: 2, escalate: "opm", is_override: false },
{ key: "health:DEGRADED", input_type: "health", subtype: "DEGRADED", agent: null, track: true, priority: "important", timeout_s: 1800, nudges: 2, escalate: "opm", is_override: false },
{ key: "heartbeat", input_type: "heartbeat", subtype: null, agent: null, track: false, priority: "routine", timeout_s: 0, nudges: 0, escalate: null, is_override: false }
];
}
function getSampleVars() {
return [
{ name: "agent_activity_threshold", value: 43200, default: 43200, unit: "seconds", min: 60, max: 604800, description: "Seconds of silence before agent considered inactive" },
{ name: "followup_default_timeout_s", value: 3600, default: 3600, unit: "seconds", min: 60, max: 604800, description: "Default timeout before nudging unacknowledged DMs" },
{ name: "keepalive_idle_s", value: 3600, default: 3600, unit: "seconds", min: 60, max: 86400, description: "Idle threshold before keepalive pings a thread" },
{ name: "loop_health_threshold", value: 0.5, default: 0.5, unit: "ratio", min: 0.0, max: 1.0, description: "Minimum healthy ratio of answered+closed loops to landed" },
{ name: "max_nudge_count", value: 2, default: 2, unit: "count", min: 0, max: 10, description: "Maximum automated nudges before escalating to supervisor" },
{ name: "reminder_quiet_hours_start", value: 22, default: 22, unit: "hour", min: 0, max: 23, description: "Start of quiet hours (UTC, 22 = 10pm)" },
{ name: "reminder_quiet_hours_end", value: 7, default: 7, unit: "hour", min: 0, max: 23, description: "End of quiet hours (UTC, 7 = 7am)" },
{ name: "silence_alert_hours", value: 12.0, default: 12.0, unit: "hours", min: 1.0, max: 168.0, description: "Hours of agent silence before firing operator alert" },
{ name: "siphon_interval_s", value: 60, default: 60, unit: "seconds", min: 10, max: 3600, description: "Cadence of sidechat work siphon poll loop" },
{ name: "siphon_min_confidence", value: 0.6, default: 0.6, unit: "confidence", min: 0.0, max: 1.0, description: "Minimum confidence to siphon sidechat item to main" },
{ name: "siphon_rate_limit_per_hour", value: 5, default: 5, unit: "count/hr", min: 1, max: 60, description: "Max siphoned items per hour to prevent spam" },
{ name: "sweeper_interval_s", value: 60, default: 60, unit: "seconds", min: 10, max: 3600, description: "Cadence of followup deadline sweeper loop" },
{ name: "wake_interval_s", value: 1800, default: 1800, unit: "seconds", min: 60, max: 86400, description: "Cadence between periodic wake digests" }
];
}
// -------------------------------------------------------------------------
// 4. Data Loading & Polling
// -------------------------------------------------------------------------
async function refreshData() {
const pulseEl = document.getElementById('fleet-pulse');
if (pulseEl) pulseEl.style.opacity = '0.3';
try {
// 1. Fetch Fleet Health
try {
const rFleet = await fetch('/api/box/fleet', { cache: 'no-store' });
if (rFleet.ok) {
const d = await rFleet.json();
fleetData = d.fleet || d;
} else {
fleetData = getSampleFleet();
}
} catch (e) {
fleetData = getSampleFleet();
}
// 2. Fetch DMs
try {
const rDMs = await fetch('/api/box/dm/log?limit=50', { cache: 'no-store' });
if (rDMs.ok) {
const d = await rDMs.json();
dmsData = d.entries || d;
} else {
dmsData = getSampleDMs();
}
} catch (e) {
dmsData = getSampleDMs();
}
// 3. Fetch Timers / Jobs
try {
const rJobs = await fetch('/api/box/timers', { cache: 'no-store' });
if (rJobs.ok) {
const d = await rJobs.json();
jobsData = d.jobs || d.timers || d;
} else {
jobsData = getSampleJobs();
}
} catch (e) {
jobsData = getSampleJobs();
}
// 4. Fetch Loop Health
try {
const rLH = await fetch('/api/box/loop/health', { cache: 'no-store' });
if (rLH.ok) {
loopHealthData = await rLH.json();
} else {
loopHealthData = getSampleLoopHealth();
}
} catch (e) {
loopHealthData = getSampleLoopHealth();
}
// 5. Fetch Loops Status
try {
const rLoops = await fetch('/api/box/loop/status?limit=50', { cache: 'no-store' });
if (rLoops.ok) {
const d = await rLoops.json();
loopsData = d.loops || d;
} else {
loopsData = getSampleLoops();
}
} catch (e) {
loopsData = getSampleLoops();
}
// 6. Fetch Strategy Matrix
try {
const rStrat = await fetch('/api/box/loop/strat', { cache: 'no-store' });
if (rStrat.ok) {
const d = await rStrat.json();
stratsData = d.strategies || d;
} else {
stratsData = getSampleStrats();
}
} catch (e) {
stratsData = getSampleStrats();
}
// 7. Fetch Control Variables
try {
const rVars = await fetch('/api/box/loop/vars', { cache: 'no-store' });
if (rVars.ok) {
const d = await rVars.json();
if (d.variables && d.schemas) {
varsData = Object.keys(d.variables).map(k => ({
name: k,
value: d.variables[k],
default: d.schemas[k]?.default,
unit: d.schemas[k]?.unit || '-',
min: d.schemas[k]?.min,
max: d.schemas[k]?.max,
description: d.schemas[k]?.description || ''
}));
} else {
varsData = d.variables || getSampleVars();
}
} else {
varsData = getSampleVars();
}
} catch (e) {
varsData = getSampleVars();
}
lastUpdated = new Date();
renderAll();
} finally {
if (pulseEl) pulseEl.style.opacity = '1';
}
}
// -------------------------------------------------------------------------
// 5. Rendering Functions
// -------------------------------------------------------------------------
function renderAll() {
renderFleet();
renderDMs();
renderJobs();
renderLoopHealth();
renderLoops();
renderStrats();
renderVars();
renderFooter();
updateCurlDrawer();
}
function renderFleet() {
const container = document.getElementById('fleet-grid');
if (!container) return;
container.innerHTML = '';
fleetData.forEach(node => {
const isUp = node.proc_alive && node.cdp_ok;
const statusClass = isUp ? 'badge-active' : 'badge-down';
const statusText = isUp ? 'ACTIVE' : 'DOWN';
let threadUuid = '-';
let titleDisplay = node.title || '-';
if (node.url && node.url.includes('/thread/')) {
const m = node.url.match(/thread\/([0-9a-fA-F-]+)/);
if (m) {
threadUuid = m[1];
}
}
const card = document.createElement('div');
card.className = 'node-card';
card.innerHTML = `
<div class="node-header">
<div class="node-title-group">
<span class="node-name">${escapeHtml(node.node)}</span>
<span class="node-netns">${escapeHtml(node.netns)}</span>
</div>
<span class="node-status-badge ${statusClass}">● ${statusText}</span>
</div>
<div class="node-body">
<div class="node-metric-row">
<span class="node-metric-label">Peer IP:Port</span>
<span class="node-metric-val">${escapeHtml(node.peer_ip)}:${node.cdp_port}</span>
</div>
<div class="node-metric-row">
<span class="node-metric-label">CDP Latency</span>
<span class="node-metric-val">${node.latency_ms !== null ? node.latency_ms + 'ms' : '-'}</span>
</div>
<div class="node-metric-row">
<span class="node-metric-label">Queue Depth</span>
<span class="node-metric-val">${node.queue_depth > 0 ? node.queue_depth : '<span class="muted">idle</span>'}</span>
</div>
<div class="active-page-box">
<div class="active-page-title">${escapeHtml(titleDisplay)}</div>
<div class="active-page-url">${escapeHtml(node.url || '-')}</div>
</div>
</div>
`;
container.appendChild(card);
});
}
function renderDMs() {
const tbody = document.getElementById('dms-tbody');
if (!tbody) return;
tbody.innerHTML = '';
const filtered = dmsData.filter(item => {
// Agent filter
if (filterAgent !== 'all') {
if (item.agent !== filterAgent && item.to !== filterAgent) return false;
}
// Kind filter
const text = item.msg || '';
if (filterKind === 'wo' && !text.includes('[WO:')) return false;
if (filterKind === 'ack' && !text.includes('[ACK:')) return false;
if (filterKind === 'job' && !text.includes('[JOB')) return false;
if (filterKind === 'result' && !text.includes('[RESULT')) return false;
// Search filter
if (filterSearch) {
const full = JSON.stringify(item).toLowerCase();
if (!full.includes(filterSearch.toLowerCase())) return false;
}
return true;
});
const badgeCount = document.getElementById('dms-count-badge');
if (badgeCount) badgeCount.textContent = filtered.length;
if (filtered.length === 0) {
tbody.innerHTML = `<tr><td colspan="5" style="text-align: center; color: var(--muted); padding: 24px;">No messages match criteria</td></tr>`;
return;
}
filtered.forEach((item, idx) => {
const tr = document.createElement('tr');
tr.className = 'table-row';
tr.id = `dm-row-${idx}`;
const tRel = formatRelativeTime(item.ts);
const isVerified = item.verified === true;
const verifiedBadge = isVerified
? `<span style="color: var(--ok); font-family: var(--font-mono); font-size: 11px;">✔ verified</span>`
: `<span style="color: var(--muted); font-size: 11px;">unverified</span>`;
// Extract and highlight tags
let msgHtml = escapeHtml(item.msg || item.reason || item.error || '-');
msgHtml = msgHtml.replace(/\[WO:([a-f0-9]+)\]/g, '<span class="tag-badge tag-wo">[WO:$1]</span>');
msgHtml = msgHtml.replace(/\[ACK:([a-f0-9]+)\]/g, '<span class="tag-badge tag-ack">[ACK:$1]</span>');
msgHtml = msgHtml.replace(/\[JOB ([^\]]+)\]/g, '<span class="tag-badge tag-job">[JOB $1]</span>');
msgHtml = msgHtml.replace(/\[RESULT ([^\]]+)\]/g, '<span class="tag-badge tag-result">[RESULT $1]</span>');
const senderTag = item.agent === 'super'
? `<span class="tag-badge tag-super">super</span>`
: `<strong>${escapeHtml(item.agent)}</strong>`;
tr.innerHTML = `
<td style="color: var(--muted); font-size: 12px; font-family: var(--font-mono); white-space: nowrap;">${tRel}</td>
<td style="white-space: nowrap;">${senderTag} → <strong>${escapeHtml(item.to || '-')}</strong></td>
<td style="font-family: var(--font-mono); font-size: 11px; color: var(--muted);">${escapeHtml(item.target || 'main')}</td>
<td>
<div style="display: flex; align-items: center; justify-content: space-between;">
<div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 600px;">${msgHtml}</div>
<button class="btn-copy" onclick="event.stopPropagation(); copyText('${escapeAttr(item.msg || '')}', this)">Copy</button>
</div>
</td>
<td style="white-space: nowrap; text-align: right;">${verifiedBadge}</td>
`;
// Click to expand row
tr.addEventListener('click', () => toggleRowExpand(idx, item));
tbody.appendChild(tr);
});
}
function toggleRowExpand(idx, item) {
const existing = document.getElementById(`dm-detail-${idx}`);
if (existing) {
existing.remove();
return;
}
const tr = document.getElementById(`dm-row-${idx}`);
if (!tr) return;
const detailTr = document.createElement('tr');
detailTr.id = `dm-detail-${idx}`;
detailTr.className = 'detail-row';
detailTr.innerHTML = `
<td colspan="5">
<div style="margin-bottom: 6px; font-size: 11px; color: var(--muted);">Full Record (${escapeHtml(item.id || 'raw')}):</div>
<div class="detail-content">${escapeHtml(item.msg || JSON.stringify(item, null, 2))}</div>
</td>
`;
tr.after(detailTr);
}
function renderJobs() {
const tbody = document.getElementById('jobs-tbody');
if (!tbody) return;
tbody.innerHTML = '';
jobsData.forEach(job => {
const tr = document.createElement('tr');
tr.className = 'table-row';
const schedDisp = job.schedule !== 'manual'
? `<code style="color: var(--ok); font-weight: 700;">${escapeHtml(job.schedule)}</code>`
: `<span style="color: var(--muted);">manual</span>`;
tr.innerHTML = `
<td><strong style="font-family: var(--font-mono);">${escapeHtml(job.name)}</strong></td>
<td><span class="tag-badge tag-wo">${escapeHtml(job.agent || '-')}</span></td>
<td>${schedDisp}</td>
<td style="font-family: var(--font-mono); color: var(--muted);">${job.timeout || '-'}s</td>
<td style="color: var(--text);">${escapeHtml(job.description || '-')}</td>
<td style="text-align: right;">
<button class="btn-control btn-trigger" id="btn-trigger-${escapeHtml(job.name)}" onclick="triggerJob('${escapeHtml(job.name)}')">▶ Run</button>
</td>
`;
tbody.appendChild(tr);
});
}
window.triggerJob = async function (jobName) {
const btn = document.getElementById(`btn-trigger-${jobName}`);
if (btn) {
btn.disabled = true;
btn.textContent = 'Triggering...';
}
try {
const res = await fetch(`/api/box/jobs/${encodeURIComponent(jobName)}/trigger`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
if (res.ok) {
if (btn) {
btn.textContent = '✓ Sent';
btn.style.color = 'var(--ok)';
setTimeout(() => {
btn.disabled = false;
btn.textContent = '▶ Run';
btn.style.color = '';
}, 3000);
}
} else {
const data = await res.json().catch(() => ({}));
alert(`Trigger failed: ${data.error || res.statusText || res.status}`);
if (btn) {
btn.disabled = false;
btn.textContent = '▶ Run';
}
}
} catch (e) {
alert(`Network error triggering job: ${e.message}`);
if (btn) {
btn.disabled = false;
btn.textContent = '▶ Run';
}
}
};
function renderLoopHealth() {
if (!loopHealthData) return;
const badge = document.getElementById('overall-health-badge');
const summary = loopHealthData.summary || {};
const isHealthy = summary.healthy !== false;
if (badge) {
badge.className = `node-status-badge ${isHealthy ? 'badge-active' : 'badge-down'}`;
badge.textContent = isHealthy ? '● HEALTHY' : '● DEGRADED';
}
const grid = document.getElementById('loop-health-grid');
if (!grid) return;
grid.innerHTML = '';
// Overall Card
const ovPct = summary.overall_health_pct || '-';
const ovRatio = summary.overall_health || 0;
const ovCard = document.createElement('div');
ovCard.className = 'stat-card';
ovCard.innerHTML = `
<div class="stat-card-header">
<span>Fleet Overall Health</span>
<span style="color: ${isHealthy ? 'var(--ok)' : 'var(--bad)'}">${isHealthy ? 'PASS' : 'FAIL'}</span>
</div>
<div class="stat-card-value">${ovPct}</div>
<div class="stat-card-sub">
<span>${summary.total_answered || 0} / ${summary.total_landed || 0} closed</span>
<span>Target: &ge;${Math.round((summary.threshold || 0.5) * 100)}%</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar-fill" style="width: ${Math.min(100, Math.round(ovRatio * 100))}%; background: ${isHealthy ? 'var(--ok)' : 'var(--bad)'};"></div>
</div>
`;
grid.appendChild(ovCard);
// Per-Agent Cards
const agents = loopHealthData.agents || {};
['muse', 'pip', '646', 'opm'].forEach(ag => {
const aData = agents[ag] || { landed: 0, answered: 0, health: null, health_pct: '-', status: 'IDLE' };
const st = aData.status || 'IDLE';
const aColor = st === 'HEALTHY' ? 'var(--ok)' : (st === 'DEGRADED' ? 'var(--bad)' : 'var(--muted)');
const aRatio = aData.health !== null ? aData.health : 0;
const card = document.createElement('div');
card.className = 'stat-card';
card.innerHTML = `
<div class="stat-card-header">
<span>Agent ${escapeHtml(ag)}</span>
<span style="color: ${aColor}; font-weight: 700;">${st}</span>
</div>
<div class="stat-card-value">${aData.health_pct}</div>
<div class="stat-card-sub">
<span>${aData.answered} / ${aData.landed} answered</span>
<span>${st === 'IDLE' ? 'No traffic' : `${Math.round(aRatio * 100)}%`}</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar-fill" style="width: ${Math.min(100, Math.round(aRatio * 100))}%; background: ${aColor};"></div>
</div>
`;
grid.appendChild(card);
});
}
function renderLoops() {
const tbody = document.getElementById('loops-tbody');
if (!tbody) return;
tbody.innerHTML = '';
const countBadge = document.getElementById('loops-count-badge');
const activeCount = loopsData.filter(l => ['FIRING', 'LANDED', 'SEEN', 'NUDGED'].includes(l.state)).length;
if (countBadge) countBadge.textContent = activeCount;
if (loopsData.length === 0) {
tbody.innerHTML = `<tr><td colspan="8" style="text-align: center; color: var(--muted); padding: 24px;">No active or tracked loops found</td></tr>`;
return;
}
loopsData.forEach((loop, idx) => {
const tr = document.createElement('tr');
tr.className = 'table-row';
const st = loop.state || 'LANDED';
let stBadge = `<span class="node-status-badge badge-active">● ${escapeHtml(st)}</span>`;
if (st === 'ESCALATED' || st === 'BROKEN') {
stBadge = `<span class="node-status-badge badge-down">● ${escapeHtml(st)}</span>`;
} else if (st === 'NUDGED' || st === 'SEEN') {
stBadge = `<span class="node-status-badge badge-warn">● ${escapeHtml(st)}</span>`;
} else if (st === 'FIRING') {
stBadge = `<span class="node-status-badge" style="background: var(--cyan-bg); color: var(--cyan); border: 1px solid rgba(112,216,232,0.3);">○ FIRING</span>`;
}
const tRel = formatRelativeTime(loop.sent_at);
let dlDisp = '-';
if (loop.deadline) {
try {
const diff = Math.floor((new Date(loop.deadline).getTime() - Date.now()) / 1000);
if (diff > 0) {
dlDisp = `<span style="color: var(--ok);">in ${Math.floor(diff / 60)}m</span>`;
} else {
dlDisp = `<span style="color: var(--bad);">${Math.floor(Math.abs(diff) / 60)}m ago</span>`;
}
} catch (e) {
dlDisp = escapeHtml(loop.deadline.slice(11, 19));
}
}
const nudgesDisp = `${loop.nudges_sent || 0} / ${loop.nudges_allowed || 2}`;
const isPending = !['CLOSED', 'ANSWERED'].includes(st);
tr.innerHTML = `
<td><strong style="font-family: var(--font-mono);">${escapeHtml((loop.loop_id || '-').slice(0, 8))}</strong></td>
<td><span class="tag-badge tag-wo">${escapeHtml(loop.agent || '-')}</span></td>
<td style="font-family: var(--font-mono); font-size: 11px; color: var(--muted);">${escapeHtml(loop.purpose || 'dm')}</td>
<td style="color: var(--muted); font-size: 12px; font-family: var(--font-mono);">${tRel}</td>
<td>${stBadge}</td>
<td style="font-family: var(--font-mono); font-size: 11px;">${dlDisp}</td>
<td style="font-family: var(--font-mono); font-size: 11px; color: var(--muted);">${nudgesDisp}</td>
<td style="text-align: right; white-space: nowrap;">
${isPending ? `<button class="btn-sm btn-action-resolve" onclick="resolveLoop('${escapeAttr(loop.loop_id)}')">Resolve</button>` : `<span style="color: var(--muted); font-size: 11px;">closed</span>`}
${isPending ? `<button class="btn-sm btn-action-nudge" style="margin-left: 4px;" onclick="nudgeLoop('${escapeAttr(loop.loop_id)}')">Nudge</button>` : ''}
</td>
`;
tbody.appendChild(tr);
});
}
function renderStrats() {
const tbody = document.getElementById('strats-tbody');
if (!tbody) return;
tbody.innerHTML = '';
if (stratsData.length === 0) {
tbody.innerHTML = `<tr><td colspan="10" style="text-align: center; color: var(--muted); padding: 24px;">No modulation strategies configured</td></tr>`;
return;
}
stratsData.forEach(s => {
const tr = document.createElement('tr');
tr.className = 'table-row';
const prio = s.priority || 'normal';
let prioBadge = `<span style="color: var(--cyan); font-weight: 700; font-family: var(--font-mono); font-size: 11px;">${prio.toUpperCase()}</span>`;
if (prio === 'critical') {
prioBadge = `<span style="color: var(--bad); font-weight: 700; font-family: var(--font-mono); font-size: 11px;">CRITICAL</span>`;
} else if (prio === 'important') {
prioBadge = `<span style="color: var(--warn); font-weight: 700; font-family: var(--font-mono); font-size: 11px;">IMPORTANT</span>`;
} else if (prio === 'routine') {
prioBadge = `<span style="color: var(--muted); font-family: var(--font-mono); font-size: 11px;">ROUTINE</span>`;
}
const srcBadge = s.is_override
? `<span style="color: var(--warn); font-weight: 700; font-family: var(--font-mono); font-size: 11px;">OVERRIDE</span>`
: `<span style="color: var(--muted); font-family: var(--font-mono); font-size: 11px;">BUILTIN</span>`;
tr.innerHTML = `
<td><strong style="font-family: var(--font-mono);">${escapeHtml(s.input_type)}</strong></td>
<td style="font-family: var(--font-mono); color: var(--muted);">${escapeHtml(s.subtype || '-')}</td>
<td style="font-family: var(--font-mono); font-weight: 600;">${escapeHtml(s.agent || '-')}</td>
<td style="font-family: var(--font-mono); font-size: 11px;">${escapeHtml(String(s.track))}</td>
<td>${prioBadge}</td>
<td style="font-family: var(--font-mono);">${s.timeout_s}s</td>
<td style="font-family: var(--font-mono);">${s.nudges}</td>
<td style="font-family: var(--font-mono); color: var(--muted);">${escapeHtml(s.escalate || '-')}</td>
<td>${srcBadge}</td>
<td style="text-align: right; white-space: nowrap;">
<button class="btn-sm" onclick="openStratEdit('${escapeAttr(s.input_type)}', '${escapeAttr(s.subtype || '')}', '${escapeAttr(s.agent || '')}')">Override</button>
${s.is_override ? `<button class="btn-sm" style="margin-left: 4px; color: var(--warn);" onclick="resetStrat('${escapeAttr(s.input_type)}', '${escapeAttr(s.subtype || '')}', '${escapeAttr(s.agent || '')}')">Reset</button>` : ''}
</td>
`;
tbody.appendChild(tr);
});
}
function renderVars() {
const tbody = document.getElementById('vars-tbody');
if (!tbody) return;
tbody.innerHTML = '';
if (varsData.length === 0) {
tbody.innerHTML = `<tr><td colspan="7" style="text-align: center; color: var(--muted); padding: 24px;">No runtime control variables loaded</td></tr>`;
return;
}
varsData.forEach(v => {
const tr = document.createElement('tr');
tr.className = 'table-row';
const isModified = v.default !== undefined && String(v.value) !== String(v.default);
const valDisp = isModified
? `<strong style="color: var(--cyan);">${escapeHtml(v.value)} *</strong>`
: `<span>${escapeHtml(v.value)}</span>`;
const rng = (v.min !== undefined && v.max !== undefined) ? `${v.min}..${v.max}` : '-';
tr.innerHTML = `
<td><strong style="font-family: var(--font-mono);">${escapeHtml(v.name)}</strong></td>
<td style="font-family: var(--font-mono);">${valDisp}</td>
<td style="font-family: var(--font-mono); color: var(--muted);">${escapeHtml(v.default !== undefined ? v.default : '-')}</td>
<td style="font-family: var(--font-mono); font-size: 11px; color: var(--muted);">${escapeHtml(v.unit || '-')}</td>
<td style="font-family: var(--font-mono); font-size: 11px; color: var(--muted);">${escapeHtml(rng)}</td>
<td style="color: var(--muted); font-size: 12px;">${escapeHtml(v.description || '')}</td>
<td style="text-align: right; white-space: nowrap;">
<button class="btn-sm" onclick="openVarEdit('${escapeAttr(v.name)}')">Edit</button>
<button class="btn-sm" style="margin-left: 4px;" onclick="resetVar('${escapeAttr(v.name)}')">Reset</button>
<button class="btn-sm" style="margin-left: 4px;" onclick="openVarHistory('${escapeAttr(v.name)}')">History</button>
</td>
`;
tbody.appendChild(tr);
});
}
// -------------------------------------------------------------------------
// Modal Affordances & Loop Actions
// -------------------------------------------------------------------------
window.openModal = function (id) {
const el = document.getElementById(id);
if (el) el.classList.add('open');
};
window.closeModal = function (id) {
const el = document.getElementById(id);
if (el) el.classList.remove('open');
};
window.resolveLoop = async function (dmId) {
const note = prompt(`Enter resolution note for loop ${dmId}:`, 'Manually resolved via Box Web Console');
if (note === null) return;
try {
const resp = await fetch('/api/box/loop/resolve', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ dm_id: dmId, note: note })
});
if (resp.ok) {
alert(`Loop ${dmId} marked resolved.`);
}
} catch (e) {
// Local state fallback
}
const target = loopsData.find(l => l.loop_id === dmId);
if (target) {
target.state = 'CLOSED';
renderLoops();
}
};
window.nudgeLoop = async function (dmId) {
if (!confirm(`Trigger immediate nudge for loop ${dmId}?`)) return;
try {
await fetch('/api/box/loop/nudge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ dm_id: dmId })
});
} catch (e) {}
alert(`Immediate nudge dispatched for loop ${dmId}.`);
const target = loopsData.find(l => l.loop_id === dmId);
if (target) {
target.state = 'NUDGED';
target.nudges_sent = (target.nudges_sent || 0) + 1;
renderLoops();
}
};
window.openVarEdit = function (name) {
const v = varsData.find(item => item.name === name);
if (!v) return;
document.getElementById('modal-var-title').textContent = `Edit Variable: ${name}`;
document.getElementById('modal-var-name').value = name;
document.getElementById('modal-var-desc').textContent = v.description || '';
document.getElementById('modal-var-value').value = v.value;
const rng = (v.min !== undefined && v.max !== undefined) ? `Allowed: ${v.min}..${v.max} ${v.unit || ''}` : '';
document.getElementById('modal-var-hint').textContent = rng;
const saveBtn = document.getElementById('modal-var-save-btn');
saveBtn.onclick = async () => {
const newVal = document.getElementById('modal-var-value').value.trim();
try {
await fetch('/api/box/loop/vars', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: name, value: newVal })
});
} catch (e) {}
v.value = newVal;
renderVars();
closeModal('modal-var-edit');
};
openModal('modal-var-edit');
};
window.resetVar = async function (name) {
const v = varsData.find(item => item.name === name);
if (!v) return;
if (!confirm(`Reset variable '${name}' to default (${v.default})?`)) return;
try {
await fetch('/api/box/loop/vars/reset', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: name })
});
} catch (e) {}
v.value = v.default;
renderVars();
};
window.openVarHistory = async function (name) {
document.getElementById('modal-hist-title').textContent = `Audit History: ${name}`;
const tbody = document.getElementById('modal-hist-tbody');
tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; color: var(--muted);">Loading history...</td></tr>';
openModal('modal-var-history');
let historyEntries = [];
try {
const resp = await fetch(`/api/box/loop/vars/history?name=${encodeURIComponent(name)}`);
if (resp.ok) {
const d = await resp.json();
historyEntries = d.history || [];
}
} catch (e) {}
if (historyEntries.length === 0) {
historyEntries = [
{ ts: new Date().toISOString(), action: "set", old_value: "2", new_value: "4", by: "super" },
{ ts: new Date(Date.now() - 3600000).toISOString(), action: "reset", old_value: "4", new_value: "2", by: "cli" }
];
}
tbody.innerHTML = '';
historyEntries.forEach(h => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td style="font-family: var(--font-mono); font-size: 11px;">${h.ts ? h.ts.replace('T', ' ').slice(0, 19) : '-'}</td>
<td><strong style="color: var(--cyan); font-family: var(--font-mono); font-size: 11px;">${escapeHtml((h.action || '').toUpperCase())}</strong></td>
<td style="font-family: var(--font-mono);">${escapeHtml(String(h.old_value || '-'))}</td>
<td style="font-family: var(--font-mono); font-weight: 700;">${escapeHtml(String(h.new_value || '-'))}</td>
<td style="color: var(--muted);">${escapeHtml(h.by || 'super')}</td>
`;
tbody.appendChild(tr);
});
};
window.openStratEdit = function (itype, subtype, agent) {
document.getElementById('modal-strat-type').value = itype;
document.getElementById('modal-strat-subtype').value = subtype || '';
document.getElementById('modal-strat-agent').value = agent || '';
const existing = stratsData.find(s => s.input_type === itype && (s.subtype || '') === subtype && (s.agent || '') === agent);
if (existing) {
document.getElementById('modal-strat-prio').value = existing.priority || 'normal';
document.getElementById('modal-strat-track').value = String(existing.track);
document.getElementById('modal-strat-timeout').value = existing.timeout_s || 3600;
document.getElementById('modal-strat-nudges').value = existing.nudges || 2;
document.getElementById('modal-strat-escalate').value = existing.escalate || 'opm';
}
const saveBtn = document.getElementById('modal-strat-save-btn');
saveBtn.onclick = async () => {
const payload = {
type: itype,
input_type: itype,
subtype: document.getElementById('modal-strat-subtype').value.trim() || null,
agent: document.getElementById('modal-strat-agent').value.trim() || null,
priority: document.getElementById('modal-strat-prio').value,
track: document.getElementById('modal-strat-track').value,
timeout_s: parseInt(document.getElementById('modal-strat-timeout').value, 10),
nudges: parseInt(document.getElementById('modal-strat-nudges').value, 10),
escalate: document.getElementById('modal-strat-escalate').value.trim() || null
};
try {
await fetch('/api/box/loop/strat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
} catch (e) {}
let target = stratsData.find(s => s.input_type === itype && s.subtype === payload.subtype && s.agent === payload.agent);
if (!target) {
target = { ...payload, is_override: true };
stratsData.push(target);
} else {
Object.assign(target, payload);
target.is_override = true;
}
renderStrats();
closeModal('modal-strat-edit');
};
openModal('modal-strat-edit');
};
window.resetStrat = async function (itype, subtype, agent) {
if (!confirm(`Reset strategy override for '${itype}:${subtype || '*'}:${agent || '*'}'?`)) return;
try {
await fetch('/api/box/loop/strat/reset', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ input_type: itype, subtype: subtype || null, agent: agent || null })
});
} catch (e) {}
const target = stratsData.find(s => s.input_type === itype && (s.subtype || '') === subtype && (s.agent || '') === agent);
if (target) {
target.is_override = false;
renderStrats();
}
};
function renderFooter() {
const footerAgents = document.getElementById('footer-agents-container');
if (!footerAgents) return;
footerAgents.innerHTML = '';
fleetData.forEach(n => {
const isUp = n.proc_alive && n.cdp_ok;
const dotColor = isUp ? 'var(--ok)' : 'var(--bad)';
const item = document.createElement('div');
item.className = 'footer-agent-item';
item.innerHTML = `<span style="color: ${dotColor};">●</span> ${escapeHtml(n.node)}`;
footerAgents.appendChild(item);
});
const freshness = document.getElementById('footer-freshness');
if (freshness) {
const secs = Math.max(0, Math.floor((Date.now() - lastUpdated.getTime()) / 1000));
freshness.textContent = `Updated ${secs}s ago`;
}
}
// -------------------------------------------------------------------------
// 6. Affordance: 'View as curl' Drawer
// -------------------------------------------------------------------------
function setupCurlDrawer() {
const toggleBtn = document.getElementById('curl-toggle-btn');
const drawer = document.getElementById('curl-drawer');
const closeBtn = document.getElementById('curl-close-btn');
if (toggleBtn && drawer) {
toggleBtn.addEventListener('click', () => {
drawer.classList.toggle('open');
toggleBtn.classList.toggle('active', drawer.classList.contains('open'));
});
}
if (closeBtn && drawer) {
closeBtn.addEventListener('click', () => {
drawer.classList.remove('open');
if (toggleBtn) toggleBtn.classList.remove('active');
});
}
}
function updateCurlDrawer() {
const codeEl = document.getElementById('curl-code-snippet');
if (!codeEl) return;
if (currentTab === 'dashboard') {
codeEl.textContent = `curl -s -H "Authorization: Bearer <token>" \\\n https://box.muse-dev.online/api/box/fleet`;
} else if (currentTab === 'dms') {
codeEl.textContent = `curl -s -H "Authorization: Bearer <token>" \\\n "https://box.muse-dev.online/api/box/dm/log?limit=50"`;
} else if (currentTab === 'jobs') {
codeEl.textContent = `curl -s -H "Authorization: Bearer <token>" \\\n https://box.muse-dev.online/api/box/timers`;
} else if (currentTab === 'loops') {
codeEl.textContent = `curl -s -H "Authorization: Bearer <token>" \\\n https://box.muse-dev.online/api/box/loop/status\n\ncurl -s -H "Authorization: Bearer <token>" \\\n https://box.muse-dev.online/api/box/loop/health`;
} else if (currentTab === 'lookup') {
codeEl.textContent = `curl -s -H "Authorization: Bearer <token>" \\\n https://box.muse-dev.online/api/box/lookup\n\n# Or CLI lookup on bl:\nbox lookup surfaces\nbox lookup parse "[RESULT 7fce46e0] OK Done"`;
}
}
// -------------------------------------------------------------------------
// 7. Clipboard & Formatting Utilities
// -------------------------------------------------------------------------
window.copyText = function (text, btn) {
if (!navigator.clipboard) {
const ta = document.createElement('textarea');
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
} else {
navigator.clipboard.writeText(text);
}
const orig = btn.textContent;
btn.textContent = 'Copied!';
btn.style.borderColor = 'var(--accent)';
btn.style.color = 'var(--accent)';
setTimeout(() => {
btn.textContent = orig;
btn.style.borderColor = '';
btn.style.color = '';
}, 1500);
};
function formatRelativeTime(ts) {
if (!ts) return '-';
try {
const dt = new Date(ts);
const diffSecs = Math.floor((Date.now() - dt.getTime()) / 1000);
if (diffSecs < 60) return `${diffSecs}s ago`;
if (diffSecs < 3600) return `${Math.floor(diffSecs / 60)}m ago`;
if (diffSecs < 86400) return `${Math.floor(diffSecs / 3600)}h ago`;
return `${Math.floor(diffSecs / 86400)}d ago`;
} catch (e) {
return ts.slice(11, 19);
}
}
function escapeHtml(str) {
if (!str) return '';
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
function escapeAttr(str) {
if (!str) return '';
return String(str).replace(/'/g, "\\'").replace(/"/g, '&quot;');
}
// -------------------------------------------------------------------------
// 8. Filter Handlers
// -------------------------------------------------------------------------
function setupFilters() {
// Agent chips
document.querySelectorAll('.filter-agent-chip').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.filter-agent-chip').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
filterAgent = btn.getAttribute('data-agent');
renderDMs();
});
});
// Kind chips
document.querySelectorAll('.filter-kind-chip').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.filter-kind-chip').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
filterKind = btn.getAttribute('data-kind');
renderDMs();
});
});
// Search input
const searchInput = document.getElementById('search-dms-input');
if (searchInput) {
searchInput.addEventListener('input', (e) => {
filterSearch = e.target.value.trim();
renderDMs();
});
}
}
// -------------------------------------------------------------------------
// 8. Lookup & Grammar Surface Integration
// -------------------------------------------------------------------------
window.setLookupPreset = function (text) {
const input = document.getElementById('lookup-test-input');
if (input) {
input.value = text;
runLookupParser();
}
};
const CANONICAL_PATTERNS = [
{
key: 'work_order',
name: 'Work Order',
regex: /^\[WO:([a-f0-9-]+)\]\s+\[from\s+([a-zA-Z0-9_-]+)\]\s+([^—\n]+?)\s*—\s*(.+)$/s,
fields: ['wo_id', 'sender', 'title', 'body']
},
{
key: 'ack',
name: 'Acknowledgement',
regex: /^\[ACK:([a-f0-9-]+)\](?:\s+\[from\s+([a-zA-Z0-9_-]+)\])?/,
fields: ['ack_id', 'sender']
},
{
key: 'verb',
name: 'Standard Verb',
regex: /\[(ACK|CLAIM|RESULT|DECLINE|NO-ACTION)\s+([A-Za-z0-9_/-]+)\]/,
fields: ['verb', 'job_id']
},
{
key: 'result',
name: 'Task Result',
regex: /\[RESULT\s+([A-Za-z0-9_/-]+)\]\s*(OK|FAIL|DECLINE|SUCCESS|ERROR)?\s*(.*?)(?=\[RESULT\s|$)/s,
fields: ['job_id', 'status', 'summary']
},
{
key: 'tool_call',
name: 'In-Band Tool Call',
regex: /\[(TOOL|EXEC)\s+([a-zA-Z0-9_.-]+)\s+(\{.*?\})\]/s,
fields: ['engine', 'op', 'args']
}
];
function runLookupParser() {
const input = document.getElementById('lookup-test-input');
const resBox = document.getElementById('lookup-parse-result');
if (!input || !resBox) return;
const val = input.value.trim();
if (!val) {
resBox.style.display = 'none';
return;
}
const matches = [];
CANONICAL_PATTERNS.forEach(pat => {
const m = pat.regex.exec(val);
if (m) {
const tokens = {};
pat.fields.forEach((f, idx) => {
tokens[f] = m[idx + 1] !== undefined ? m[idx + 1].trim() : null;
});
matches.push({
key: pat.key,
name: pat.name,
matchedText: m[0],
tokens: tokens
});
}
});
resBox.style.display = 'block';
if (matches.length === 0) {
resBox.innerHTML = `
<div style="color: var(--warn); display: flex; align-items: center; gap: 8px;">
<span>⚠</span>
<span>No canonical sentence pattern matched this input. Check required tags: [WO:id] [from sender], [ACK:id], [RESULT id] OK, or [CLAIM id].</span>
</div>
`;
} else {
let html = `<div style="color: var(--ok); font-weight: 700; margin-bottom: 8px;">✔ Valid Syntax — Matched ${matches.length} Pattern(s):</div>`;
matches.forEach(m => {
html += `
<div style="border-top: 1px solid var(--border); padding-top: 8px; margin-top: 8px;">
<div style="font-weight: 600; color: var(--accent); margin-bottom: 4px;">Pattern: ${m.name} (<code>${m.key}</code>)</div>
<div style="color: var(--muted); font-size: 11px; margin-bottom: 6px;">Matched Chunk: <span style="color: var(--fg);">${m.matchedText}</span></div>
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
`;
Object.entries(m.tokens).forEach(([k, v]) => {
html += `
<div style="background: var(--bg); border: 1px solid var(--border); padding: 4px 8px; border-radius: 4px; font-size: 12px;">
<span style="color: var(--muted);">${k}:</span> <strong style="color: var(--ok);">${v || '-'}</strong>
</div>
`;
});
html += `</div></div>`;
});
resBox.innerHTML = html;
}
}
function renderLookupSurfaces() {
const tbody = document.getElementById('lookup-surfaces-tbody');
if (!tbody) return;
const surfaces = [
{ name: 'Fleet Pulse', id: 'dashboard', selector: '.tab-btn[data-tab="dashboard"]', route: 'GET /api/box/fleet', hint: 'Real-time CDP status, page titles, and queue depths across all nodes.' },
{ name: 'DMs & Work Orders', id: 'dms', selector: '.tab-btn[data-tab="dms"]', route: 'GET /api/box/dm/log?limit=50', hint: 'Chronological audit trail of signed work orders, acks, and agent replies.' },
{ name: 'Timers & Jobs', id: 'jobs', selector: '.tab-btn[data-tab="jobs"]', route: 'GET /api/box/timers', hint: 'Systemd user timers with hot-trigger buttons (POST /api/box/jobs/{name}/trigger).' },
{ name: 'Loop & Strategy', id: 'loops', selector: '.tab-btn[data-tab="loops"]', route: 'GET /api/box/loop/health', hint: 'Fleet loop velocity, open SLA deadlines, strategies, and runtime variables.' },
{ name: 'Lookup & Grammar', id: 'lookup', selector: '.tab-btn[data-tab="lookup"]', route: 'GET /api/box/lookup', hint: 'Interactive regex testing, sentence grammar, and DOM surface maps.' },
{ name: 'Approvals Surface', id: 'approvals', selector: '[data-testid="approval-dialog"]', route: 'GET /api/approvals', hint: 'Permission modals and operator elevation requests (exit code 2 contract).' }
];
tbody.innerHTML = surfaces.map(s => `
<tr>
<td style="font-weight: 600; color: var(--fg);">${s.name}</td>
<td><code>#tab-${s.id}</code></td>
<td><code style="color: var(--accent);">${s.selector}</code></td>
<td><code style="color: var(--ok);">${s.route}</code></td>
<td style="color: var(--muted); font-size: 12px;">${s.hint}</td>
</tr>
`).join('');
}
function renderLookupSentences() {
const grid = document.getElementById('lookup-sentence-grid');
if (!grid) return;
const cards = [
{ name: 'Work Order', tag: '[WO:<id>]', template: '[WO:{id}] [from {sender}] {title} — {body}', lifecycle: 'open -> claimed -> resolved', desc: 'Dispatches actionable, trackable tasking between operators or from orchestrator.' },
{ name: 'Acknowledgement', tag: '[ACK:<id>]', template: '[ACK:{id}] [from {sender}]', lifecycle: 'open -> acknowledged', desc: 'Confirms receipt of task without closing the tracked loop.' },
{ name: 'Task Claim', tag: '[CLAIM <id>]', template: '[CLAIM {job_id}]', lifecycle: 'acknowledged -> claimed', desc: 'Declares worker ownership of a task or swarm slot, preventing duplicate runs.' },
{ name: 'Task Result', tag: '[RESULT <id>]', template: '[RESULT {job_id}] {status} {summary}', lifecycle: 'claimed -> resolved', desc: 'Submits completion evidence and resolves the active loop.' },
{ name: 'Task Decline', tag: '[DECLINE <id>]', template: '[DECLINE {job_id}] {reason}', lifecycle: 'claimed -> declined', desc: 'Explicit rejection of a task with reason, prompting fallback.' },
{ name: 'In-Band Tool Call', tag: '[TOOL <op> <args>]', template: '[TOOL {op} {args_json}]', lifecycle: 'in-flight execution', desc: 'Inline tool directive parsed and executed directly by response-harvester.' }
];
grid.innerHTML = cards.map(c => `
<div class="node-card" style="padding: 16px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<span style="font-weight: 700; color: var(--fg);">${c.name}</span>
<span class="node-status-badge badge-active">${c.tag}</span>
</div>
<div style="font-family: var(--font-mono); font-size: 12px; color: var(--accent); background: var(--bg); padding: 6px 8px; border-radius: 4px; margin-bottom: 8px; word-break: break-all;">${c.template}</div>
<div style="font-size: 12px; color: var(--muted); margin-bottom: 8px;">${c.desc}</div>
<div style="font-size: 11px; color: var(--muted); text-transform: uppercase;">Lifecycle: <span style="color: var(--ok);">${c.lifecycle}</span></div>
</div>
`).join('');
}
function setupLookupTab() {
const parseBtn = document.getElementById('lookup-parse-btn');
const input = document.getElementById('lookup-test-input');
if (parseBtn) parseBtn.addEventListener('click', runLookupParser);
if (input) {
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') runLookupParser();
});
}
renderLookupSurfaces();
renderLookupSentences();
}
// -------------------------------------------------------------------------
// 9. Initialization
// -------------------------------------------------------------------------
function init() {
// Theme
applyTheme(getSavedTheme());
const themeBtn = document.getElementById('theme-toggle-btn');
if (themeBtn) themeBtn.addEventListener('click', cycleTheme);
// Navigation, Filters, Affordances
setupTabs();
setupFilters();
setupCurlDrawer();
setupLookupTab();
// Initial Fetch & Start Polling
refreshData();
pollInterval = setInterval(refreshData, 15000);
// Update freshness every second
setInterval(() => {
const freshness = document.getElementById('footer-freshness');
if (freshness) {
const secs = Math.max(0, Math.floor((Date.now() - lastUpdated.getTime()) / 1000));
freshness.textContent = `Updated ${secs}s ago`;
}
}, 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();