@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.
Files changed (2) hide show
  1. package/lib/panel.js +125 -4
  2. 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 = o.recent_incidents.length === 0
1429
- ? '<p class="muted">No incidents recorded.</p>'
1475
+ const incidentsBlock = filteredIncidents.length === 0
1476
+ ? '<p class="muted">No incidents in this filter.</p>'
1430
1477
  : '<div class="incidents-feed">' +
1431
- o.recent_incidents.map((inc) => {
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.63",
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"