@axiomatic-labs/claudeflow 2.13.63 → 2.13.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/panel.js +125 -4
- package/package.json +1 -1
package/lib/panel.js
CHANGED
|
@@ -734,6 +734,9 @@ details[open] summary { padding-bottom: 8px; border-bottom: 1px solid var(--bord
|
|
|
734
734
|
.logs-controls { display: flex; gap: 12px; align-items: center; margin-bottom: 14px; }
|
|
735
735
|
.logs-controls select, .logs-controls button { background: var(--panel); border: 1px solid var(--border); color: var(--fg); padding: 6px 10px; border-radius: 6px; font: inherit; }
|
|
736
736
|
.logs-controls button:hover { border-color: var(--accent); cursor: pointer; }
|
|
737
|
+
.observer-filter { background: var(--panel); border: 1px solid var(--border); color: var(--fg); padding: 4px 10px; border-radius: 999px; cursor: pointer; font-size: 12px; font: inherit; }
|
|
738
|
+
.observer-filter:hover { border-color: var(--accent, #6cf); }
|
|
739
|
+
.observer-filter.active { background: rgba(108,170,255,0.12); border-color: var(--accent, #6cf); color: var(--accent, #6cf); }
|
|
737
740
|
.btn-warn { background: var(--panel); border: 1px solid rgba(245,194,52,0.5); color: var(--warn, #f5c234); padding: 6px 12px; border-radius: 6px; cursor: pointer; font: inherit; }
|
|
738
741
|
.btn-warn:hover { border-color: var(--warn, #f5c234); background: rgba(245,194,52,0.08); }
|
|
739
742
|
.btn-warn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
@@ -829,6 +832,7 @@ const SECTIONS = [
|
|
|
829
832
|
|
|
830
833
|
let state = null;
|
|
831
834
|
let active = 'overview';
|
|
835
|
+
let observerFilter = 'all';
|
|
832
836
|
let timer = null;
|
|
833
837
|
|
|
834
838
|
// Preserve which <details> were open across re-renders (auto-refresh would
|
|
@@ -1424,11 +1428,54 @@ function renderObserver() {
|
|
|
1424
1428
|
</div>\`
|
|
1425
1429
|
: '<p class="muted">No browser snapshots received yet. (The observer collects when the agent navigates with mcp__claude-in-chrome__*.)</p>';
|
|
1426
1430
|
|
|
1431
|
+
// Build the filter index: for each surface (server label), find which
|
|
1432
|
+
// incidents belong to it. browser incidents are matched by origin URL
|
|
1433
|
+
// against each server's :PORT. Browser-other = browser incidents whose
|
|
1434
|
+
// origin doesn't match any configured server (e.g. external URL).
|
|
1435
|
+
const serverByOrigin = {};
|
|
1436
|
+
o.servers.forEach((srv) => {
|
|
1437
|
+
if (srv.port) serverByOrigin[\`localhost:\${srv.port}\`] = srv.label;
|
|
1438
|
+
if (srv.port) serverByOrigin[\`127.0.0.1:\${srv.port}\`] = srv.label;
|
|
1439
|
+
});
|
|
1440
|
+
function incidentBucket(inc) {
|
|
1441
|
+
if (inc.source === 'server') {
|
|
1442
|
+
return (inc.metadata && inc.metadata.label) || inc.surface || 'server-unknown';
|
|
1443
|
+
}
|
|
1444
|
+
if (inc.source === 'browser') {
|
|
1445
|
+
const url = (inc.metadata && (inc.metadata.url || inc.metadata.origin)) || '';
|
|
1446
|
+
const m = url.match(/^https?:\\/\\/([^/]+)/);
|
|
1447
|
+
if (m && serverByOrigin[m[1]]) return serverByOrigin[m[1]];
|
|
1448
|
+
return 'browser-other';
|
|
1449
|
+
}
|
|
1450
|
+
return inc.source || 'other';
|
|
1451
|
+
}
|
|
1452
|
+
const buckets = {};
|
|
1453
|
+
o.recent_incidents.forEach((inc) => {
|
|
1454
|
+
const b = incidentBucket(inc);
|
|
1455
|
+
if (!buckets[b]) buckets[b] = [];
|
|
1456
|
+
buckets[b].push(inc);
|
|
1457
|
+
});
|
|
1458
|
+
const surfacesInUse = Object.keys(buckets).sort();
|
|
1459
|
+
// Apply observerFilter client-side state. Default 'all' (no filter).
|
|
1460
|
+
const filteredIncidents = (typeof observerFilter === 'undefined' || observerFilter === 'all')
|
|
1461
|
+
? o.recent_incidents
|
|
1462
|
+
: (buckets[observerFilter] || []);
|
|
1463
|
+
|
|
1464
|
+
const filterPill = (label, value, count) => {
|
|
1465
|
+
const active = (typeof observerFilter === 'undefined' && value === 'all') || observerFilter === value;
|
|
1466
|
+
const cls = active ? 'observer-filter active' : 'observer-filter';
|
|
1467
|
+
return \`<button class="\${cls}" data-observer-filter="\${escapeHtml(value)}">\${escapeHtml(label)}<span class="muted" style="margin-left:6px;font-size:11px;">\${count}</span></button>\`;
|
|
1468
|
+
};
|
|
1469
|
+
const filterPills = '<div class="observer-filters" style="display:flex;gap:6px;flex-wrap:wrap;margin:8px 0;">' +
|
|
1470
|
+
filterPill('All', 'all', o.recent_incidents.length) +
|
|
1471
|
+
surfacesInUse.map((surface) => filterPill(surface, surface, buckets[surface].length)).join('') +
|
|
1472
|
+
'</div>';
|
|
1473
|
+
|
|
1427
1474
|
// Incidents feed
|
|
1428
|
-
const incidentsBlock =
|
|
1429
|
-
? '<p class="muted">No incidents
|
|
1475
|
+
const incidentsBlock = filteredIncidents.length === 0
|
|
1476
|
+
? '<p class="muted">No incidents in this filter.</p>'
|
|
1430
1477
|
: '<div class="incidents-feed">' +
|
|
1431
|
-
|
|
1478
|
+
filteredIncidents.map((inc) => {
|
|
1432
1479
|
const sevClass = inc.severity === 'error' || inc.severity === 'critical' ? 'err' : (inc.severity === 'warning' ? 'warn' : 'info');
|
|
1433
1480
|
const resolvedBadge = inc.resolved_at
|
|
1434
1481
|
? '<span class="badge ok" style="margin-left:6px;">resolved</span>'
|
|
@@ -1452,14 +1499,22 @@ function renderObserver() {
|
|
|
1452
1499
|
}).join('') +
|
|
1453
1500
|
'</div>';
|
|
1454
1501
|
|
|
1502
|
+
// Clear button — only meaningful when the daemon is running AND there's
|
|
1503
|
+
// either at least one unresolved incident or some browser snapshots to wipe.
|
|
1504
|
+
const clearable = daemonAlive && (totals.unresolved > 0 || (o.browser && o.browser.snapshots_received > 0) || o.recent_incidents.length > 0);
|
|
1505
|
+
const clearBtn = clearable
|
|
1506
|
+
? '<button id="clear-observer" class="logs-clear-btn" style="margin-left:14px;" title="Resolves all active incidents, clears per-server recent_log buffers, and resets browser snapshot counter. The state file keeps incident history (marked resolved). Future events repopulate as usual.">Clear observer state</button>'
|
|
1507
|
+
: '';
|
|
1508
|
+
|
|
1455
1509
|
return \`<h2>Observer</h2>
|
|
1456
|
-
<p class="sub">Daemon: \${statusBadge}\${restartBtn}</p>
|
|
1510
|
+
<p class="sub">Daemon: \${statusBadge}\${restartBtn}\${clearBtn}</p>
|
|
1457
1511
|
<p class="sub">Incidents: \${totalsBadge} \${summaryStatus}</p>
|
|
1458
1512
|
<h3 style="margin-top:18px;font-size:14px;">Servers (\${o.servers.length})</h3>
|
|
1459
1513
|
<div class="card" style="padding:0;">\${serversTable}</div>
|
|
1460
1514
|
<h3 style="margin-top:18px;font-size:14px;">Browser channel</h3>
|
|
1461
1515
|
\${browserBlock}
|
|
1462
1516
|
<h3 style="margin-top:18px;font-size:14px;">Incidents (most recent \${o.recent_incidents.length})</h3>
|
|
1517
|
+
\${filterPills}
|
|
1463
1518
|
<div class="card" style="padding:0;">\${incidentsBlock}</div>\`;
|
|
1464
1519
|
}
|
|
1465
1520
|
|
|
@@ -1607,8 +1662,38 @@ document.addEventListener('click', (e) => {
|
|
|
1607
1662
|
if (t.id === 'fix-cdp-port') {
|
|
1608
1663
|
return fixCdpPortAction(t);
|
|
1609
1664
|
}
|
|
1665
|
+
if (t.id === 'clear-observer') {
|
|
1666
|
+
return clearObserverAction(t);
|
|
1667
|
+
}
|
|
1668
|
+
if (t.dataset && t.dataset.observerFilter) {
|
|
1669
|
+
observerFilter = t.dataset.observerFilter;
|
|
1670
|
+
renderContent();
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1610
1673
|
});
|
|
1611
1674
|
|
|
1675
|
+
async function clearObserverAction(btn) {
|
|
1676
|
+
if (!confirm('Resolve all active observer incidents and clear server logs?\\n\\nIncident history is preserved in state.incidents (marked resolved). Future events repopulate normally. This cannot be undone.')) return;
|
|
1677
|
+
const originalLabel = btn.textContent;
|
|
1678
|
+
btn.disabled = true;
|
|
1679
|
+
btn.textContent = 'Clearing…';
|
|
1680
|
+
try {
|
|
1681
|
+
const r = await fetch('/api/observer/clear', { method: 'POST' });
|
|
1682
|
+
const result = await r.json();
|
|
1683
|
+
if (r.ok && result.ok) {
|
|
1684
|
+
showToast('Observer state cleared at ' + (result.reset_at || 'now'), 'ok');
|
|
1685
|
+
} else {
|
|
1686
|
+
showToast('Clear failed: ' + (result.error || 'unknown'), 'err');
|
|
1687
|
+
}
|
|
1688
|
+
} catch (e) {
|
|
1689
|
+
showToast('Network error: ' + e.message, 'err');
|
|
1690
|
+
} finally {
|
|
1691
|
+
btn.disabled = false;
|
|
1692
|
+
btn.textContent = originalLabel;
|
|
1693
|
+
await refresh();
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1612
1697
|
async function fixCdpPortAction(btn) {
|
|
1613
1698
|
const originalLabel = btn.textContent;
|
|
1614
1699
|
btn.disabled = true;
|
|
@@ -1936,6 +2021,42 @@ function handler(cwd) {
|
|
|
1936
2021
|
}), 'application/json');
|
|
1937
2022
|
}
|
|
1938
2023
|
|
|
2024
|
+
if (req.method === 'POST' && route === '/api/observer/clear') {
|
|
2025
|
+
// Forwards to the observer daemon's /reset-session endpoint, which
|
|
2026
|
+
// resolves all active browser + server incidents, clears the per-server
|
|
2027
|
+
// recent_log buffers, and resets the browser snapshot counter. The
|
|
2028
|
+
// history is preserved in state.incidents (marked resolved), so this
|
|
2029
|
+
// is a soft clear: future polls won't see the old incidents as active.
|
|
2030
|
+
const observer = readObserverState(cwd);
|
|
2031
|
+
if (!observer.running) {
|
|
2032
|
+
return send(409, JSON.stringify({ ok: false, error: 'observer is not running — start it first, then clear.' }), 'application/json');
|
|
2033
|
+
}
|
|
2034
|
+
const { request: httpRequest } = require('node:http');
|
|
2035
|
+
const result = await new Promise((resolve) => {
|
|
2036
|
+
const req2 = httpRequest({
|
|
2037
|
+
hostname: '127.0.0.1',
|
|
2038
|
+
port: observer.port,
|
|
2039
|
+
path: '/reset-session',
|
|
2040
|
+
method: 'POST',
|
|
2041
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': 2 },
|
|
2042
|
+
}, (r) => {
|
|
2043
|
+
let body = '';
|
|
2044
|
+
r.on('data', (c) => { body += c; });
|
|
2045
|
+
r.on('end', () => {
|
|
2046
|
+
try { resolve({ status: r.statusCode, body: JSON.parse(body) }); }
|
|
2047
|
+
catch { resolve({ status: r.statusCode, body: { raw: body } }); }
|
|
2048
|
+
});
|
|
2049
|
+
});
|
|
2050
|
+
req2.on('error', (err) => resolve({ status: 0, body: { error: err.message } }));
|
|
2051
|
+
req2.write('{}');
|
|
2052
|
+
req2.end();
|
|
2053
|
+
});
|
|
2054
|
+
if (result.status === 200 && result.body && result.body.ok) {
|
|
2055
|
+
return send(200, JSON.stringify({ ok: true, reset_at: result.body.reset_at }), 'application/json');
|
|
2056
|
+
}
|
|
2057
|
+
return send(502, JSON.stringify({ ok: false, error: 'observer returned ' + result.status, daemon_response: result.body }), 'application/json');
|
|
2058
|
+
}
|
|
2059
|
+
|
|
1939
2060
|
if (req.method === 'POST' && route === '/api/observer/restart') {
|
|
1940
2061
|
// Re-run the SessionStart/browser-error-daemon.js hook script
|
|
1941
2062
|
// synchronously. The script calls ensureObserver() which spawns
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiomatic-labs/claudeflow",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.65",
|
|
4
4
|
"description": "Claudeflow — AI-powered development toolkit for Claude Code. Skills, agents, hooks, and quality gates that ship production apps.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"claudeflow": "./bin/cli.js"
|