@steadwing/openalerts 0.2.1 → 0.2.3
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 +78 -47
- package/dist/core/engine.d.ts +7 -0
- package/dist/core/engine.js +50 -6
- package/dist/core/evaluator.js +19 -2
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +2 -0
- package/dist/core/llm-enrichment.d.ts +21 -0
- package/dist/core/llm-enrichment.js +180 -0
- package/dist/core/rules.js +63 -25
- package/dist/core/types.d.ts +6 -1
- package/dist/core/types.js +1 -1
- package/dist/index.js +9 -4
- package/dist/plugin/adapter.d.ts +14 -2
- package/dist/plugin/adapter.js +81 -9
- package/dist/plugin/commands.js +2 -21
- package/dist/plugin/dashboard-html.js +111 -97
- package/dist/plugin/dashboard-routes.js +43 -5
- package/dist/plugin/log-bridge.js +27 -0
- package/package.json +4 -1
|
@@ -13,22 +13,22 @@ export function getDashboardHtml() {
|
|
|
13
13
|
<title>OpenAlerts Monitor</title>
|
|
14
14
|
<style>
|
|
15
15
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
16
|
-
body{font-family:'SF Mono','Cascadia Code','Consolas',monospace;background:#0d1117;color:#c9d1d9;font-size:
|
|
16
|
+
body{font-family:'SF Mono','Cascadia Code','Consolas',monospace;background:#0d1117;color:#c9d1d9;font-size:14px;overflow:hidden;height:100vh}
|
|
17
17
|
.grid{display:grid;grid-template-rows:auto auto 1fr;height:100vh}
|
|
18
18
|
|
|
19
19
|
/* ── Top bar ──────────────────── */
|
|
20
20
|
.topbar{background:#161b22;border-bottom:1px solid #30363d;padding:8px 16px;display:flex;align-items:center;gap:16px;flex-wrap:wrap}
|
|
21
|
-
.topbar h1{font-size:
|
|
21
|
+
.topbar h1{font-size:16px;font-weight:600;color:#f0f6fc;letter-spacing:0.5px}
|
|
22
22
|
.dot{width:7px;height:7px;border-radius:50%;display:inline-block;margin-right:4px}
|
|
23
23
|
.dot.live{background:#3fb950;animation:pulse 2s infinite}
|
|
24
24
|
.dot.dead{background:#f85149}
|
|
25
25
|
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}}
|
|
26
|
-
.stat{color:#8b949e;font-size:
|
|
26
|
+
.stat{color:#8b949e;font-size:12px}
|
|
27
27
|
.stat b{color:#c9d1d9;font-weight:500}
|
|
28
28
|
|
|
29
29
|
/* ── Tabs ──────────────────── */
|
|
30
30
|
.tabbar{background:#161b22;border-bottom:1px solid #30363d;display:flex}
|
|
31
|
-
.tab{padding:7px 18px;font-size:
|
|
31
|
+
.tab{padding:7px 18px;font-size:13px;font-weight:600;color:#8b949e;cursor:pointer;border-bottom:2px solid transparent;transition:all 0.15s}
|
|
32
32
|
.tab:hover{color:#c9d1d9;background:#1c2129}
|
|
33
33
|
.tab.active{color:#58a6ff;border-bottom-color:#58a6ff}
|
|
34
34
|
.tab-content{display:none;overflow:hidden;flex:1}
|
|
@@ -39,7 +39,7 @@ export function getDashboardHtml() {
|
|
|
39
39
|
.activity-panels{display:grid;grid-template-columns:1fr 280px;gap:0;overflow:hidden;flex:1}
|
|
40
40
|
@media(max-width:900px){.activity-panels{grid-template-columns:1fr}}
|
|
41
41
|
.panel{display:flex;flex-direction:column;overflow:hidden}
|
|
42
|
-
.panel-header{background:#161b22;padding:6px 12px;font-size:
|
|
42
|
+
.panel-header{background:#161b22;padding:6px 12px;font-size:12px;font-weight:600;color:#8b949e;text-transform:uppercase;letter-spacing:0.8px;border-bottom:1px solid #30363d;flex-shrink:0;display:flex;align-items:center;justify-content:space-between}
|
|
43
43
|
.panel:first-child{border-right:1px solid #30363d}
|
|
44
44
|
.scroll{flex:1;overflow-y:auto}
|
|
45
45
|
.scroll::-webkit-scrollbar{width:5px}
|
|
@@ -65,16 +65,16 @@ export function getDashboardHtml() {
|
|
|
65
65
|
.flow-body.shut{max-height:0!important;overflow:hidden}
|
|
66
66
|
|
|
67
67
|
/* ── Event/Log row ──────────────────── */
|
|
68
|
-
.row{padding:3px 10px 3px 24px;border-top:1px solid #0d1117;font-size:
|
|
68
|
+
.row{padding:3px 10px 3px 24px;border-top:1px solid #0d1117;font-size:12px;line-height:1.5;animation:fi 0.15s ease}
|
|
69
69
|
.row:hover{background:#0d1117}
|
|
70
70
|
.row.standalone{padding-left:10px;border-bottom:1px solid #21262d;border-top:none}
|
|
71
71
|
.row.deep{padding-left:38px}
|
|
72
72
|
|
|
73
73
|
/* OpenAlerts event row */
|
|
74
74
|
.row .r-main{display:flex;align-items:center;gap:5px}
|
|
75
|
-
.r-time{color:#484f58;font-size:
|
|
76
|
-
.r-icon{width:14px;text-align:center;flex-shrink:0;font-size:
|
|
77
|
-
.r-type{font-weight:600;min-width:
|
|
75
|
+
.r-time{color:#484f58;font-size:11px;min-width:55px;flex-shrink:0}
|
|
76
|
+
.r-icon{width:14px;text-align:center;flex-shrink:0;font-size:12px}
|
|
77
|
+
.r-type{font-weight:600;min-width:110px;font-size:11px;flex-shrink:0}
|
|
78
78
|
.r-type.llm{color:#58a6ff} .r-type.tool{color:#bc8cff} .r-type.agent{color:#3fb950}
|
|
79
79
|
.r-type.session{color:#d29922} .r-type.infra{color:#f85149} .r-type.custom{color:#8b949e}
|
|
80
80
|
.r-type.watchdog{color:#6e7681}
|
|
@@ -83,10 +83,10 @@ export function getDashboardHtml() {
|
|
|
83
83
|
.r-oc.error{background:#3d1a1a;color:#f85149}
|
|
84
84
|
.r-oc.timeout{background:#3d2e1a;color:#d29922}
|
|
85
85
|
.r-pills{display:flex;gap:4px;flex-wrap:wrap;margin-left:auto;align-items:center}
|
|
86
|
-
.p{font-size:
|
|
86
|
+
.p{font-size:10px;background:#21262d;padding:0 5px;border-radius:3px;white-space:nowrap}
|
|
87
87
|
.p.t{color:#bc8cff;background:#2a1f3d} .p.d{color:#d29922} .p.tk{color:#58a6ff}
|
|
88
88
|
.p.q{color:#f0883e} .p.m{color:#8b949e} .p.ch{color:#d2a8ff} .p.s{color:#6e7681;font-size:8px}
|
|
89
|
-
.r-det{padding:1px 0 1px 70px;color:#6e7681;font-size:
|
|
89
|
+
.r-det{padding:1px 0 1px 70px;color:#6e7681;font-size:11px}
|
|
90
90
|
.r-det .err{color:#f85149} .r-det .dim{color:#484f58} .r-det .sc{color:#d29922}
|
|
91
91
|
|
|
92
92
|
/* OpenClaw log row */
|
|
@@ -101,25 +101,25 @@ export function getDashboardHtml() {
|
|
|
101
101
|
@keyframes fi{from{opacity:0;transform:translateY(-2px)}to{opacity:1;transform:none}}
|
|
102
102
|
|
|
103
103
|
/* ── Alerts panel ──────────────────── */
|
|
104
|
-
.al{padding:6px 10px;border-bottom:1px solid #21262d;font-size:
|
|
105
|
-
.al-sev{font-weight:700;text-transform:uppercase;font-size:
|
|
104
|
+
.al{padding:6px 10px;border-bottom:1px solid #21262d;font-size:12px;animation:fi 0.2s}
|
|
105
|
+
.al-sev{font-weight:700;text-transform:uppercase;font-size:10px;letter-spacing:0.4px}
|
|
106
106
|
.al-sev.error{color:#f85149} .al-sev.warn{color:#d29922} .al-sev.critical{color:#ff7b72} .al-sev.info{color:#58a6ff}
|
|
107
|
-
.al-title{color:#c9d1d9;margin-top:1px;font-size:
|
|
108
|
-
.al-detail{color:#8b949e;margin-top:1px;font-size:
|
|
109
|
-
.al-time{color:#484f58;font-size:
|
|
107
|
+
.al-title{color:#c9d1d9;margin-top:1px;font-size:12px}
|
|
108
|
+
.al-detail{color:#8b949e;margin-top:1px;font-size:11px}
|
|
109
|
+
.al-time{color:#484f58;font-size:11px;margin-top:1px}
|
|
110
110
|
|
|
111
111
|
/* ── Rules ──────────────────── */
|
|
112
112
|
.rules{border-top:1px solid #30363d;padding:6px 10px;flex-shrink:0}
|
|
113
|
-
.rules h3{font-size:
|
|
114
|
-
.rl{display:flex;align-items:center;gap:5px;font-size:
|
|
113
|
+
.rules h3{font-size:12px;color:#8b949e;text-transform:uppercase;letter-spacing:0.7px;margin-bottom:4px}
|
|
114
|
+
.rl{display:flex;align-items:center;gap:5px;font-size:12px;padding:1px 0}
|
|
115
115
|
.rl-d{width:5px;height:5px;border-radius:50%;flex-shrink:0}
|
|
116
116
|
.rl-d.ok{background:#3fb950} .rl-d.fired{background:#f85149;animation:pulse 1s infinite}
|
|
117
|
-
.rl-s{color:#8b949e;margin-left:auto;font-size:
|
|
117
|
+
.rl-s{color:#8b949e;margin-left:auto;font-size:11px}
|
|
118
118
|
|
|
119
119
|
/* ── Logs tab ──────────────────── */
|
|
120
120
|
.logs-t{flex:1;display:flex;flex-direction:column;overflow:hidden}
|
|
121
|
-
.log-bar{background:#161b22;padding:8px 12px;border-bottom:1px solid #30363d;display:flex;align-items:center;gap:10px;flex-wrap:wrap;flex-shrink:0;font-size:
|
|
122
|
-
.log-bar select,.log-bar input{background:#0d1117;border:1px solid #30363d;color:#c9d1d9;font-family:inherit;font-size:
|
|
121
|
+
.log-bar{background:#161b22;padding:8px 12px;border-bottom:1px solid #30363d;display:flex;align-items:center;gap:10px;flex-wrap:wrap;flex-shrink:0;font-size:12px}
|
|
122
|
+
.log-bar select,.log-bar input{background:#0d1117;border:1px solid #30363d;color:#c9d1d9;font-family:inherit;font-size:11px;padding:3px 6px;border-radius:3px}
|
|
123
123
|
.log-bar input[type="text"]{min-width:180px}
|
|
124
124
|
.log-bar button{background:#21262d;border:1px solid #30363d;color:#c9d1d9;font-family:inherit;font-size:10px;padding:3px 10px;border-radius:3px;cursor:pointer;transition:all 0.12s}
|
|
125
125
|
.log-bar button:hover{background:#30363d;border-color:#484f58}
|
|
@@ -131,31 +131,31 @@ export function getDashboardHtml() {
|
|
|
131
131
|
.log-filters label{font-size:10px}
|
|
132
132
|
.log-truncate{background:#3d2e1a;border:1px solid #d29922;color:#d29922;padding:4px 10px;font-size:10px;border-radius:3px;margin:8px 12px;display:none}
|
|
133
133
|
.log-truncate.show{display:block}
|
|
134
|
-
.log-list{flex:1;overflow-y:auto;font-size:
|
|
134
|
+
.log-list{flex:1;overflow-y:auto;font-size:12px}
|
|
135
135
|
.log-e{padding:2px 12px;border-bottom:1px solid #161b22;display:flex;gap:6px;align-items:baseline;position:relative}
|
|
136
136
|
.log-e:hover{background:#161b22}
|
|
137
137
|
.log-e:hover .log-copy{opacity:1}
|
|
138
|
-
.log-ts{color:#484f58;font-size:
|
|
139
|
-
.log-lv{font-size:
|
|
138
|
+
.log-ts{color:#484f58;font-size:11px;min-width:70px;flex-shrink:0}
|
|
139
|
+
.log-lv{font-size:10px;font-weight:700;min-width:42px;flex-shrink:0}
|
|
140
140
|
.log-lv.TRACE{color:#6e7681} .log-lv.DEBUG{color:#8b949e} .log-lv.INFO{color:#58a6ff} .log-lv.WARN{color:#d29922} .log-lv.ERROR{color:#f85149} .log-lv.FATAL{color:#ff7b72;background:#3d1a1a;padding:1px 3px;border-radius:2px}
|
|
141
|
-
.log-su{color:#bc8cff;font-size:
|
|
142
|
-
.log-mg{color:#c9d1d9;word-break:break-all;flex:1}
|
|
141
|
+
.log-su{color:#bc8cff;font-size:11px;min-width:110px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
142
|
+
.log-mg{color:#c9d1d9;font-size:12px;word-break:break-all;flex:1}
|
|
143
143
|
.log-copy{position:absolute;right:8px;top:4px;font-size:9px;color:#484f58;cursor:pointer;border:1px solid #30363d;background:#161b22;padding:1px 4px;border-radius:2px;font-family:inherit;opacity:0;transition:opacity 0.12s}
|
|
144
144
|
.log-copy:hover{color:#c9d1d9;border-color:#484f58}
|
|
145
145
|
|
|
146
146
|
/* ── Health tab ──────────────────── */
|
|
147
147
|
.health-t{flex:1;overflow-y:auto;padding:14px}
|
|
148
148
|
.h-sec{margin-bottom:16px}
|
|
149
|
-
.h-sec h3{font-size:
|
|
149
|
+
.h-sec h3{font-size:13px;color:#8b949e;text-transform:uppercase;letter-spacing:0.7px;margin-bottom:6px;padding-bottom:3px;border-bottom:1px solid #21262d}
|
|
150
150
|
.h-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:6px}
|
|
151
151
|
.h-card{background:#161b22;border:1px solid #21262d;border-radius:5px;padding:8px 12px}
|
|
152
|
-
.h-card .lb{color:#8b949e;font-size:
|
|
153
|
-
.h-card .vl{color:#c9d1d9;font-size:
|
|
152
|
+
.h-card .lb{color:#8b949e;font-size:11px;margin-bottom:2px}
|
|
153
|
+
.h-card .vl{color:#c9d1d9;font-size:15px;font-weight:600}
|
|
154
154
|
.h-card .vl.ok{color:#3fb950} .h-card .vl.bad{color:#f85149}
|
|
155
155
|
.h-tbl{width:100%;border-collapse:collapse}
|
|
156
|
-
.h-tbl td{padding:
|
|
156
|
+
.h-tbl td{padding:4px 8px;font-size:13px;border-bottom:1px solid #161b22}
|
|
157
157
|
.h-tbl td:first-child{color:#8b949e;width:140px}
|
|
158
|
-
.h-tbl th{padding:
|
|
158
|
+
.h-tbl th{padding:4px 8px;font-size:12px;color:#8b949e;text-align:left;border-bottom:1px solid #30363d;font-weight:600}
|
|
159
159
|
|
|
160
160
|
/* ── Expandable event detail ──────────────────── */
|
|
161
161
|
.row.expandable{cursor:pointer}
|
|
@@ -245,9 +245,10 @@ export function getDashboardHtml() {
|
|
|
245
245
|
</div>
|
|
246
246
|
<button id="lR" title="Refresh logs (Ctrl+R)">↻ Refresh</button>
|
|
247
247
|
<button id="lE" title="Export filtered logs">⬇ Export</button>
|
|
248
|
+
<label title="Fetch all logs (not just recent) when searching or filtering"><input type="checkbox" id="lAll"> All logs</label>
|
|
248
249
|
<label style="margin-left:auto" title="Auto-scroll to new logs"><input type="checkbox" id="lA" checked> Auto-follow</label>
|
|
249
250
|
</div>
|
|
250
|
-
<div class="log-truncate" id="lT">⚠ Logs truncated
|
|
251
|
+
<div class="log-truncate" id="lT">⚠ Logs truncated — showing most recent entries only. Check "All logs" to search across the full log file.</div>
|
|
251
252
|
<div class="log-list" id="logList"><div class="empty-msg">Loading...</div></div>
|
|
252
253
|
</div>
|
|
253
254
|
</div>
|
|
@@ -271,14 +272,6 @@ export function getDashboardHtml() {
|
|
|
271
272
|
<h3>Alert Rules Status</h3>
|
|
272
273
|
<div id="dbRules"></div>
|
|
273
274
|
</div>
|
|
274
|
-
<div class="h-sec">
|
|
275
|
-
<h3>Circuit Breakers</h3>
|
|
276
|
-
<div id="dbCircuit" style="font-size:11px"></div>
|
|
277
|
-
</div>
|
|
278
|
-
<div class="h-sec">
|
|
279
|
-
<h3>Task Timeouts</h3>
|
|
280
|
-
<div id="dbTasks" style="font-size:11px"></div>
|
|
281
|
-
</div>
|
|
282
275
|
</div>
|
|
283
276
|
</div>
|
|
284
277
|
</div>
|
|
@@ -293,6 +286,30 @@ export function getDashboardHtml() {
|
|
|
293
286
|
function esc(s){var d=document.createElement('div');d.textContent=s;return d.innerHTML}
|
|
294
287
|
function cat(t){if(!t)return'custom';var p=t.split('.')[0];return['llm','tool','agent','session','infra','watchdog'].indexOf(p)>=0?p:'custom'}
|
|
295
288
|
function fT(ts){if(!ts)return'';var d=typeof ts==='number'?new Date(ts):new Date(ts);return d.toLocaleTimeString('en-US',{hour12:false,hour:'2-digit',minute:'2-digit',second:'2-digit'})}
|
|
289
|
+
|
|
290
|
+
// ─── Human-readable event labels ──────────────────────
|
|
291
|
+
var EVT_LABELS={
|
|
292
|
+
'llm.call':['\\u{1F916}','LLM Called'],
|
|
293
|
+
'llm.error':['\\u26A0','LLM Failed'],
|
|
294
|
+
'llm.token_usage':['\\u{1F4CA}','Token Usage'],
|
|
295
|
+
'tool.call':['\\u{1F527}','Tool Executed'],
|
|
296
|
+
'tool.error':['\\u{1F527}','Tool Failed'],
|
|
297
|
+
'agent.start':['\\u25B6','Agent Started'],
|
|
298
|
+
'agent.end':['\\u23F9','Agent Finished'],
|
|
299
|
+
'agent.error':['\\u{1F6A8}','Agent Error'],
|
|
300
|
+
'agent.stuck':['\\u23F3','Agent Stuck'],
|
|
301
|
+
'session.start':['\\u{1F4AC}','Session Started'],
|
|
302
|
+
'session.end':['\\u{1F3C1}','Session Ended'],
|
|
303
|
+
'session.stuck':['\\u23F3','Session Stuck'],
|
|
304
|
+
'infra.error':['\\u{1F6A8}','Infra Error'],
|
|
305
|
+
'infra.heartbeat':['\\u2764','Heartbeat'],
|
|
306
|
+
'infra.queue_depth':['\\u{1F4E6}','Queue Depth'],
|
|
307
|
+
'watchdog.tick':['\\u{1F440}','Health Check'],
|
|
308
|
+
'msg.delivered':['\\u2709','Message Sent'],
|
|
309
|
+
'msg.in':['\\u{1F4E5}','Message Received'],
|
|
310
|
+
'msg.out':['\\u{1F4E4}','Message Sending']
|
|
311
|
+
};
|
|
312
|
+
function friendlyLabel(ft){return EVT_LABELS[ft]||['\\u2022',ft]}
|
|
296
313
|
function fISO(ts){if(!ts)return'';return new Date(typeof ts==='number'?ts:Date.parse(ts)).toISOString()}
|
|
297
314
|
function fD(ms){if(ms==null)return'';if(ms<1000)return ms+'ms';if(ms<60000)return(ms/1000).toFixed(1)+'s';return Math.floor(ms/60000)+'m '+Math.round((ms%60000)/1000)+'s'}
|
|
298
315
|
function fU(ms){var s=Math.floor(ms/1000),m=Math.floor(s/60),h=Math.floor(m/60);return h>0?h+'h '+m%60+'m':m+'m '+s%60+'s'}
|
|
@@ -317,7 +334,7 @@ export function getDashboardHtml() {
|
|
|
317
334
|
var c=document.createElement('div');c.className='flow active';
|
|
318
335
|
var hdr=document.createElement('div');hdr.className='flow-hdr';
|
|
319
336
|
var short=sid.length>20?sid.slice(0,8)+'..'+sid.slice(-4):sid;
|
|
320
|
-
hdr.innerHTML='<span class="flow-arr">\\u25BC</span><span class="flow-lbl" title="'+esc(sid)+'">Session '+esc(short)+'</span><span class="flow-badge active" data-r="st">
|
|
337
|
+
hdr.innerHTML='<span class="flow-arr">\\u25BC</span><span class="flow-lbl" title="'+esc(sid)+'">Session '+esc(short)+'</span><span class="flow-badge active" data-r="st">Running</span><span class="flow-info" data-r="info">'+fT(ev.ts||ev.tsMs)+'</span>';
|
|
321
338
|
var summary=document.createElement('div');summary.className='flow-summary';summary.setAttribute('data-r','summary');
|
|
322
339
|
var body=document.createElement('div');body.className='flow-body';
|
|
323
340
|
hdr.addEventListener('click',function(){
|
|
@@ -337,26 +354,27 @@ export function getDashboardHtml() {
|
|
|
337
354
|
function updFlow(f,st){
|
|
338
355
|
f.st=st;f.el.className='flow '+st;
|
|
339
356
|
var sEl=f.hdr.querySelector('[data-r="st"]');
|
|
340
|
-
|
|
357
|
+
var stLabels={active:'Running',done:'Completed',error:'Failed'};
|
|
358
|
+
if(sEl){sEl.className='flow-badge '+st;sEl.textContent=stLabels[st]||st}
|
|
341
359
|
var iEl=f.hdr.querySelector('[data-r="info"]');
|
|
342
360
|
if(iEl){
|
|
343
361
|
var ps=[f.n+' events'];
|
|
344
362
|
if(f.dur>0)ps.push(fD(f.dur));
|
|
345
|
-
if(f.tok>0)ps.push(f.tok+'
|
|
363
|
+
if(f.tok>0)ps.push(f.tok+' tokens');
|
|
346
364
|
if(f.cost>0)ps.push('$'+f.cost.toFixed(4));
|
|
347
|
-
if(f.tools>0)ps.push(f.tools+' tools');
|
|
348
|
-
if(f.llms>0)ps.push(f.llms+'
|
|
365
|
+
if(f.tools>0)ps.push(f.tools+(f.tools===1?' tool':' tools'));
|
|
366
|
+
if(f.llms>0)ps.push(f.llms+(f.llms===1?' LLM call':' LLM calls'));
|
|
349
367
|
iEl.textContent=ps.join(' \\u00B7 ');
|
|
350
368
|
}
|
|
351
369
|
// Update summary bar
|
|
352
370
|
var sh='';
|
|
353
371
|
sh+='<span>Events: <span class="fs-v">'+f.n+'</span></span>';
|
|
354
372
|
if(f.dur>0)sh+='<span>Duration: <span class="fs-v">'+fD(f.dur)+'</span></span>';
|
|
355
|
-
if(f.tok>0)sh+='<span>Tokens: <span class="fs-v">'+f.tok+'</span></span>';
|
|
373
|
+
if(f.tok>0)sh+='<span>Tokens used: <span class="fs-v">'+f.tok+'</span></span>';
|
|
356
374
|
if(f.cost>0)sh+='<span>Cost: <span class="fs-v">$'+f.cost.toFixed(4)+'</span></span>';
|
|
357
375
|
var tn=Object.keys(f.toolNames);
|
|
358
|
-
if(tn.length)sh+='<span>Tools: <span class="fs-tools">'+tn.map(esc).join(', ')+'</span></span>';
|
|
359
|
-
if(f.errCount>0)sh+='<span
|
|
376
|
+
if(tn.length)sh+='<span>Tools used: <span class="fs-tools">'+tn.map(esc).join(', ')+'</span></span>';
|
|
377
|
+
if(f.errCount>0)sh+='<span>\\u26A0 Errors: <span class="fs-err">'+f.errCount+'</span></span>';
|
|
360
378
|
if(f.agentId)sh+='<span>Agent: <span class="fs-agent">'+esc(f.agentId)+'</span></span>';
|
|
361
379
|
f.summary.innerHTML=sh;
|
|
362
380
|
}
|
|
@@ -423,29 +441,35 @@ export function getDashboardHtml() {
|
|
|
423
441
|
var ft=ev.type||'?';
|
|
424
442
|
if(ft==='custom'&&m.openclawEventType==='session.state')ft='session.'+(m.sessionState||'state');
|
|
425
443
|
if(ft==='custom'&&m.openclawEventType==='message_sent')ft='msg.delivered';
|
|
444
|
+
if(ft==='custom'&&m.openclawHook==='message_received')ft='msg.in';
|
|
445
|
+
if(ft==='custom'&&m.openclawHook==='message_sending')ft='msg.out';
|
|
446
|
+
|
|
447
|
+
var fl=friendlyLabel(ft);
|
|
426
448
|
|
|
427
449
|
var h='<div class="r-main">';
|
|
428
450
|
h+='<span class="r-time">'+fT(ev.ts)+'</span>';
|
|
451
|
+
h+='<span class="r-icon">'+fl[0]+'</span>';
|
|
429
452
|
if(ev.severity)h+='<span class="sev-dot '+(ev.severity||'')+'" title="'+esc(ev.severity)+'"></span>';
|
|
430
|
-
h+='<span class="r-type '+c+'">'+esc(
|
|
431
|
-
if(oc)h+='<span class="r-oc '+oc+'">'+(oc==='success'?'\\u2713':oc==='error'?'\\u2717':'\\
|
|
453
|
+
h+='<span class="r-type '+c+'">'+esc(fl[1])+'</span>';
|
|
454
|
+
if(oc)h+='<span class="r-oc '+oc+'">'+(oc==='success'?'\\u2713 OK':oc==='error'?'\\u2717 Failed':oc==='timeout'?'\\u23F1 Timeout':'\\u25CB '+oc)+'</span>';
|
|
432
455
|
h+='<span class="r-pills">';
|
|
433
456
|
if(m.toolName)h+='<span class="p t">'+esc(String(m.toolName))+'</span>';
|
|
434
457
|
if(ev.durationMs!=null)h+='<span class="p d">'+fD(ev.durationMs)+'</span>';
|
|
435
|
-
if(ev.tokenCount!=null)h+='<span class="p tk">'+ev.tokenCount+'
|
|
458
|
+
if(ev.tokenCount!=null)h+='<span class="p tk">'+ev.tokenCount+' tokens</span>';
|
|
436
459
|
if(ev.costUsd!=null)h+='<span class="p cost">$'+ev.costUsd.toFixed(4)+'</span>';
|
|
437
460
|
if(ev.agentId)h+='<span class="p agent">'+esc(ev.agentId.length>12?ev.agentId.slice(0,8)+'..':ev.agentId)+'</span>';
|
|
438
|
-
if(ev.queueDepth!=null)h+='<span class="p q">
|
|
461
|
+
if(ev.queueDepth!=null)h+='<span class="p q">Queue: '+ev.queueDepth+'</span>';
|
|
439
462
|
if(m.model)h+='<span class="p m">'+esc(String(m.model))+'</span>';
|
|
440
|
-
if(ev.channel)h+='<span class="p ch">'+esc(ev.channel)+'</span>';
|
|
463
|
+
if(ev.channel)h+='<span class="p ch">via '+esc(ev.channel)+'</span>';
|
|
441
464
|
if(m.messageCount!=null)h+='<span class="p">'+m.messageCount+' msgs</span>';
|
|
465
|
+
if(m.content){var preview=String(m.content);if(preview.length>60)preview=preview.slice(0,57)+'...';h+='<span class="p">'+esc(preview)+'</span>'}
|
|
442
466
|
if(m.source&&String(m.source)!=='simulate')h+='<span class="p s">'+esc(String(m.source))+'</span>';
|
|
443
467
|
h+='</span></div>';
|
|
444
468
|
|
|
445
469
|
var ds=[];
|
|
446
470
|
if(ev.error)ds.push('<span class="err">'+esc(ev.error.length>120?ev.error.slice(0,120)+'...':ev.error)+'</span>');
|
|
447
|
-
if(ev.ageMs!=null)ds.push('
|
|
448
|
-
if(m.sessionState)ds.push('<span class="sc">'+(m.previousState||'?')+' \\u2192 '+m.sessionState+'</span>');
|
|
471
|
+
if(ev.ageMs!=null)ds.push('idle for '+fD(ev.ageMs));
|
|
472
|
+
if(m.sessionState)ds.push('<span class="sc">'+esc(String(m.previousState||'?'))+' \\u2192 '+esc(String(m.sessionState))+'</span>');
|
|
449
473
|
if(m.provider)ds.push('<span class="dim">provider: '+esc(String(m.provider))+'</span>');
|
|
450
474
|
if(m.to)ds.push('<span class="dim">to: '+esc(String(m.to))+'</span>');
|
|
451
475
|
if(ev.sessionKey&&depth===0)ds.push('<span class="dim">session: '+esc(ev.sessionKey.slice(0,12))+'</span>');
|
|
@@ -572,7 +596,7 @@ export function getDashboardHtml() {
|
|
|
572
596
|
function addAlert(a){
|
|
573
597
|
alEmpty.style.display='none';
|
|
574
598
|
var d=document.createElement('div');d.className='al';
|
|
575
|
-
d.innerHTML='<div class="al-sev '+(
|
|
599
|
+
var sv=a.severity||'error';d.innerHTML='<div class="al-sev '+esc(sv)+'">['+esc(sv.toUpperCase())+'] '+esc(a.ruleId||'')+'</div><div class="al-title">'+esc(a.title||'')+'</div><div class="al-detail">'+esc(a.detail||'')+'</div><div class="al-time">'+fT(a.ts)+'</div>';
|
|
576
600
|
alList.insertBefore(d,alList.firstChild);
|
|
577
601
|
while(alList.children.length>MAX_ALERTS+1)alList.removeChild(alList.lastChild);
|
|
578
602
|
}
|
|
@@ -596,17 +620,21 @@ export function getDashboardHtml() {
|
|
|
596
620
|
// ─── SSE (OpenAlerts events + OpenClaw log tailing) ──────────────────────
|
|
597
621
|
function connectSSE(){
|
|
598
622
|
if(evSrc)evSrc.close();
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
623
|
+
try{
|
|
624
|
+
evSrc=new EventSource('/openalerts/events');
|
|
625
|
+
evSrc.addEventListener('openalerts',function(e){try{addEvent(JSON.parse(e.data))}catch(_){}});
|
|
626
|
+
evSrc.addEventListener('history',function(e){try{var evs=JSON.parse(e.data);for(var i=0;i<evs.length;i++)addEvent(evs[i])}catch(_){}});
|
|
627
|
+
evSrc.addEventListener('oclog',function(e){try{addLogEntry(JSON.parse(e.data))}catch(_){}});
|
|
628
|
+
evSrc.onopen=function(){$('sDot').className='dot live';$('sConn').textContent='live'};
|
|
629
|
+
evSrc.onerror=function(e){$('sDot').className='dot dead';$('sConn').textContent='err:'+evSrc.readyState};
|
|
630
|
+
}catch(e){$('sConn').textContent='SSE fail:'+e.message}
|
|
604
631
|
}
|
|
605
632
|
|
|
606
633
|
// ─── State polling ──────────────────────
|
|
607
634
|
var prevAl={};
|
|
608
635
|
function pollState(){
|
|
609
|
-
fetch('/openalerts/state').then(function(r){return r.json()}).then(function(s){
|
|
636
|
+
fetch('/openalerts/state').then(function(r){if(!r.ok)throw new Error('HTTP '+r.status);return r.json()}).catch(function(e){$('sUp').textContent='fetch err: '+e.message;return null}).then(function(s){
|
|
637
|
+
if(!s)return;
|
|
610
638
|
if(s.stats){
|
|
611
639
|
$('sMsgs').textContent=s.stats.messagesProcessed||0;
|
|
612
640
|
$('sErr').textContent=(s.stats.messageErrors||0)+(s.stats.webhookErrors||0)+(s.stats.toolErrors||0);
|
|
@@ -687,8 +715,19 @@ export function getDashboardHtml() {
|
|
|
687
715
|
if($('lA').checked)list.scrollTop=list.scrollHeight;
|
|
688
716
|
}
|
|
689
717
|
|
|
718
|
+
function hasActiveFilter(){
|
|
719
|
+
var fSub=$('lF').value;
|
|
720
|
+
var fSrch=$('lS').value;
|
|
721
|
+
var allLevels=['TRACE','DEBUG','INFO','WARN','ERROR','FATAL'];
|
|
722
|
+
var anyUnchecked=false;
|
|
723
|
+
for(var li=0;li<allLevels.length;li++){var cb=$('lv-'+allLevels[li]);if(cb&&!cb.checked){anyUnchecked=true;break}}
|
|
724
|
+
return !!(fSub||fSrch||anyUnchecked);
|
|
725
|
+
}
|
|
726
|
+
|
|
690
727
|
function refreshLogs(){
|
|
691
|
-
|
|
728
|
+
var fetchAll=$('lAll').checked||hasActiveFilter();
|
|
729
|
+
var limit=fetchAll?0:500;
|
|
730
|
+
fetch('/openalerts/logs?limit='+limit).then(function(r){return r.json()}).then(function(data){
|
|
692
731
|
var list=$('logList');
|
|
693
732
|
var entries=data.entries||[];
|
|
694
733
|
var fSub=$('lF').value, fSrch=$('lS').value.toLowerCase();
|
|
@@ -713,15 +752,16 @@ export function getDashboardHtml() {
|
|
|
713
752
|
}
|
|
714
753
|
|
|
715
754
|
list.innerHTML='';
|
|
716
|
-
|
|
717
|
-
|
|
755
|
+
var shown=0,total=entries.length;
|
|
718
756
|
for(var i=0;i<entries.length;i++){
|
|
719
757
|
var e=entries[i];
|
|
720
758
|
if(fSub&&e.subsystem.indexOf(fSub)<0)continue;
|
|
721
759
|
if(!isLevelEnabled(e.level))continue;
|
|
722
760
|
if(fSrch&&e.message.toLowerCase().indexOf(fSrch)<0&&e.subsystem.toLowerCase().indexOf(fSrch)<0)continue;
|
|
723
761
|
list.appendChild(buildLogTabRow(e));
|
|
762
|
+
shown++;
|
|
724
763
|
}
|
|
764
|
+
if(!shown){list.innerHTML='<div class="empty-msg">No logs match your filters. '+(truncated?'Try checking "All logs" to search the full log file.':'')+'</div>';return}
|
|
725
765
|
if($('lA').checked)list.scrollTop=list.scrollHeight;
|
|
726
766
|
}).catch(function(){$('logList').innerHTML='<div class="empty-msg">Failed to load.</div>'});
|
|
727
767
|
}
|
|
@@ -736,7 +776,7 @@ export function getDashboardHtml() {
|
|
|
736
776
|
var btn=row.querySelector('.log-copy');
|
|
737
777
|
if(btn)lines.push(btn.getAttribute('data-raw'));
|
|
738
778
|
});
|
|
739
|
-
var blob=new Blob([lines.join('
|
|
779
|
+
var blob=new Blob([lines.join('\\n')],{type:'text/plain'});
|
|
740
780
|
var url=URL.createObjectURL(blob);
|
|
741
781
|
var a=document.createElement('a');
|
|
742
782
|
a.href=url;a.download='openalerts-logs-'+Date.now()+'.txt';
|
|
@@ -748,6 +788,7 @@ export function getDashboardHtml() {
|
|
|
748
788
|
$('lR').addEventListener('click',refreshLogs);
|
|
749
789
|
$('lE').addEventListener('click',exportLogs);
|
|
750
790
|
$('lF').addEventListener('change',refreshLogs);
|
|
791
|
+
$('lAll').addEventListener('change',refreshLogs);
|
|
751
792
|
var sDb;$('lS').addEventListener('input',function(){clearTimeout(sDb);sDb=setTimeout(refreshLogs,300)});
|
|
752
793
|
|
|
753
794
|
// Level filter checkboxes
|
|
@@ -814,7 +855,7 @@ export function getDashboardHtml() {
|
|
|
814
855
|
var cds=s.cooldowns||{};
|
|
815
856
|
if(s.rules)for(var i=0;i<s.rules.length;i++){
|
|
816
857
|
var r=s.rules[i];
|
|
817
|
-
var cdTs=cds
|
|
858
|
+
var cdTs=findLastFired(cds,r.id);
|
|
818
859
|
var lastFired=cdTs?fAgo(cdTs):'--';
|
|
819
860
|
html+='<tr><td>'+esc(r.id)+'</td><td>'+(r.status==='fired'?'<span style="color:#f85149;font-weight:700">FIRING</span>':'<span style="color:#3fb950">OK</span>')+'</td><td>'+lastFired+'</td></tr>';
|
|
820
861
|
}
|
|
@@ -826,34 +867,12 @@ export function getDashboardHtml() {
|
|
|
826
867
|
html+='</table></div>';
|
|
827
868
|
}
|
|
828
869
|
|
|
829
|
-
// Circuit Breakers (when backend exposes this)
|
|
830
|
-
if(s.circuitBreakers&&s.circuitBreakers.length){
|
|
831
|
-
html+='<div class="h-sec"><h3>Circuit Breakers</h3><table class="h-tbl"><tr><th>Category</th><th>Name</th><th>State</th><th>Failures</th><th>Last Change</th></tr>';
|
|
832
|
-
for(var k=0;k<s.circuitBreakers.length;k++){
|
|
833
|
-
var cb=s.circuitBreakers[k];
|
|
834
|
-
var stColor=cb.state==='CLOSED'?'#3fb950':cb.state==='OPEN'?'#f85149':'#d29922';
|
|
835
|
-
html+='<tr><td>'+esc(cb.category)+'</td><td>'+esc(cb.name)+'</td><td style="color:'+stColor+';font-weight:700">'+cb.state+'</td><td>'+cb.failures+'</td><td>'+fAgo(cb.lastStateChange)+'</td></tr>';
|
|
836
|
-
}
|
|
837
|
-
html+='</table></div>';
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
// Task Timeouts (when backend exposes this)
|
|
841
|
-
if(s.taskTimeouts&&s.taskTimeouts.length){
|
|
842
|
-
html+='<div class="h-sec"><h3>Running Tasks</h3><table class="h-tbl"><tr><th>Type</th><th>ID</th><th>Duration</th><th>Timeout</th><th>Status</th></tr>';
|
|
843
|
-
for(var l=0;l<s.taskTimeouts.length;l++){
|
|
844
|
-
var tt=s.taskTimeouts[l];
|
|
845
|
-
var dur=Date.now()-tt.startedAt;
|
|
846
|
-
var pct=Math.floor((dur/tt.timeoutMs)*100);
|
|
847
|
-
var warn=pct>80;
|
|
848
|
-
html+='<tr><td>'+esc(tt.type)+'</td><td>'+esc(tt.id.slice(0,12))+'</td><td>'+fD(dur)+'</td><td>'+fD(tt.timeoutMs)+'</td><td style="color:'+(warn?'#d29922':'#3fb950')+'">'+pct+'%</td></tr>';
|
|
849
|
-
}
|
|
850
|
-
html+='</table></div>';
|
|
851
|
-
}
|
|
852
|
-
|
|
853
870
|
hEl.innerHTML=html;
|
|
854
871
|
}
|
|
855
872
|
function hCard(l,v,c){return'<div class="h-card"><div class="lb">'+esc(l)+'</div><div class="vl '+(c||'')+'">'+esc(String(v))+'</div></div>'}
|
|
856
873
|
function hTr(l,v){return'<tr><td>'+esc(l)+'</td><td><b>'+esc(String(v))+'</b></td></tr>'}
|
|
874
|
+
/** Find the most recent cooldown timestamp for a rule ID (handles contextual fingerprints like "llm-errors:telegram"). */
|
|
875
|
+
function findLastFired(cds,ruleId){var latest=0;for(var k in cds){if(k===ruleId||k.indexOf(ruleId+':')===0){if(cds[k]>latest)latest=cds[k]}}return latest||null}
|
|
857
876
|
|
|
858
877
|
// ─── Debug tab ──────────────────────
|
|
859
878
|
function refreshDebug(){
|
|
@@ -900,18 +919,13 @@ export function getDashboardHtml() {
|
|
|
900
919
|
var cds=s.cooldowns||{};
|
|
901
920
|
if(s.rules)for(var i=0;i<s.rules.length;i++){
|
|
902
921
|
var r=s.rules[i];
|
|
903
|
-
var cdTs=cds
|
|
922
|
+
var cdTs=findLastFired(cds,r.id);
|
|
904
923
|
var lastFired=cdTs?fAgo(cdTs):'never';
|
|
905
924
|
rulesHtml+='<tr><td>'+esc(r.id)+'</td><td>'+(r.status==='fired'?'<span style="color:#f85149;font-weight:700">FIRING</span>':'<span style="color:#3fb950">OK</span>')+'</td><td>'+lastFired+'</td></tr>';
|
|
906
925
|
}
|
|
907
926
|
rulesHtml+='</table>';
|
|
908
927
|
$('dbRules').innerHTML=rulesHtml;
|
|
909
928
|
|
|
910
|
-
// Circuit breakers (placeholder - will be populated when backend exposes this)
|
|
911
|
-
$('dbCircuit').innerHTML='<div style="color:#8b949e;padding:8px">Circuit breaker state not yet exposed by backend</div>';
|
|
912
|
-
|
|
913
|
-
// Task timeouts (placeholder - will be populated when backend exposes this)
|
|
914
|
-
$('dbTasks').innerHTML='<div style="color:#8b949e;padding:8px">Task timeout state not yet exposed by backend</div>';
|
|
915
929
|
}
|
|
916
930
|
$('dbRefresh').addEventListener('click',refreshDebug);
|
|
917
931
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { DEFAULTS } from "../core/index.js";
|
|
3
4
|
import { getDashboardHtml } from "./dashboard-html.js";
|
|
4
5
|
// ─── SSE connection tracking ─────────────────────────────────────────────────
|
|
5
6
|
const sseConnections = new Set();
|
|
@@ -26,14 +27,35 @@ const RULE_IDS = [
|
|
|
26
27
|
"heartbeat-fail",
|
|
27
28
|
"queue-depth",
|
|
28
29
|
"high-error-rate",
|
|
30
|
+
"tool-errors",
|
|
29
31
|
"gateway-down",
|
|
30
32
|
];
|
|
31
33
|
function getRuleStatuses(engine) {
|
|
32
34
|
const state = engine.state;
|
|
33
35
|
const now = Date.now();
|
|
36
|
+
const cooldownWindow = 15 * 60 * 1000;
|
|
34
37
|
return RULE_IDS.map((id) => {
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
// For gateway-down, reflect current condition: if heartbeats have resumed,
|
|
39
|
+
// show OK even if the rule fired recently.
|
|
40
|
+
if (id === "gateway-down") {
|
|
41
|
+
const silenceMs = state.lastHeartbeatTs > 0
|
|
42
|
+
? now - state.lastHeartbeatTs
|
|
43
|
+
: 0;
|
|
44
|
+
const isCurrentlyDown = state.lastHeartbeatTs > 0 &&
|
|
45
|
+
silenceMs >= DEFAULTS.gatewayDownThresholdMs;
|
|
46
|
+
return { id, status: isCurrentlyDown ? "fired" : "ok" };
|
|
47
|
+
}
|
|
48
|
+
// Cooldown keys are fingerprints like "llm-errors:unknown", not bare rule IDs.
|
|
49
|
+
// Check if ANY cooldown key starting with this rule ID has fired recently.
|
|
50
|
+
let fired = false;
|
|
51
|
+
for (const [key, ts] of state.cooldowns) {
|
|
52
|
+
if (key === id || key.startsWith(id + ":")) {
|
|
53
|
+
if (ts > now - cooldownWindow) {
|
|
54
|
+
fired = true;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
37
59
|
return { id, status: fired ? "fired" : "ok" };
|
|
38
60
|
});
|
|
39
61
|
}
|
|
@@ -115,8 +137,8 @@ function readOpenClawLogs(maxEntries, afterTs) {
|
|
|
115
137
|
entries.push(parsed);
|
|
116
138
|
subsystemSet.add(parsed.subsystem);
|
|
117
139
|
}
|
|
118
|
-
const truncated = entries.length > maxEntries;
|
|
119
|
-
const sliced = entries.slice(-maxEntries);
|
|
140
|
+
const truncated = maxEntries > 0 && entries.length > maxEntries;
|
|
141
|
+
const sliced = maxEntries > 0 ? entries.slice(-maxEntries) : entries;
|
|
120
142
|
const subsystems = Array.from(subsystemSet).sort();
|
|
121
143
|
return { entries: sliced, truncated, subsystems };
|
|
122
144
|
}
|
|
@@ -208,6 +230,20 @@ export function createDashboardHandler(getEngine) {
|
|
|
208
230
|
"Access-Control-Allow-Origin": "*",
|
|
209
231
|
});
|
|
210
232
|
res.flushHeaders();
|
|
233
|
+
// Send initial connection event so the browser knows the stream is live
|
|
234
|
+
res.write(`:ok\n\n`);
|
|
235
|
+
// Send current state snapshot as initial event
|
|
236
|
+
const state = engine.state;
|
|
237
|
+
res.write(`event: state\ndata: ${JSON.stringify({
|
|
238
|
+
uptimeMs: Date.now() - state.startedAt,
|
|
239
|
+
stats: state.stats,
|
|
240
|
+
rules: getRuleStatuses(engine),
|
|
241
|
+
})}\n\n`);
|
|
242
|
+
// Send event history so dashboard survives refreshes
|
|
243
|
+
const history = engine.getRecentLiveEvents(200);
|
|
244
|
+
if (history.length > 0) {
|
|
245
|
+
res.write(`event: history\ndata: ${JSON.stringify(history)}\n\n`);
|
|
246
|
+
}
|
|
211
247
|
// Subscribe to engine events
|
|
212
248
|
const unsub = engine.bus.on((event) => {
|
|
213
249
|
try {
|
|
@@ -274,7 +310,9 @@ export function createDashboardHandler(getEngine) {
|
|
|
274
310
|
// ── GET /openalerts/logs → OpenClaw log entries (for Logs tab) ────
|
|
275
311
|
if (url.startsWith("/openalerts/logs") && req.method === "GET") {
|
|
276
312
|
const urlObj = new URL(url, "http://localhost");
|
|
277
|
-
const
|
|
313
|
+
const rawLimit = urlObj.searchParams.get("limit") || "200";
|
|
314
|
+
// limit=0 means "no limit" — return all log entries
|
|
315
|
+
const limit = rawLimit === "0" ? 0 : Math.min(parseInt(rawLimit, 10), 50000);
|
|
278
316
|
const afterTs = urlObj.searchParams.get("after") || undefined;
|
|
279
317
|
const result = readOpenClawLogs(limit, afterTs);
|
|
280
318
|
res.writeHead(200, {
|
|
@@ -272,6 +272,30 @@ export function createLogBridge(engine) {
|
|
|
272
272
|
},
|
|
273
273
|
});
|
|
274
274
|
}
|
|
275
|
+
// ── Lane task error (diagnostic) ────────────────────────────────────────────
|
|
276
|
+
// Safety net: catches lane-level errors from diagnostic logs.
|
|
277
|
+
// The agent_end hook already covers agent errors → llm-errors rule.
|
|
278
|
+
// This emits as infra.error to avoid double-counting in the llm-errors window
|
|
279
|
+
// while still ensuring infra-errors fires if the hook path fails.
|
|
280
|
+
// Format: "lane task error: lane=main durationMs=1 error="Error: ...""
|
|
281
|
+
function handleLaneTaskError(rec) {
|
|
282
|
+
const { lane, error: errorMsg } = rec.kvs;
|
|
283
|
+
const dedupeKey = `lane-error:${lane}:${rec.ts}`;
|
|
284
|
+
if (dedupeSet.has(dedupeKey))
|
|
285
|
+
return;
|
|
286
|
+
dedupeSet.add(dedupeKey);
|
|
287
|
+
ingest({
|
|
288
|
+
type: "infra.error",
|
|
289
|
+
ts: rec.ts,
|
|
290
|
+
outcome: "error",
|
|
291
|
+
error: errorMsg,
|
|
292
|
+
meta: {
|
|
293
|
+
lane,
|
|
294
|
+
source: "log-bridge",
|
|
295
|
+
openclawLog: "lane_task_error",
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
}
|
|
275
299
|
// ── Exec command (exec) ────────────────────────────────────────────────────
|
|
276
300
|
function handleExecCommand(rec) {
|
|
277
301
|
pendingCommand = rec.message;
|
|
@@ -312,6 +336,9 @@ export function createLogBridge(engine) {
|
|
|
312
336
|
if (msg.startsWith("session state:")) {
|
|
313
337
|
handleSessionState(rec);
|
|
314
338
|
}
|
|
339
|
+
else if (msg.startsWith("lane task error:")) {
|
|
340
|
+
handleLaneTaskError(rec);
|
|
341
|
+
}
|
|
315
342
|
}
|
|
316
343
|
else if (rec.subsystem === "exec") {
|
|
317
344
|
if (msg.startsWith("elevated command")) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steadwing/openalerts",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenAlerts — An alerting layer for agentic frameworks",
|
|
6
6
|
"author": "Steadwing",
|
|
@@ -39,5 +39,8 @@
|
|
|
39
39
|
"keywords": ["openalerts", "openclaw", "monitoring", "alerting", "plugin"],
|
|
40
40
|
"engines": {
|
|
41
41
|
"node": ">=18"
|
|
42
|
+
},
|
|
43
|
+
"overrides": {
|
|
44
|
+
"tar": "^7.5.7"
|
|
42
45
|
}
|
|
43
46
|
}
|