openclaw-observability 1.0.0 → 1.0.2
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/README.md +189 -0
- package/dist/config.js +3 -3
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +62 -61
- package/dist/index.js.map +1 -1
- package/dist/security/scanner.js +2 -2
- package/dist/security/scanner.js.map +1 -1
- package/dist/security/types.d.ts +1 -1
- package/dist/security/types.js +1 -1
- package/dist/storage/buffer.js +2 -2
- package/dist/storage/buffer.js.map +1 -1
- package/dist/storage/duckdb-local-writer.d.ts.map +1 -1
- package/dist/storage/duckdb-local-writer.js +65 -43
- package/dist/storage/duckdb-local-writer.js.map +1 -1
- package/dist/storage/mysql-writer.js +4 -4
- package/dist/storage/mysql-writer.js.map +1 -1
- package/dist/storage/schema.js +1 -1
- package/dist/storage/schema.js.map +1 -1
- package/dist/web/api.d.ts +50 -0
- package/dist/web/api.d.ts.map +1 -1
- package/dist/web/api.js +176 -0
- package/dist/web/api.js.map +1 -1
- package/dist/web/routes.d.ts +2 -2
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +30 -22
- package/dist/web/routes.js.map +1 -1
- package/dist/web/ui.d.ts +1 -1
- package/dist/web/ui.js +440 -6
- package/dist/web/ui.js.map +1 -1
- package/openclaw.plugin.json +13 -13
- package/package.json +5 -5
package/dist/web/ui.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Observability panel SPA — single-page HTML application
|
|
4
4
|
* Contains Dashboard overview + Session Trace detail views
|
|
5
5
|
* Uses hash routing: #/ = Dashboard, #/trace/{sessionId} = Trace
|
|
6
6
|
*
|
|
@@ -14,7 +14,7 @@ function getAppHtml() {
|
|
|
14
14
|
'<head>\n' +
|
|
15
15
|
'<meta charset="UTF-8">\n' +
|
|
16
16
|
'<meta name="viewport" content="width=device-width, initial-scale=1.0">\n' +
|
|
17
|
-
'<title>OpenClaw
|
|
17
|
+
'<title>OpenClaw Observability</title>\n' +
|
|
18
18
|
'<meta name="color-scheme" content="dark light">\n' +
|
|
19
19
|
'<style>\n' +
|
|
20
20
|
CSS +
|
|
@@ -293,6 +293,78 @@ body.resizing *{cursor:row-resize!important}
|
|
|
293
293
|
.trace-alert-banner:hover{filter:brightness(1.1)}
|
|
294
294
|
.trace-alert-banner .count{font-weight:700}
|
|
295
295
|
|
|
296
|
+
/* ---- Analytics ---- */
|
|
297
|
+
.an-grid{display:grid;gap:20px;margin-bottom:24px}
|
|
298
|
+
.an-grid-2{grid-template-columns:1fr 1fr}
|
|
299
|
+
.an-grid-3{grid-template-columns:1fr 1fr 1fr}
|
|
300
|
+
@media(max-width:900px){.an-grid-2,.an-grid-3{grid-template-columns:1fr}}
|
|
301
|
+
.an-card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:20px 24px;box-shadow:inset 0 1px 0 var(--card-highlight)}
|
|
302
|
+
.an-card h3{font-size:14px;font-weight:600;color:var(--text-strong);margin-bottom:16px;display:flex;align-items:center;gap:8px;letter-spacing:-.02em}
|
|
303
|
+
.an-card h3 .icon{font-size:16px}
|
|
304
|
+
.an-card .sub{font-size:11px;color:var(--muted);font-weight:400;margin-left:auto}
|
|
305
|
+
|
|
306
|
+
/* Bar chart */
|
|
307
|
+
.chart-bars{display:flex;align-items:flex-end;gap:3px;height:160px;padding:0 4px;justify-content:center}
|
|
308
|
+
.chart-bar-col{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;min-width:0;max-width:60px;position:relative;height:100%}
|
|
309
|
+
.chart-bar{width:100%;border-radius:3px 3px 0 0;min-height:2px;transition:opacity var(--duration-fast);cursor:pointer;position:relative}
|
|
310
|
+
.chart-bar:hover{opacity:.85}
|
|
311
|
+
.chart-bar-stack{width:100%;display:flex;flex-direction:column-reverse}
|
|
312
|
+
.chart-bar-seg{width:100%;min-height:0;transition:opacity var(--duration-fast)}
|
|
313
|
+
.chart-bar-seg:last-child{border-radius:3px 3px 0 0}
|
|
314
|
+
.chart-x-labels{display:flex;gap:3px;padding:6px 4px 0;border-top:1px solid var(--border);justify-content:center}
|
|
315
|
+
.chart-x-labels span{flex:1;text-align:center;font-size:9px;color:var(--muted);font-family:var(--mono);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:60px}
|
|
316
|
+
.chart-y-axis{display:flex;flex-direction:column;justify-content:space-between;align-items:flex-end;height:160px;padding-right:8px;min-width:40px}
|
|
317
|
+
.chart-y-axis span{font-size:10px;color:var(--muted);font-family:var(--mono)}
|
|
318
|
+
.chart-container{display:flex}
|
|
319
|
+
.chart-main{flex:1;min-width:0}
|
|
320
|
+
.chart-tooltip{position:absolute;bottom:100%;left:50%;transform:translateX(-50%);background:var(--card);border:1px solid var(--border);border-radius:var(--radius-sm);padding:4px 8px;font-size:11px;color:var(--text);white-space:nowrap;pointer-events:none;z-index:20;box-shadow:var(--shadow-md);display:none}
|
|
321
|
+
.chart-bar-col:hover .chart-tooltip{display:block}
|
|
322
|
+
.chart-legend{display:flex;gap:16px;justify-content:center;margin-top:12px;flex-wrap:wrap}
|
|
323
|
+
.chart-legend-item{display:flex;align-items:center;gap:5px;font-size:11px;color:var(--muted)}
|
|
324
|
+
.chart-legend-dot{width:10px;height:10px;border-radius:2px;flex-shrink:0}
|
|
325
|
+
|
|
326
|
+
/* Horizontal bar chart */
|
|
327
|
+
.hbar-list{display:flex;flex-direction:column;gap:10px}
|
|
328
|
+
.hbar-row{display:flex;align-items:center;gap:10px}
|
|
329
|
+
.hbar-label{font-size:12px;color:var(--text);min-width:0;flex:0 0 140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--mono);text-align:right}
|
|
330
|
+
.hbar-track{flex:1;height:22px;background:var(--secondary);border-radius:4px;overflow:hidden;display:flex;position:relative}
|
|
331
|
+
.hbar-fill{height:100%;border-radius:4px 0 0 4px;transition:width .3s var(--ease-out);min-width:2px}
|
|
332
|
+
.hbar-fill:last-child{border-radius:0 4px 4px 0}
|
|
333
|
+
.hbar-value{font-size:11px;color:var(--muted);min-width:60px;font-family:var(--mono);text-align:right}
|
|
334
|
+
|
|
335
|
+
/* Donut/ring chart */
|
|
336
|
+
.donut-wrap{display:flex;align-items:center;gap:24px;justify-content:center}
|
|
337
|
+
.donut-svg{width:140px;height:140px;transform:rotate(-90deg)}
|
|
338
|
+
.donut-circle{fill:none;stroke-linecap:round;transition:stroke-dashoffset .4s var(--ease-out)}
|
|
339
|
+
.donut-center{font-size:18px;font-weight:700;color:var(--text-strong)}
|
|
340
|
+
.donut-legend{display:flex;flex-direction:column;gap:8px}
|
|
341
|
+
.donut-legend-item{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--text)}
|
|
342
|
+
.donut-legend-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}
|
|
343
|
+
.donut-legend-val{margin-left:auto;font-family:var(--mono);color:var(--muted);font-size:11px;min-width:50px;text-align:right}
|
|
344
|
+
|
|
345
|
+
/* Data table */
|
|
346
|
+
.an-table{width:100%;border-collapse:collapse}
|
|
347
|
+
.an-table th{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;font-weight:600;text-align:left;padding:8px 12px;border-bottom:1px solid var(--border)}
|
|
348
|
+
.an-table th:last-child,.an-table td:last-child{text-align:right}
|
|
349
|
+
.an-table td{font-size:12px;color:var(--text);padding:8px 12px;border-bottom:1px solid var(--border)}
|
|
350
|
+
.an-table tr:last-child td{border-bottom:none}
|
|
351
|
+
.an-table tr:hover td{background:var(--bg-hover)}
|
|
352
|
+
.an-table .mono{font-family:var(--mono);color:var(--text-strong)}
|
|
353
|
+
|
|
354
|
+
/* Token split bar */
|
|
355
|
+
.token-split{display:flex;height:8px;border-radius:4px;overflow:hidden;margin-top:4px}
|
|
356
|
+
.token-split .inp{background:#8b5cf6}
|
|
357
|
+
.token-split .outp{background:#f59e0b}
|
|
358
|
+
.kpi-sub{font-size:11px;color:var(--muted);margin-top:4px;font-family:var(--mono)}
|
|
359
|
+
.kpi-sub .inp-color{color:#8b5cf6}
|
|
360
|
+
.kpi-sub .outp-color{color:#f59e0b}
|
|
361
|
+
|
|
362
|
+
/* Metric tab switcher */
|
|
363
|
+
.metric-tabs{display:flex;gap:4px;margin-bottom:12px}
|
|
364
|
+
.metric-tab{padding:4px 12px;border-radius:var(--radius-sm);font-size:11px;font-weight:500;color:var(--muted);cursor:pointer;transition:all var(--duration-fast);border:1px solid transparent;background:none}
|
|
365
|
+
.metric-tab:hover{color:var(--text);background:var(--bg-hover)}
|
|
366
|
+
.metric-tab.active{color:var(--accent-foreground);background:var(--accent);border-color:var(--accent)}
|
|
367
|
+
|
|
296
368
|
/* ---- Empty / Loading ---- */
|
|
297
369
|
.empty{text-align:center;padding:48px 24px;color:var(--muted)}
|
|
298
370
|
.empty .icon{font-size:36px;margin-bottom:12px}
|
|
@@ -337,7 +409,7 @@ var TYPE_LABELS = {
|
|
|
337
409
|
var app = document.getElementById('app');
|
|
338
410
|
var currentPage = 1;
|
|
339
411
|
var filterSearch = '';
|
|
340
|
-
var filterTimeRange = ''; //
|
|
412
|
+
var filterTimeRange = '24h'; // default to 24h for better performance
|
|
341
413
|
|
|
342
414
|
var TIME_PRESETS = [
|
|
343
415
|
{ key:'30m', label:'30 min', ms: 30*60*1000 },
|
|
@@ -367,7 +439,7 @@ function getTimeLabel() {
|
|
|
367
439
|
|
|
368
440
|
/* ---------- theme ---------- */
|
|
369
441
|
function getTheme() {
|
|
370
|
-
var saved = localStorage.getItem('oc-
|
|
442
|
+
var saved = localStorage.getItem('oc-observability-theme');
|
|
371
443
|
if (saved) return saved;
|
|
372
444
|
return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
|
373
445
|
}
|
|
@@ -377,7 +449,7 @@ function applyTheme(t) {
|
|
|
377
449
|
} else {
|
|
378
450
|
document.documentElement.removeAttribute('data-theme');
|
|
379
451
|
}
|
|
380
|
-
localStorage.setItem('oc-
|
|
452
|
+
localStorage.setItem('oc-observability-theme', t);
|
|
381
453
|
}
|
|
382
454
|
function toggleTheme() {
|
|
383
455
|
var cur = getTheme();
|
|
@@ -494,6 +566,8 @@ function router() {
|
|
|
494
566
|
var sid = decodeURIComponent(qIdx >= 0 ? raw.substring(0, qIdx) : raw);
|
|
495
567
|
var params = parseHashParams(hash);
|
|
496
568
|
renderTrace(sid, params.action, params.t);
|
|
569
|
+
} else if (hash.indexOf('#/analytics') === 0) {
|
|
570
|
+
renderAnalytics();
|
|
497
571
|
} else if (hash.indexOf('#/security') === 0) {
|
|
498
572
|
renderSecurity();
|
|
499
573
|
} else {
|
|
@@ -515,11 +589,12 @@ function renderLayout(active, content) {
|
|
|
515
589
|
'<div class="brand-logo">' + ICON_ACTIVITY + '</div>' +
|
|
516
590
|
'<div class="brand-text">' +
|
|
517
591
|
'<div class="brand-title">OpenClaw</div>' +
|
|
518
|
-
'<div class="brand-sub">
|
|
592
|
+
'<div class="brand-sub">Observability</div>' +
|
|
519
593
|
'</div>' +
|
|
520
594
|
'</div>' +
|
|
521
595
|
'<div class="topbar-nav">' +
|
|
522
596
|
'<a href="#/" class="' + (active==='dashboard'?'active':'') + '">Dashboard</a>' +
|
|
597
|
+
'<a href="#/analytics" class="' + (active==='analytics'?'active':'') + '">Analytics</a>' +
|
|
523
598
|
'<a href="#/security" class="' + (active==='security'?'active':'') + '">Security' + (window.__alertCount > 0 ? '<span class="nav-badge">' + window.__alertCount + '</span>' : '') + '</a>' +
|
|
524
599
|
'</div>' +
|
|
525
600
|
'</div>' +
|
|
@@ -678,8 +753,367 @@ window.selectTimeRange = function(key) {
|
|
|
678
753
|
document.addEventListener('click', function() {
|
|
679
754
|
var menu = document.getElementById('time-menu');
|
|
680
755
|
if (menu) menu.classList.remove('open');
|
|
756
|
+
// Close analytics dropdowns too
|
|
757
|
+
var anMenu = document.getElementById('an-time-menu');
|
|
758
|
+
if (anMenu) anMenu.classList.remove('open');
|
|
681
759
|
});
|
|
682
760
|
|
|
761
|
+
/* ================================================================ */
|
|
762
|
+
/* Analytics tab */
|
|
763
|
+
/* ================================================================ */
|
|
764
|
+
|
|
765
|
+
var anTimeRange = '24h';
|
|
766
|
+
var anMetricTab = 'sessions'; // sessions | tokens
|
|
767
|
+
|
|
768
|
+
function anGetTimeFromISO() {
|
|
769
|
+
if (!anTimeRange) return '';
|
|
770
|
+
var preset = TIME_PRESETS.find(function(p){ return p.key === anTimeRange; });
|
|
771
|
+
if (!preset || !preset.ms) return '';
|
|
772
|
+
return new Date(Date.now() - preset.ms).toISOString();
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function anGetTimeLabel() {
|
|
776
|
+
if (!anTimeRange) return 'All time';
|
|
777
|
+
var preset = TIME_PRESETS.find(function(p){ return p.key === anTimeRange; });
|
|
778
|
+
return preset ? preset.label : 'All time';
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
async function renderAnalytics() {
|
|
782
|
+
app.innerHTML = renderLayout('analytics', '<div class="loading">Loading analytics...</div>');
|
|
783
|
+
|
|
784
|
+
try {
|
|
785
|
+
var qs = '';
|
|
786
|
+
var tf = anGetTimeFromISO();
|
|
787
|
+
if (tf) qs = '?timeFrom=' + encodeURIComponent(tf);
|
|
788
|
+
|
|
789
|
+
var data = await fetchApi('/analytics' + qs);
|
|
790
|
+
|
|
791
|
+
var ov = data.overview;
|
|
792
|
+
var ts = data.timeSeries || [];
|
|
793
|
+
var mu = data.modelUsage || [];
|
|
794
|
+
var ad = data.actionDistribution || {};
|
|
795
|
+
var ta = data.topAgents || [];
|
|
796
|
+
var tbm = data.tokensByModel || [];
|
|
797
|
+
|
|
798
|
+
var html = '';
|
|
799
|
+
|
|
800
|
+
// --- Time range filter ---
|
|
801
|
+
html += '<div class="filter-bar" style="margin-bottom:20px">';
|
|
802
|
+
html += '<div class="time-dropdown" id="an-time-dropdown">';
|
|
803
|
+
html += '<button class="time-btn" onclick="anToggleTimeMenu(event)">';
|
|
804
|
+
html += '<svg viewBox="0 0 24 24" style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.5"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg> ';
|
|
805
|
+
html += esc(anGetTimeLabel());
|
|
806
|
+
html += ' <svg viewBox="0 0 24 24" style="width:12px;height:12px"><polyline points="6 9 12 15 18 9"/></svg>';
|
|
807
|
+
html += '</button>';
|
|
808
|
+
html += '<div class="time-menu" id="an-time-menu">';
|
|
809
|
+
TIME_PRESETS.forEach(function(p) {
|
|
810
|
+
var cls = (p.key === anTimeRange) ? ' active' : '';
|
|
811
|
+
html += '<div class="time-menu-item' + cls + '" onclick="anSelectTime(\\'' + p.key + '\\')">';
|
|
812
|
+
html += '<span class="check">' + (p.key === anTimeRange ? '✓' : '') + '</span>';
|
|
813
|
+
html += esc(p.label);
|
|
814
|
+
html += '</div>';
|
|
815
|
+
});
|
|
816
|
+
html += '</div></div>';
|
|
817
|
+
html += '</div>';
|
|
818
|
+
|
|
819
|
+
// --- KPI stat cards ---
|
|
820
|
+
var inpPct = ov.totalTokens > 0 ? Math.round(ov.inputTokens / ov.totalTokens * 100) : 50;
|
|
821
|
+
html += '<div class="stat-grid" style="grid-template-columns:repeat(auto-fit,minmax(140px,1fr))">';
|
|
822
|
+
html += statCard('Sessions', fmtNum(ov.totalSessions));
|
|
823
|
+
|
|
824
|
+
// Tokens with input/output split
|
|
825
|
+
html += '<div class="stat"><div class="stat-label">Total Tokens</div>';
|
|
826
|
+
html += '<div class="stat-value">' + fmtNum(ov.totalTokens) + '</div>';
|
|
827
|
+
html += '<div class="token-split"><div class="inp" style="width:' + inpPct + '%"></div><div class="outp" style="width:' + (100 - inpPct) + '%"></div></div>';
|
|
828
|
+
html += '<div class="kpi-sub"><span class="inp-color">⬤</span> ' + fmtNum(ov.inputTokens) + ' in <span class="outp-color">⬤</span> ' + fmtNum(ov.outputTokens) + ' out</div>';
|
|
829
|
+
html += '</div>';
|
|
830
|
+
|
|
831
|
+
html += statCard('Actions', fmtNum(ov.totalActions));
|
|
832
|
+
html += statCard('Avg Latency', fmtDur(ov.avgLatencyMs));
|
|
833
|
+
html += statCard('Models', String(ov.activeModels));
|
|
834
|
+
html += statCard('Alerts', String(ov.securityAlerts));
|
|
835
|
+
html += '</div>';
|
|
836
|
+
|
|
837
|
+
// --- Row 1: Traces by time + Token usage by time ---
|
|
838
|
+
html += '<div class="an-grid an-grid-2">';
|
|
839
|
+
|
|
840
|
+
// Traces by time (bar chart)
|
|
841
|
+
html += '<div class="an-card">';
|
|
842
|
+
var gran = data.granularity || 'day';
|
|
843
|
+
var granLabel = gran === 'hour' ? (ts.length + ' hours') : (ts.length + ' days');
|
|
844
|
+
html += '<h3><span class="icon">📊</span> Activity Over Time';
|
|
845
|
+
html += '<span class="sub">' + granLabel + '</span></h3>';
|
|
846
|
+
html += buildTimeSeriesChart(ts, anMetricTab, gran);
|
|
847
|
+
html += '</div>';
|
|
848
|
+
|
|
849
|
+
// Model Usage (horizontal bars)
|
|
850
|
+
html += '<div class="an-card">';
|
|
851
|
+
html += '<h3><span class="icon">🤖</span> Model Usage';
|
|
852
|
+
html += '<span class="sub">' + mu.length + ' models</span></h3>';
|
|
853
|
+
html += buildModelUsageChart(mu, tbm);
|
|
854
|
+
html += '</div>';
|
|
855
|
+
|
|
856
|
+
html += '</div>';
|
|
857
|
+
|
|
858
|
+
// --- Row 2: Action Distribution + Top Agents ---
|
|
859
|
+
html += '<div class="an-grid an-grid-2">';
|
|
860
|
+
|
|
861
|
+
// Action type distribution (donut + legend)
|
|
862
|
+
html += '<div class="an-card">';
|
|
863
|
+
html += '<h3><span class="icon">🎯</span> Action Distribution</h3>';
|
|
864
|
+
html += buildActionDistribution(ad);
|
|
865
|
+
html += '</div>';
|
|
866
|
+
|
|
867
|
+
// Top agents table
|
|
868
|
+
html += '<div class="an-card">';
|
|
869
|
+
html += '<h3><span class="icon">👤</span> Top Agents';
|
|
870
|
+
html += '<span class="sub">' + ta.length + ' agents</span></h3>';
|
|
871
|
+
html += buildAgentsTable(ta);
|
|
872
|
+
html += '</div>';
|
|
873
|
+
|
|
874
|
+
html += '</div>';
|
|
875
|
+
|
|
876
|
+
app.innerHTML = renderLayout('analytics', html);
|
|
877
|
+
|
|
878
|
+
} catch(err) {
|
|
879
|
+
app.innerHTML = renderLayout('analytics',
|
|
880
|
+
'<div class="empty"><div class="icon">⚠️</div><div class="text">Failed to load analytics: ' + esc(String(err)) + '</div></div>');
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/* --- Chart builders --- */
|
|
885
|
+
|
|
886
|
+
function buildTimeSeriesChart(ts, metric, gran) {
|
|
887
|
+
gran = gran || 'day';
|
|
888
|
+
if (!ts || ts.length === 0) {
|
|
889
|
+
return '<div class="empty" style="padding:24px"><div class="text">No data in selected range</div></div>';
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// Metric tab switcher
|
|
893
|
+
var h = '<div class="metric-tabs">';
|
|
894
|
+
h += '<div class="metric-tab' + (metric==='sessions'?' active':'') + '" onclick="anSwitchMetric(\\'sessions\\')">Sessions</div>';
|
|
895
|
+
h += '<div class="metric-tab' + (metric==='tokens'?' active':'') + '" onclick="anSwitchMetric(\\'tokens\\')">Tokens</div>';
|
|
896
|
+
h += '<div class="metric-tab' + (metric==='actions'?' active':'') + '" onclick="anSwitchMetric(\\'actions\\')">Actions</div>';
|
|
897
|
+
h += '</div>';
|
|
898
|
+
|
|
899
|
+
// Determine values and max
|
|
900
|
+
var vals = ts.map(function(p) {
|
|
901
|
+
if (metric === 'tokens') return { v1: p.inputTokens, v2: p.outputTokens, total: p.tokens, label: p.date };
|
|
902
|
+
if (metric === 'actions') return { v1: p.actions, v2: 0, total: p.actions, label: p.date };
|
|
903
|
+
return { v1: p.sessions, v2: 0, total: p.sessions, label: p.date };
|
|
904
|
+
});
|
|
905
|
+
var maxVal = Math.max.apply(null, vals.map(function(v){ return v.total; }));
|
|
906
|
+
if (maxVal === 0) maxVal = 1;
|
|
907
|
+
|
|
908
|
+
// Y-axis
|
|
909
|
+
h += '<div class="chart-container">';
|
|
910
|
+
h += '<div class="chart-y-axis">';
|
|
911
|
+
h += '<span>' + fmtNum(maxVal) + '</span>';
|
|
912
|
+
h += '<span>' + fmtNum(Math.round(maxVal * 0.5)) + '</span>';
|
|
913
|
+
h += '<span>0</span>';
|
|
914
|
+
h += '</div>';
|
|
915
|
+
|
|
916
|
+
// Bars
|
|
917
|
+
h += '<div class="chart-main">';
|
|
918
|
+
h += '<div class="chart-bars">';
|
|
919
|
+
var showTokenSplit = metric === 'tokens';
|
|
920
|
+
var barColor1 = metric === 'tokens' ? '#8b5cf6' : (metric === 'actions' ? '#3b82f6' : 'var(--accent)');
|
|
921
|
+
var barColor2 = '#f59e0b';
|
|
922
|
+
|
|
923
|
+
vals.forEach(function(v) {
|
|
924
|
+
var pct = Math.max((v.total / maxVal) * 100, 1);
|
|
925
|
+
var dayLabel;
|
|
926
|
+
if (gran === 'hour') {
|
|
927
|
+
// "2026-03-12 14" → "14:00"
|
|
928
|
+
var hourPart = v.label.length >= 13 ? v.label.slice(11, 13) : v.label;
|
|
929
|
+
dayLabel = hourPart + ':00';
|
|
930
|
+
} else {
|
|
931
|
+
dayLabel = v.label.length > 5 ? v.label.slice(5) : v.label; // MM-DD
|
|
932
|
+
}
|
|
933
|
+
h += '<div class="chart-bar-col">';
|
|
934
|
+
h += '<div class="chart-tooltip">' + v.label + ': ' + fmtNum(v.total);
|
|
935
|
+
if (showTokenSplit) h += ' (in:' + fmtNum(v.v1) + ' out:' + fmtNum(v.v2) + ')';
|
|
936
|
+
h += '</div>';
|
|
937
|
+
|
|
938
|
+
if (showTokenSplit && v.v1 + v.v2 > 0) {
|
|
939
|
+
var pct1 = (v.v1 / maxVal) * 100;
|
|
940
|
+
var pct2 = (v.v2 / maxVal) * 100;
|
|
941
|
+
h += '<div class="chart-bar-stack" style="height:' + pct + '%">';
|
|
942
|
+
h += '<div class="chart-bar-seg" style="height:' + (v.v2 / v.total * 100) + '%;background:' + barColor2 + '"></div>';
|
|
943
|
+
h += '<div class="chart-bar-seg" style="height:' + (v.v1 / v.total * 100) + '%;background:' + barColor1 + '"></div>';
|
|
944
|
+
h += '</div>';
|
|
945
|
+
} else {
|
|
946
|
+
h += '<div class="chart-bar" style="height:' + pct + '%;background:' + barColor1 + '"></div>';
|
|
947
|
+
}
|
|
948
|
+
h += '</div>';
|
|
949
|
+
});
|
|
950
|
+
h += '</div>';
|
|
951
|
+
|
|
952
|
+
// X labels (show max 15 labels)
|
|
953
|
+
h += '<div class="chart-x-labels">';
|
|
954
|
+
var step = Math.max(1, Math.ceil(vals.length / 15));
|
|
955
|
+
vals.forEach(function(v, i) {
|
|
956
|
+
var xLabel;
|
|
957
|
+
if (gran === 'hour') {
|
|
958
|
+
var hp = v.label.length >= 13 ? v.label.slice(11, 13) : v.label;
|
|
959
|
+
xLabel = hp + ':00';
|
|
960
|
+
} else {
|
|
961
|
+
xLabel = v.label.length > 5 ? v.label.slice(5) : v.label;
|
|
962
|
+
}
|
|
963
|
+
h += '<span>' + (i % step === 0 ? xLabel : '') + '</span>';
|
|
964
|
+
});
|
|
965
|
+
h += '</div>';
|
|
966
|
+
h += '</div></div>'; // chart-main, chart-container
|
|
967
|
+
|
|
968
|
+
// Legend for token split
|
|
969
|
+
if (showTokenSplit) {
|
|
970
|
+
h += '<div class="chart-legend">';
|
|
971
|
+
h += '<div class="chart-legend-item"><div class="chart-legend-dot" style="background:#8b5cf6"></div>Input tokens</div>';
|
|
972
|
+
h += '<div class="chart-legend-item"><div class="chart-legend-dot" style="background:#f59e0b"></div>Output tokens</div>';
|
|
973
|
+
h += '</div>';
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
return h;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
function buildModelUsageChart(mu, tbm) {
|
|
980
|
+
if (!mu || mu.length === 0) {
|
|
981
|
+
return '<div class="empty" style="padding:24px"><div class="text">No model data</div></div>';
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
var maxTokens = Math.max.apply(null, mu.map(function(m){ return m.inputTokens + m.outputTokens; }));
|
|
985
|
+
if (maxTokens === 0) maxTokens = 1;
|
|
986
|
+
|
|
987
|
+
var h = '<div class="hbar-list">';
|
|
988
|
+
mu.forEach(function(m) {
|
|
989
|
+
var total = m.inputTokens + m.outputTokens;
|
|
990
|
+
var pctInp = (m.inputTokens / maxTokens) * 100;
|
|
991
|
+
var pctOutp = (m.outputTokens / maxTokens) * 100;
|
|
992
|
+
var shortModel = m.model.length > 20 ? m.model.slice(m.model.indexOf('/') + 1) : m.model;
|
|
993
|
+
|
|
994
|
+
h += '<div class="hbar-row">';
|
|
995
|
+
h += '<div class="hbar-label" title="' + esc(m.model) + '">' + esc(shortModel) + '</div>';
|
|
996
|
+
h += '<div class="hbar-track">';
|
|
997
|
+
h += '<div class="hbar-fill" style="width:' + pctInp + '%;background:#8b5cf6"></div>';
|
|
998
|
+
h += '<div class="hbar-fill" style="width:' + pctOutp + '%;background:#f59e0b"></div>';
|
|
999
|
+
h += '</div>';
|
|
1000
|
+
h += '<div class="hbar-value">' + fmtNum(total) + '</div>';
|
|
1001
|
+
h += '</div>';
|
|
1002
|
+
});
|
|
1003
|
+
h += '</div>';
|
|
1004
|
+
|
|
1005
|
+
h += '<div class="chart-legend" style="margin-top:16px">';
|
|
1006
|
+
h += '<div class="chart-legend-item"><div class="chart-legend-dot" style="background:#8b5cf6"></div>Input</div>';
|
|
1007
|
+
h += '<div class="chart-legend-item"><div class="chart-legend-dot" style="background:#f59e0b"></div>Output</div>';
|
|
1008
|
+
h += '</div>';
|
|
1009
|
+
|
|
1010
|
+
// Model details table
|
|
1011
|
+
h += '<table class="an-table" style="margin-top:16px">';
|
|
1012
|
+
h += '<tr><th>Model</th><th>Calls</th><th>Avg Latency</th><th>Tokens</th></tr>';
|
|
1013
|
+
mu.forEach(function(m) {
|
|
1014
|
+
var shortModel = m.model.length > 20 ? m.model.slice(m.model.indexOf('/') + 1) : m.model;
|
|
1015
|
+
h += '<tr>';
|
|
1016
|
+
h += '<td title="' + esc(m.model) + '">' + esc(shortModel) + '</td>';
|
|
1017
|
+
h += '<td class="mono">' + m.calls + '</td>';
|
|
1018
|
+
h += '<td class="mono">' + fmtDur(m.avgLatency) + '</td>';
|
|
1019
|
+
h += '<td class="mono">' + fmtNum(m.inputTokens + m.outputTokens) + '</td>';
|
|
1020
|
+
h += '</tr>';
|
|
1021
|
+
});
|
|
1022
|
+
h += '</table>';
|
|
1023
|
+
|
|
1024
|
+
return h;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
function buildActionDistribution(ad) {
|
|
1028
|
+
var keys = Object.keys(ad);
|
|
1029
|
+
if (keys.length === 0) {
|
|
1030
|
+
return '<div class="empty" style="padding:24px"><div class="text">No action data</div></div>';
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
var total = keys.reduce(function(s, k) { return s + ad[k]; }, 0);
|
|
1034
|
+
var COLORS = ['#8b5cf6','#f59e0b','#3b82f6','#10b981','#ef4444','#06b6d4','#f97316','#a855f7','#22c55e','#64748b','#d946ef','#84cc16'];
|
|
1035
|
+
|
|
1036
|
+
// Donut chart using SVG
|
|
1037
|
+
var h = '<div class="donut-wrap">';
|
|
1038
|
+
var radius = 55;
|
|
1039
|
+
var circumference = 2 * Math.PI * radius;
|
|
1040
|
+
h += '<div style="position:relative;width:140px;height:140px">';
|
|
1041
|
+
h += '<svg class="donut-svg" viewBox="0 0 140 140">';
|
|
1042
|
+
var offset = 0;
|
|
1043
|
+
keys.forEach(function(k, i) {
|
|
1044
|
+
var pct = ad[k] / total;
|
|
1045
|
+
var dashLen = pct * circumference;
|
|
1046
|
+
var color = COLORS[i % COLORS.length];
|
|
1047
|
+
h += '<circle class="donut-circle" cx="70" cy="70" r="' + radius + '" stroke="' + color + '" stroke-width="16" ';
|
|
1048
|
+
h += 'stroke-dasharray="' + dashLen + ' ' + (circumference - dashLen) + '" ';
|
|
1049
|
+
h += 'stroke-dashoffset="' + (-offset) + '"/>';
|
|
1050
|
+
offset += dashLen;
|
|
1051
|
+
});
|
|
1052
|
+
h += '</svg>';
|
|
1053
|
+
h += '<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center">';
|
|
1054
|
+
h += '<div class="donut-center">' + fmtNum(total) + '</div>';
|
|
1055
|
+
h += '<div style="font-size:10px;color:var(--muted)">total</div>';
|
|
1056
|
+
h += '</div></div>';
|
|
1057
|
+
|
|
1058
|
+
// Legend
|
|
1059
|
+
h += '<div class="donut-legend">';
|
|
1060
|
+
keys.slice(0, 10).forEach(function(k, i) {
|
|
1061
|
+
var pct = Math.round(ad[k] / total * 100);
|
|
1062
|
+
h += '<div class="donut-legend-item">';
|
|
1063
|
+
h += '<div class="donut-legend-dot" style="background:' + COLORS[i % COLORS.length] + '"></div>';
|
|
1064
|
+
h += '<span>' + typeLabel(k) + '</span>';
|
|
1065
|
+
h += '<span class="donut-legend-val">' + ad[k] + ' (' + pct + '%)</span>';
|
|
1066
|
+
h += '</div>';
|
|
1067
|
+
});
|
|
1068
|
+
if (keys.length > 10) {
|
|
1069
|
+
h += '<div class="donut-legend-item" style="color:var(--muted)">... +' + (keys.length - 10) + ' more</div>';
|
|
1070
|
+
}
|
|
1071
|
+
h += '</div>';
|
|
1072
|
+
h += '</div>';
|
|
1073
|
+
|
|
1074
|
+
return h;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
function buildAgentsTable(ta) {
|
|
1078
|
+
if (!ta || ta.length === 0) {
|
|
1079
|
+
return '<div class="empty" style="padding:24px"><div class="text">No agent data</div></div>';
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
var maxSess = Math.max.apply(null, ta.map(function(a){ return a.sessions; }));
|
|
1083
|
+
var h = '<table class="an-table">';
|
|
1084
|
+
h += '<tr><th>Agent</th><th>Sessions</th><th>Actions</th><th>Tokens</th></tr>';
|
|
1085
|
+
ta.forEach(function(a) {
|
|
1086
|
+
h += '<tr>';
|
|
1087
|
+
h += '<td>🤖 ' + esc(a.agent) + '</td>';
|
|
1088
|
+
h += '<td class="mono">' + a.sessions + '</td>';
|
|
1089
|
+
h += '<td class="mono">' + fmtNum(a.actions) + '</td>';
|
|
1090
|
+
h += '<td class="mono">' + fmtNum(a.tokens) + '</td>';
|
|
1091
|
+
h += '</tr>';
|
|
1092
|
+
});
|
|
1093
|
+
h += '</table>';
|
|
1094
|
+
|
|
1095
|
+
return h;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/* Analytics event handlers */
|
|
1099
|
+
window.anToggleTimeMenu = function(e) {
|
|
1100
|
+
e.stopPropagation();
|
|
1101
|
+
var menu = document.getElementById('an-time-menu');
|
|
1102
|
+
if (menu) menu.classList.toggle('open');
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
window.anSelectTime = function(key) {
|
|
1106
|
+
anTimeRange = key;
|
|
1107
|
+
var menu = document.getElementById('an-time-menu');
|
|
1108
|
+
if (menu) menu.classList.remove('open');
|
|
1109
|
+
renderAnalytics();
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
window.anSwitchMetric = function(metric) {
|
|
1113
|
+
anMetricTab = metric;
|
|
1114
|
+
renderAnalytics();
|
|
1115
|
+
};
|
|
1116
|
+
|
|
683
1117
|
/* ================================================================ */
|
|
684
1118
|
/* Security tab — alert badge count */
|
|
685
1119
|
/* ================================================================ */
|
package/dist/web/ui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/web/ui.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAEH,gCAmBC;AAnBD,SAAgB,UAAU;IACxB,OAAO,mBAAmB;QAC5B,oBAAoB;QACpB,UAAU;QACV,0BAA0B;QAC1B,0EAA0E;QAC1E,
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/web/ui.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAEH,gCAmBC;AAnBD,SAAgB,UAAU;IACxB,OAAO,mBAAmB;QAC5B,oBAAoB;QACpB,UAAU;QACV,0BAA0B;QAC1B,0EAA0E;QAC1E,yCAAyC;QACzC,mDAAmD;QACnD,WAAW;QACX,GAAG;QACH,YAAY;QACZ,WAAW;QACX,UAAU;QACV,wBAAwB;QACxB,YAAY;QACZ,SAAS;QACT,aAAa;QACb,WAAW;QACX,SAAS,CAAC;AACV,CAAC;AAED,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE,MAAM,GAAG,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6VX,CAAC;AAEF,wEAAwE;AACxE,wEAAwE;AACxE,wEAAwE;AAExE,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA61CjB,CAAC"}
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "
|
|
3
|
-
"name": "openclaw-
|
|
4
|
-
"version": "1.0.
|
|
5
|
-
"description": "Conversation model action
|
|
2
|
+
"id": "openclaw-observability",
|
|
3
|
+
"name": "openclaw-observability",
|
|
4
|
+
"version": "1.0.2",
|
|
5
|
+
"description": "Conversation model action observability plugin — full-chain traceability with built-in visualization",
|
|
6
6
|
"entry": "dist/index.js",
|
|
7
7
|
"slots": [
|
|
8
8
|
"before_model_resolve",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"duckdb.path": {
|
|
35
35
|
"label": "Local DB File Path",
|
|
36
|
-
"placeholder": "~/.openclaw/
|
|
36
|
+
"placeholder": "~/.openclaw/observability.duckdb",
|
|
37
37
|
"help": "Local database file path (only effective in local mode)"
|
|
38
38
|
},
|
|
39
39
|
"mysql.host": {
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
},
|
|
58
58
|
"mysql.database": {
|
|
59
59
|
"label": "Database Name",
|
|
60
|
-
"placeholder": "
|
|
61
|
-
"help": "
|
|
60
|
+
"placeholder": "openclaw_observability",
|
|
61
|
+
"help": "Database name (auto-created if not exists)"
|
|
62
62
|
},
|
|
63
63
|
"buffer.batchSize": {
|
|
64
64
|
"label": "Batch Size",
|
|
@@ -68,8 +68,8 @@
|
|
|
68
68
|
},
|
|
69
69
|
"buffer.flushIntervalMs": {
|
|
70
70
|
"label": "Flush Interval (ms)",
|
|
71
|
-
"placeholder": "
|
|
72
|
-
"help": "Flush interval in milliseconds (default:
|
|
71
|
+
"placeholder": "5000",
|
|
72
|
+
"help": "Flush interval in milliseconds (default: 5s)",
|
|
73
73
|
"advanced": true
|
|
74
74
|
},
|
|
75
75
|
"redaction.enabled": {
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
},
|
|
84
84
|
"security.enabled": {
|
|
85
85
|
"label": "Enable Security Scanning",
|
|
86
|
-
"help": "Enable security
|
|
86
|
+
"help": "Enable security scanning (secret leaks, dangerous commands, prompt injection, etc.)"
|
|
87
87
|
},
|
|
88
88
|
"security.rules.secretLeakage": {
|
|
89
89
|
"label": "Secret Leakage Detection",
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
"properties": {
|
|
127
127
|
"path": {
|
|
128
128
|
"type": "string",
|
|
129
|
-
"default": "~/.openclaw/
|
|
129
|
+
"default": "~/.openclaw/observability.duckdb"
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
},
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
},
|
|
153
153
|
"database": {
|
|
154
154
|
"type": "string",
|
|
155
|
-
"default": "
|
|
155
|
+
"default": "openclaw_observability"
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
},
|
|
@@ -168,7 +168,7 @@
|
|
|
168
168
|
},
|
|
169
169
|
"flushIntervalMs": {
|
|
170
170
|
"type": "number",
|
|
171
|
-
"default":
|
|
171
|
+
"default": 5000,
|
|
172
172
|
"minimum": 1000,
|
|
173
173
|
"maximum": 300000
|
|
174
174
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-observability",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "OpenClaw
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "OpenClaw observability plugin — records all conversation model actions into DuckDB/MySQL for traceability, with built-in visualization",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"openclaw": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"dist/",
|
|
14
|
-
"openclaw.plugin.json"
|
|
14
|
+
"openclaw.plugin.json",
|
|
15
|
+
"README.md"
|
|
15
16
|
],
|
|
16
17
|
"scripts": {
|
|
17
18
|
"build": "tsc",
|
|
@@ -23,11 +24,10 @@
|
|
|
23
24
|
"openclaw",
|
|
24
25
|
"plugin",
|
|
25
26
|
"observability",
|
|
26
|
-
"
|
|
27
|
+
"tracing",
|
|
27
28
|
"duckdb",
|
|
28
29
|
"mysql",
|
|
29
30
|
"trace",
|
|
30
|
-
"langfuse",
|
|
31
31
|
"security"
|
|
32
32
|
],
|
|
33
33
|
"license": "MIT",
|