@axiomatic-labs/claudeflow 2.13.66 → 2.13.68
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 +109 -17
- package/package.json +1 -1
package/lib/panel.js
CHANGED
|
@@ -331,11 +331,18 @@ function getObserverInfo(cwd) {
|
|
|
331
331
|
const unresolvedIncidents = summary && Array.isArray(summary.unresolved_incidents)
|
|
332
332
|
? summary.unresolved_incidents
|
|
333
333
|
: [];
|
|
334
|
-
const
|
|
334
|
+
const allInState = state && state.incidents && typeof state.incidents === 'object'
|
|
335
335
|
? Object.values(state.incidents)
|
|
336
336
|
.sort((a, b) => String(b.last_seen_at || '').localeCompare(String(a.last_seen_at || '')))
|
|
337
337
|
.slice(0, 50)
|
|
338
338
|
: [];
|
|
339
|
+
// Default surface to UI: ACTIVE only. "Clear observer state" resolves all
|
|
340
|
+
// active incidents, so after clear the active list is empty (which matches
|
|
341
|
+
// the user's expectation that "Clear" empties the feed). Resolved incidents
|
|
342
|
+
// are kept in state.incidents for audit and can be surfaced via the
|
|
343
|
+
// "Show resolved" filter pill.
|
|
344
|
+
const activeIncidents = allInState.filter((inc) => !inc.resolved_at && inc.active !== false);
|
|
345
|
+
const resolvedIncidents = allInState.filter((inc) => inc.resolved_at || inc.active === false);
|
|
339
346
|
|
|
340
347
|
return {
|
|
341
348
|
daemon,
|
|
@@ -347,7 +354,10 @@ function getObserverInfo(cwd) {
|
|
|
347
354
|
servers,
|
|
348
355
|
browser,
|
|
349
356
|
unresolved_incidents: unresolvedIncidents,
|
|
350
|
-
|
|
357
|
+
active_incidents: activeIncidents,
|
|
358
|
+
resolved_incidents: resolvedIncidents,
|
|
359
|
+
// Kept for backwards compat with older render code — same as active_incidents.
|
|
360
|
+
recent_incidents: activeIncidents,
|
|
351
361
|
totals: summary ? {
|
|
352
362
|
unresolved: summary.unresolved_incidents_total || 0,
|
|
353
363
|
browser: summary.browser_incidents_total || 0,
|
|
@@ -848,6 +858,7 @@ const SECTIONS = [
|
|
|
848
858
|
let state = null;
|
|
849
859
|
let active = 'overview';
|
|
850
860
|
let observerFilter = 'all';
|
|
861
|
+
let observerShowResolved = false;
|
|
851
862
|
let timer = null;
|
|
852
863
|
|
|
853
864
|
// Preserve which <details> were open across re-renders (auto-refresh would
|
|
@@ -1020,6 +1031,13 @@ function row(k, v, badge) {
|
|
|
1020
1031
|
return \`<div class="card-row"><span class="k">\${escapeHtml(k)}</span><span class="v">\${b}\${escapeHtml(v)}</span></div>\`;
|
|
1021
1032
|
}
|
|
1022
1033
|
|
|
1034
|
+
// Same as row() but the value is rendered as raw HTML (caller is responsible
|
|
1035
|
+
// for escaping). Use for values that already contain HTML markup (e.g. <code>).
|
|
1036
|
+
function rowHtml(k, vHtml, badge) {
|
|
1037
|
+
const b = badge ? \`<span class="badge \${badge.kind}">\${escapeHtml(badge.text)}</span>\` : '';
|
|
1038
|
+
return \`<div class="card-row"><span class="k">\${escapeHtml(k)}</span><span class="v">\${b}\${vHtml}</span></div>\`;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1023
1041
|
function escapeHtml(s) {
|
|
1024
1042
|
return String(s == null ? '' : s)
|
|
1025
1043
|
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
@@ -1450,17 +1468,20 @@ function renderObserver() {
|
|
|
1450
1468
|
// Browser section
|
|
1451
1469
|
const browserBlock = o.browser && o.browser.last_snapshot_at
|
|
1452
1470
|
? \`<div class="card" style="padding:10px 14px;font-size:13px;">
|
|
1453
|
-
\${
|
|
1454
|
-
\${
|
|
1455
|
-
\${
|
|
1456
|
-
\${
|
|
1471
|
+
\${rowHtml('Latest URL', o.browser.latest_url ? '<code>' + escapeHtml(o.browser.latest_url) + '</code>' : '<span class="muted">none</span>')}
|
|
1472
|
+
\${rowHtml('Latest route', o.browser.latest_route ? '<code>' + escapeHtml(o.browser.latest_route) + '</code>' : '<span class="muted">none</span>')}
|
|
1473
|
+
\${rowHtml('Snapshots received', String(o.browser.snapshots_received))}
|
|
1474
|
+
\${rowHtml('Last snapshot', escapeHtml(formatRelativeTime(o.browser.last_snapshot_at)))}
|
|
1457
1475
|
</div>\`
|
|
1458
1476
|
: '<p class="muted">No browser snapshots received yet. (The observer collects when the agent navigates with mcp__claude-in-chrome__*.)</p>';
|
|
1459
1477
|
|
|
1460
|
-
//
|
|
1461
|
-
//
|
|
1462
|
-
//
|
|
1463
|
-
|
|
1478
|
+
// Active vs resolved toggle (client-side state observerShowResolved).
|
|
1479
|
+
// After "Clear observer state", all active incidents are marked resolved;
|
|
1480
|
+
// by default the UI shows only active. Toggle the pill to see history.
|
|
1481
|
+
const showResolved = (typeof observerShowResolved !== 'undefined') && observerShowResolved;
|
|
1482
|
+
const sourceList = showResolved ? o.resolved_incidents : o.active_incidents;
|
|
1483
|
+
|
|
1484
|
+
// Build the filter index ON THE CURRENT VIEW (active or resolved).
|
|
1464
1485
|
const serverByOrigin = {};
|
|
1465
1486
|
o.servers.forEach((srv) => {
|
|
1466
1487
|
if (srv.port) serverByOrigin[\`localhost:\${srv.port}\`] = srv.label;
|
|
@@ -1479,7 +1500,7 @@ function renderObserver() {
|
|
|
1479
1500
|
return inc.source || 'other';
|
|
1480
1501
|
}
|
|
1481
1502
|
const buckets = {};
|
|
1482
|
-
|
|
1503
|
+
sourceList.forEach((inc) => {
|
|
1483
1504
|
const b = incidentBucket(inc);
|
|
1484
1505
|
if (!buckets[b]) buckets[b] = [];
|
|
1485
1506
|
buckets[b].push(inc);
|
|
@@ -1487,7 +1508,7 @@ function renderObserver() {
|
|
|
1487
1508
|
const surfacesInUse = Object.keys(buckets).sort();
|
|
1488
1509
|
// Apply observerFilter client-side state. Default 'all' (no filter).
|
|
1489
1510
|
const filteredIncidents = (typeof observerFilter === 'undefined' || observerFilter === 'all')
|
|
1490
|
-
?
|
|
1511
|
+
? sourceList
|
|
1491
1512
|
: (buckets[observerFilter] || []);
|
|
1492
1513
|
|
|
1493
1514
|
const filterPill = (label, value, count) => {
|
|
@@ -1495,11 +1516,42 @@ function renderObserver() {
|
|
|
1495
1516
|
const cls = active ? 'observer-filter active' : 'observer-filter';
|
|
1496
1517
|
return \`<button class="\${cls}" data-observer-filter="\${escapeHtml(value)}">\${escapeHtml(label)}<span class="muted" style="margin-left:6px;font-size:11px;">\${count}</span></button>\`;
|
|
1497
1518
|
};
|
|
1498
|
-
|
|
1499
|
-
|
|
1519
|
+
// Show resolved toggle pill — separate from surface filters.
|
|
1520
|
+
const showResolvedClass = showResolved ? 'observer-filter active' : 'observer-filter';
|
|
1521
|
+
const showResolvedPill = \`<button class="\${showResolvedClass}" data-observer-show-resolved="toggle" style="margin-left:auto;">\${showResolved ? 'Showing resolved' : 'Show resolved'}<span class="muted" style="margin-left:6px;font-size:11px;">\${o.resolved_incidents.length}</span></button>\`;
|
|
1522
|
+
const filterPills = '<div class="observer-filters" style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin:8px 0;">' +
|
|
1523
|
+
filterPill('All', 'all', sourceList.length) +
|
|
1500
1524
|
surfacesInUse.map((surface) => filterPill(surface, surface, buckets[surface].length)).join('') +
|
|
1525
|
+
showResolvedPill +
|
|
1501
1526
|
'</div>';
|
|
1502
1527
|
|
|
1528
|
+
// Match file:line patterns in stack traces (most common formats):
|
|
1529
|
+
// http://localhost:3001/src/foo.tsx:51:22
|
|
1530
|
+
// /Users/abs/path.ts:42
|
|
1531
|
+
// at SomeFn (path:line:col)
|
|
1532
|
+
function extractFileLineHints(text) {
|
|
1533
|
+
if (typeof text !== 'string') return [];
|
|
1534
|
+
const hints = new Set();
|
|
1535
|
+
// localhost source URLs (Vite/CRA dev server)
|
|
1536
|
+
const localRe = /https?:\\/\\/localhost(?::\\d+)?\\/([^\\s:]+?\\.(?:tsx?|jsx?|vue|svelte)):(\\d+)(?::(\\d+))?/g;
|
|
1537
|
+
let m;
|
|
1538
|
+
while ((m = localRe.exec(text)) !== null) {
|
|
1539
|
+
hints.add(\`\${m[1]}:\${m[2]}\${m[3] ? ':' + m[3] : ''}\`);
|
|
1540
|
+
if (hints.size >= 6) break;
|
|
1541
|
+
}
|
|
1542
|
+
// Bare absolute paths
|
|
1543
|
+
const absRe = /\\b(\\/[A-Za-z0-9_./-]+?\\.(?:tsx?|jsx?|vue|svelte)):(\\d+)(?::(\\d+))?/g;
|
|
1544
|
+
while ((m = absRe.exec(text)) !== null) {
|
|
1545
|
+
hints.add(\`\${m[1]}:\${m[2]}\${m[3] ? ':' + m[3] : ''}\`);
|
|
1546
|
+
if (hints.size >= 6) break;
|
|
1547
|
+
}
|
|
1548
|
+
return [...hints];
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
// Server-side detail (recent_log + exit info) lookup per surface label.
|
|
1552
|
+
const serverByLabel = {};
|
|
1553
|
+
o.servers.forEach((srv) => { serverByLabel[srv.label] = srv; });
|
|
1554
|
+
|
|
1503
1555
|
// Incidents feed
|
|
1504
1556
|
const incidentsBlock = filteredIncidents.length === 0
|
|
1505
1557
|
? '<p class="muted">No incidents in this filter.</p>'
|
|
@@ -1512,6 +1564,36 @@ function renderObserver() {
|
|
|
1512
1564
|
const surfaceLabel = inc.source === 'server' && inc.metadata && inc.metadata.label
|
|
1513
1565
|
? inc.metadata.label
|
|
1514
1566
|
: inc.surface || inc.source;
|
|
1567
|
+
const fullMsg = inc.message || '';
|
|
1568
|
+
const fileLineHints = extractFileLineHints(fullMsg);
|
|
1569
|
+
// Show first 240 chars by default, collapsible to full message.
|
|
1570
|
+
const PREVIEW_CHARS = 240;
|
|
1571
|
+
const isLong = fullMsg.length > PREVIEW_CHARS;
|
|
1572
|
+
const previewMsg = isLong ? fullMsg.slice(0, PREVIEW_CHARS) + '…' : fullMsg;
|
|
1573
|
+
const fullBlock = isLong
|
|
1574
|
+
? '<details style="margin-top:4px;"><summary style="cursor:pointer;color:var(--muted);font-size:11px;">Show full message + stack (' + fullMsg.length + ' chars)</summary><pre style="font-size:11px;white-space:pre-wrap;background:var(--panel-2);padding:8px;border-radius:4px;margin:4px 0 0 0;overflow:auto;max-height:400px;">' + escapeHtml(fullMsg) + '</pre></details>'
|
|
1575
|
+
: '';
|
|
1576
|
+
const fileLinesHtml = fileLineHints.length > 0
|
|
1577
|
+
? '<div class="muted" style="font-size:11px;margin-top:4px;">📄 ' + fileLineHints.map((f) => '<code>' + escapeHtml(f) + '</code>').join(' • ') + '</div>'
|
|
1578
|
+
: '';
|
|
1579
|
+
const urlLine = inc.metadata && inc.metadata.url
|
|
1580
|
+
? '<div class="muted" style="font-size:11px;margin-top:4px;">🌐 <code>' + escapeHtml(inc.metadata.url) + '</code>' + (inc.metadata.route && inc.metadata.route !== inc.metadata.url ? ' (' + escapeHtml(inc.metadata.route) + ')' : '') + '</div>'
|
|
1581
|
+
: '';
|
|
1582
|
+
// For server incidents, attach recent_log + exit info from the matching server record.
|
|
1583
|
+
let serverDetail = '';
|
|
1584
|
+
if (inc.source === 'server') {
|
|
1585
|
+
const srv = serverByLabel[surfaceLabel];
|
|
1586
|
+
if (srv) {
|
|
1587
|
+
const exitInfo = srv.last_exit_code !== null && srv.last_exit_code !== undefined
|
|
1588
|
+
? \`<div class="muted" style="font-size:11px;margin-top:4px;">🛑 last exit: code \${srv.last_exit_code}\${srv.last_exit_signal ? ' / signal ' + srv.last_exit_signal : ''} at \${escapeHtml(formatRelativeTime(srv.last_exit_at))}</div>\`
|
|
1589
|
+
: '';
|
|
1590
|
+
const logLines = Array.isArray(srv.recent_log) ? srv.recent_log : [];
|
|
1591
|
+
const logBlock = logLines.length > 0
|
|
1592
|
+
? '<details style="margin-top:4px;"><summary style="cursor:pointer;color:var(--muted);font-size:11px;">📋 recent_log (' + logLines.length + ' lines from daemon-supervised stdout/stderr)</summary><pre style="font-size:11px;white-space:pre-wrap;background:var(--panel-2);padding:8px;border-radius:4px;margin:4px 0 0 0;overflow:auto;max-height:300px;">' + logLines.slice(-50).map((l) => escapeHtml(typeof l === 'string' ? l : (l.line || JSON.stringify(l)))).join('\\n') + '</pre></details>'
|
|
1593
|
+
: '<div class="muted" style="font-size:11px;margin-top:4px;">📋 No recent_log captured (server is observed_external — daemon only port-probes it, doesn\\'t pipe stdout/stderr). Run the server under the daemon supervisor to capture log lines, or check the dev server\\'s own terminal output.</div>';
|
|
1594
|
+
serverDetail = exitInfo + logBlock;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1515
1597
|
return \`<div class="incident-row" style="padding:10px 14px;border-top:1px solid var(--border);">
|
|
1516
1598
|
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:4px;">
|
|
1517
1599
|
<span class="badge \${sevClass}">\${escapeHtml(inc.severity || 'info')}</span>
|
|
@@ -1522,8 +1604,11 @@ function renderObserver() {
|
|
|
1522
1604
|
\${resolvedBadge}
|
|
1523
1605
|
<span class="muted" style="margin-left:auto;font-size:11px;">\${escapeHtml(formatRelativeTime(inc.last_seen_at))}</span>
|
|
1524
1606
|
</div>
|
|
1525
|
-
<div style="font-size:12px;white-space:pre-wrap;">\${escapeHtml(
|
|
1526
|
-
\${
|
|
1607
|
+
<div style="font-size:12px;white-space:pre-wrap;">\${escapeHtml(previewMsg)}</div>
|
|
1608
|
+
\${fullBlock}
|
|
1609
|
+
\${fileLinesHtml}
|
|
1610
|
+
\${urlLine}
|
|
1611
|
+
\${serverDetail}
|
|
1527
1612
|
</div>\`;
|
|
1528
1613
|
}).join('') +
|
|
1529
1614
|
'</div>';
|
|
@@ -1542,7 +1627,7 @@ function renderObserver() {
|
|
|
1542
1627
|
<div class="card" style="padding:0;">\${serversTable}</div>
|
|
1543
1628
|
<h3 style="margin-top:18px;font-size:14px;">Browser channel</h3>
|
|
1544
1629
|
\${browserBlock}
|
|
1545
|
-
<h3 style="margin-top:18px;font-size:14px;"
|
|
1630
|
+
<h3 style="margin-top:18px;font-size:14px;">\${showResolved ? 'Resolved incidents (' + o.resolved_incidents.length + ')' : 'Active incidents (' + o.active_incidents.length + ')'}</h3>
|
|
1546
1631
|
\${filterPills}
|
|
1547
1632
|
<div class="card" style="padding:0;">\${incidentsBlock}</div>\`;
|
|
1548
1633
|
}
|
|
@@ -1699,6 +1784,13 @@ document.addEventListener('click', (e) => {
|
|
|
1699
1784
|
renderContent();
|
|
1700
1785
|
return;
|
|
1701
1786
|
}
|
|
1787
|
+
if (t.dataset && t.dataset.observerShowResolved) {
|
|
1788
|
+
observerShowResolved = !observerShowResolved;
|
|
1789
|
+
// Reset surface filter when toggling, since counts change.
|
|
1790
|
+
observerFilter = 'all';
|
|
1791
|
+
renderContent();
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1702
1794
|
});
|
|
1703
1795
|
|
|
1704
1796
|
async function clearObserverAction(btn) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiomatic-labs/claudeflow",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.68",
|
|
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"
|