@openclaw/diagnostics-prometheus 2026.7.2-beta.5 → 2026.8.1-beta.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.
Files changed (2) hide show
  1. package/dist/index.js +133 -124
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { isInternalDiagnosticEventMetadata, redactSensitiveText } from "./api.js";
2
2
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
3
+ import { normalizeDiagnosticLane, normalizeDiagnosticValue } from "openclaw/plugin-sdk/diagnostic-runtime";
4
+ import { asNonNegativeFiniteNumber } from "openclaw/plugin-sdk/number-runtime";
3
5
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
4
6
  //#region extensions/diagnostics-prometheus/src/service.ts
5
7
  const DURATION_BUCKETS_SECONDS = [
@@ -61,29 +63,10 @@ const RATIO_BUCKETS = [
61
63
  8,
62
64
  16
63
65
  ];
64
- const LOW_CARDINALITY_VALUE_RE = /^[A-Za-z0-9_.:-]{1,120}$/u;
65
66
  const MAX_PROMETHEUS_SERIES = 2048;
66
67
  const DROPPED_SERIES_COUNTER_NAME = "openclaw_prometheus_series_dropped_total";
67
- function lowCardinalityLabel(value, fallback = "unknown") {
68
- if (!value) return fallback;
69
- const redacted = redactSensitiveText(value.trim());
70
- const redactedLower = redacted.toLowerCase();
71
- if (redactedLower.startsWith("agent:") || redactedLower.includes(":agent:")) return fallback;
72
- return LOW_CARDINALITY_VALUE_RE.test(redacted) ? redacted : fallback;
73
- }
74
- function lowCardinalityQueueLaneLabel(value, fallback = "unknown") {
75
- if (!value) return fallback;
76
- const redacted = redactSensitiveText(value.trim());
77
- if (redacted.toLowerCase().startsWith("agent:")) return fallback;
78
- const scopedLaneIndex = redacted.indexOf(":");
79
- const lane = scopedLaneIndex >= 0 ? redacted.slice(0, scopedLaneIndex) : redacted;
80
- return LOW_CARDINALITY_VALUE_RE.test(lane) ? lane : fallback;
81
- }
82
- function numericValue(value) {
83
- return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
84
- }
85
68
  function seconds(ms) {
86
- const value = numericValue(ms);
69
+ const value = asNonNegativeFiniteNumber(ms);
87
70
  return value === void 0 ? void 0 : value / 1e3;
88
71
  }
89
72
  function sortedLabels(labels) {
@@ -246,126 +229,126 @@ function renderPrometheusMetrics(store) {
246
229
  }
247
230
  function runLabels(evt) {
248
231
  return {
249
- ...evt.blockedBy ? { blocked_by: lowCardinalityLabel(evt.blockedBy) } : {},
250
- channel: lowCardinalityLabel(evt.channel),
251
- model: lowCardinalityLabel(evt.model),
252
- outcome: lowCardinalityLabel(evt.outcome, "unknown"),
253
- provider: lowCardinalityLabel(evt.provider),
254
- trigger: lowCardinalityLabel(evt.trigger)
232
+ ...evt.blockedBy ? { blocked_by: normalizeDiagnosticValue(evt.blockedBy) } : {},
233
+ channel: normalizeDiagnosticValue(evt.channel),
234
+ model: normalizeDiagnosticValue(evt.model),
235
+ outcome: normalizeDiagnosticValue(evt.outcome, "unknown"),
236
+ provider: normalizeDiagnosticValue(evt.provider),
237
+ trigger: normalizeDiagnosticValue(evt.trigger)
255
238
  };
256
239
  }
257
240
  function modelCallLabels(evt) {
258
241
  return {
259
- api: lowCardinalityLabel(evt.api),
260
- error_category: evt.type === "model.call.error" ? lowCardinalityLabel(evt.errorCategory, "other") : "none",
261
- model: lowCardinalityLabel(evt.model),
242
+ api: normalizeDiagnosticValue(evt.api),
243
+ error_category: evt.type === "model.call.error" ? normalizeDiagnosticValue(evt.errorCategory, "other") : "none",
244
+ model: normalizeDiagnosticValue(evt.model),
262
245
  observation_unit: evt.observationUnit === "turn" ? "turn" : "request",
263
246
  outcome: evt.type === "model.call.error" ? "error" : "completed",
264
- provider: lowCardinalityLabel(evt.provider),
265
- transport: lowCardinalityLabel(evt.transport)
247
+ provider: normalizeDiagnosticValue(evt.provider),
248
+ transport: normalizeDiagnosticValue(evt.transport)
266
249
  };
267
250
  }
268
251
  function modelFailoverLabels(evt) {
269
252
  return {
270
- from_model: lowCardinalityLabel(evt.fromModel),
271
- from_provider: lowCardinalityLabel(evt.fromProvider),
272
- lane: lowCardinalityQueueLaneLabel(evt.lane),
273
- reason: lowCardinalityLabel(evt.reason, "other"),
253
+ from_model: normalizeDiagnosticValue(evt.fromModel),
254
+ from_provider: normalizeDiagnosticValue(evt.fromProvider),
255
+ lane: normalizeDiagnosticLane(evt.lane),
256
+ reason: normalizeDiagnosticValue(evt.reason, "other"),
274
257
  suspended: evt.suspended === void 0 ? "unknown" : String(evt.suspended),
275
- to_model: lowCardinalityLabel(evt.toModel),
276
- to_provider: lowCardinalityLabel(evt.toProvider)
258
+ to_model: normalizeDiagnosticValue(evt.toModel),
259
+ to_provider: normalizeDiagnosticValue(evt.toProvider)
277
260
  };
278
261
  }
279
262
  function toolExecutionLabels(evt) {
280
263
  return {
281
- error_category: evt.type === "tool.execution.error" ? lowCardinalityLabel(evt.errorCategory, "other") : "none",
264
+ error_category: evt.type === "tool.execution.error" ? normalizeDiagnosticValue(evt.errorCategory, "other") : "none",
282
265
  outcome: evt.type === "tool.execution.error" ? "error" : "completed",
283
- params_kind: lowCardinalityLabel(evt.paramsSummary?.kind),
284
- tool: lowCardinalityLabel(evt.toolName, "tool"),
285
- tool_owner: lowCardinalityLabel(evt.toolOwner, "none"),
286
- tool_source: lowCardinalityLabel(evt.toolSource, "core")
266
+ params_kind: normalizeDiagnosticValue(evt.paramsSummary?.kind),
267
+ tool: normalizeDiagnosticValue(evt.toolName, "tool"),
268
+ tool_owner: normalizeDiagnosticValue(evt.toolOwner, "none"),
269
+ tool_source: normalizeDiagnosticValue(evt.toolSource, "core")
287
270
  };
288
271
  }
289
272
  function toolExecutionBlockedLabels(evt) {
290
273
  return {
291
- denied_reason: lowCardinalityLabel(evt.deniedReason, "other"),
292
- params_kind: lowCardinalityLabel(evt.paramsSummary?.kind),
293
- tool: lowCardinalityLabel(evt.toolName, "tool"),
294
- tool_owner: lowCardinalityLabel(evt.toolOwner, "none"),
295
- tool_source: lowCardinalityLabel(evt.toolSource, "core")
274
+ denied_reason: normalizeDiagnosticValue(evt.deniedReason, "other"),
275
+ params_kind: normalizeDiagnosticValue(evt.paramsSummary?.kind),
276
+ tool: normalizeDiagnosticValue(evt.toolName, "tool"),
277
+ tool_owner: normalizeDiagnosticValue(evt.toolOwner, "none"),
278
+ tool_source: normalizeDiagnosticValue(evt.toolSource, "core")
296
279
  };
297
280
  }
298
281
  function skillLabels(evt) {
299
282
  return {
300
- activation: lowCardinalityLabel(evt.activation, "unknown"),
301
- agent: lowCardinalityLabel(evt.agentId),
302
- skill: lowCardinalityLabel(evt.skillName, "skill"),
303
- source: lowCardinalityLabel(evt.skillSource)
283
+ activation: normalizeDiagnosticValue(evt.activation, "unknown"),
284
+ agent: normalizeDiagnosticValue(evt.agentId),
285
+ skill: normalizeDiagnosticValue(evt.skillName, "skill"),
286
+ source: normalizeDiagnosticValue(evt.skillSource)
304
287
  };
305
288
  }
306
289
  function harnessLabels(evt) {
307
290
  return {
308
- channel: lowCardinalityLabel(evt.channel),
309
- error_category: evt.type === "harness.run.error" ? lowCardinalityLabel(evt.errorCategory, "other") : "none",
310
- harness: lowCardinalityLabel(evt.harnessId),
311
- model: lowCardinalityLabel(evt.model),
312
- outcome: evt.type === "harness.run.error" ? "error" : lowCardinalityLabel(evt.outcome),
313
- phase: evt.type === "harness.run.error" ? lowCardinalityLabel(evt.phase) : "none",
314
- plugin: lowCardinalityLabel(evt.pluginId),
315
- provider: lowCardinalityLabel(evt.provider)
291
+ channel: normalizeDiagnosticValue(evt.channel),
292
+ error_category: evt.type === "harness.run.error" ? normalizeDiagnosticValue(evt.errorCategory, "other") : "none",
293
+ harness: normalizeDiagnosticValue(evt.harnessId),
294
+ model: normalizeDiagnosticValue(evt.model),
295
+ outcome: evt.type === "harness.run.error" ? "error" : normalizeDiagnosticValue(evt.outcome),
296
+ phase: evt.type === "harness.run.error" ? normalizeDiagnosticValue(evt.phase) : "none",
297
+ plugin: normalizeDiagnosticValue(evt.pluginId),
298
+ provider: normalizeDiagnosticValue(evt.provider)
316
299
  };
317
300
  }
318
301
  function webhookLabels(evt) {
319
302
  return {
320
- channel: lowCardinalityLabel(evt.channel),
321
- webhook: lowCardinalityLabel(evt.updateType)
303
+ channel: normalizeDiagnosticValue(evt.channel),
304
+ webhook: normalizeDiagnosticValue(evt.updateType)
322
305
  };
323
306
  }
324
307
  function sessionStuckLabels(evt) {
325
308
  return {
326
- reason: lowCardinalityLabel(evt.reason, "none"),
309
+ reason: normalizeDiagnosticValue(evt.reason, "none"),
327
310
  state: evt.state
328
311
  };
329
312
  }
330
313
  function sessionRecoveryLabels(evt) {
331
314
  return {
332
- action: evt.type === "session.recovery.completed" ? lowCardinalityLabel(evt.action, "unknown") : evt.allowActiveAbort ? "abort" : "recover",
333
- active_work_kind: lowCardinalityLabel(evt.activeWorkKind, "none"),
315
+ action: evt.type === "session.recovery.completed" ? normalizeDiagnosticValue(evt.action, "unknown") : evt.allowActiveAbort ? "abort" : "recover",
316
+ active_work_kind: normalizeDiagnosticValue(evt.activeWorkKind, "none"),
334
317
  state: evt.state,
335
318
  status: evt.type === "session.recovery.completed" ? evt.status : "requested"
336
319
  };
337
320
  }
338
321
  function livenessLabels(evt) {
339
- return { reason: lowCardinalityLabel(evt.reasons.join(":"), "unknown") };
322
+ return { reason: normalizeDiagnosticValue(evt.reasons.join(":"), "unknown") };
340
323
  }
341
324
  function payloadLargeLabels(evt) {
342
325
  return {
343
326
  action: evt.action,
344
- channel: lowCardinalityLabel(evt.channel, "none"),
345
- plugin: lowCardinalityLabel(evt.pluginId, "none"),
346
- reason: lowCardinalityLabel(evt.reason, "none"),
347
- surface: lowCardinalityLabel(evt.surface, "unknown")
327
+ channel: normalizeDiagnosticValue(evt.channel, "none"),
328
+ plugin: normalizeDiagnosticValue(evt.pluginId, "none"),
329
+ reason: normalizeDiagnosticValue(evt.reason, "none"),
330
+ surface: normalizeDiagnosticValue(evt.surface, "unknown")
348
331
  };
349
332
  }
350
333
  function talkLabels(evt) {
351
334
  return {
352
- brain: lowCardinalityLabel(evt.brain),
353
- event_type: lowCardinalityLabel(evt.talkEventType),
354
- mode: lowCardinalityLabel(evt.mode),
355
- provider: lowCardinalityLabel(evt.provider),
356
- transport: lowCardinalityLabel(evt.transport)
335
+ brain: normalizeDiagnosticValue(evt.brain),
336
+ event_type: normalizeDiagnosticValue(evt.talkEventType),
337
+ mode: normalizeDiagnosticValue(evt.mode),
338
+ provider: normalizeDiagnosticValue(evt.provider),
339
+ transport: normalizeDiagnosticValue(evt.transport)
357
340
  };
358
341
  }
359
342
  function recordModelUsage(store, evt) {
360
343
  const labels = {
361
- agent: lowCardinalityLabel(evt.agentId),
362
- channel: lowCardinalityLabel(evt.channel),
363
- model: lowCardinalityLabel(evt.model),
364
- provider: lowCardinalityLabel(evt.provider)
344
+ agent: normalizeDiagnosticValue(evt.agentId),
345
+ channel: normalizeDiagnosticValue(evt.channel),
346
+ model: normalizeDiagnosticValue(evt.model),
347
+ provider: normalizeDiagnosticValue(evt.provider)
365
348
  };
366
349
  const usage = evt.usage;
367
350
  const recordTokens = (tokenType, value) => {
368
- const amount = numericValue(value);
351
+ const amount = asNonNegativeFiniteNumber(value);
369
352
  if (amount === void 0 || amount === 0) return;
370
353
  store.counter("openclaw_model_tokens_total", "Model tokens reported by diagnostic usage events.", {
371
354
  ...labels,
@@ -383,7 +366,7 @@ function recordModelUsage(store, evt) {
383
366
  recordTokens("cache_write", usage.cacheWrite);
384
367
  recordTokens("prompt", usage.promptTokens);
385
368
  recordTokens("total", usage.total);
386
- store.counter("openclaw_model_cost_usd_total", "Estimated model cost in USD reported by diagnostic usage events.", labels, numericValue(evt.costUsd) ?? 0);
369
+ store.counter("openclaw_model_cost_usd_total", "Estimated model cost in USD reported by diagnostic usage events.", labels, asNonNegativeFiniteNumber(evt.costUsd) ?? 0);
387
370
  store.histogram("openclaw_model_usage_duration_seconds", "Model usage event duration in seconds.", labels, seconds(evt.durationMs));
388
371
  }
389
372
  function recordDiagnosticEvent(store, evt, metadata) {
@@ -422,14 +405,14 @@ function recordDiagnosticEvent(store, evt, metadata) {
422
405
  return;
423
406
  case "message.processed":
424
407
  store.counter("openclaw_message_processed_total", "Inbound messages processed by outcome.", {
425
- channel: lowCardinalityLabel(evt.channel),
408
+ channel: normalizeDiagnosticValue(evt.channel),
426
409
  outcome: evt.outcome,
427
- reason: lowCardinalityLabel(evt.reason, "none")
410
+ reason: normalizeDiagnosticValue(evt.reason, "none")
428
411
  });
429
412
  store.histogram("openclaw_message_processed_duration_seconds", "Inbound message processing duration in seconds.", {
430
- channel: lowCardinalityLabel(evt.channel),
413
+ channel: normalizeDiagnosticValue(evt.channel),
431
414
  outcome: evt.outcome,
432
- reason: lowCardinalityLabel(evt.reason, "none")
415
+ reason: normalizeDiagnosticValue(evt.reason, "none")
433
416
  }, seconds(evt.durationMs));
434
417
  return;
435
418
  case "webhook.received":
@@ -443,55 +426,55 @@ function recordDiagnosticEvent(store, evt, metadata) {
443
426
  return;
444
427
  case "message.delivery.started":
445
428
  store.counter("openclaw_message_delivery_started_total", "Outbound message delivery attempts started.", {
446
- channel: lowCardinalityLabel(evt.channel),
447
- delivery_kind: lowCardinalityLabel(evt.deliveryKind, "other")
429
+ channel: normalizeDiagnosticValue(evt.channel),
430
+ delivery_kind: normalizeDiagnosticValue(evt.deliveryKind, "other")
448
431
  });
449
432
  return;
450
433
  case "message.received":
451
434
  store.counter("openclaw_message_received_total", "Inbound messages received by channel.", {
452
- channel: lowCardinalityLabel(evt.channel),
453
- source: lowCardinalityLabel(evt.source)
435
+ channel: normalizeDiagnosticValue(evt.channel),
436
+ source: normalizeDiagnosticValue(evt.source)
454
437
  });
455
438
  return;
456
439
  case "message.dispatch.started":
457
440
  store.counter("openclaw_message_dispatch_started_total", "Inbound message dispatch attempts started by channel.", {
458
- channel: lowCardinalityLabel(evt.channel),
459
- source: lowCardinalityLabel(evt.source)
441
+ channel: normalizeDiagnosticValue(evt.channel),
442
+ source: normalizeDiagnosticValue(evt.source)
460
443
  });
461
444
  return;
462
445
  case "message.dispatch.completed":
463
446
  store.counter("openclaw_message_dispatch_completed_total", "Inbound message dispatch attempts completed by outcome.", {
464
- channel: lowCardinalityLabel(evt.channel),
447
+ channel: normalizeDiagnosticValue(evt.channel),
465
448
  outcome: evt.outcome,
466
- reason: lowCardinalityLabel(evt.reason, "none"),
467
- source: lowCardinalityLabel(evt.source)
449
+ reason: normalizeDiagnosticValue(evt.reason, "none"),
450
+ source: normalizeDiagnosticValue(evt.source)
468
451
  });
469
452
  store.histogram("openclaw_message_dispatch_duration_seconds", "Inbound message dispatch duration in seconds.", {
470
- channel: lowCardinalityLabel(evt.channel),
453
+ channel: normalizeDiagnosticValue(evt.channel),
471
454
  outcome: evt.outcome,
472
- reason: lowCardinalityLabel(evt.reason, "none"),
473
- source: lowCardinalityLabel(evt.source)
455
+ reason: normalizeDiagnosticValue(evt.reason, "none"),
456
+ source: normalizeDiagnosticValue(evt.source)
474
457
  }, seconds(evt.durationMs));
475
458
  return;
476
459
  case "message.delivery.completed":
477
460
  case "message.delivery.error":
478
461
  store.counter("openclaw_message_delivery_total", "Outbound message delivery attempts by outcome.", {
479
- channel: lowCardinalityLabel(evt.channel),
480
- delivery_kind: lowCardinalityLabel(evt.deliveryKind, "other"),
481
- error_category: evt.type === "message.delivery.error" ? lowCardinalityLabel(evt.errorCategory, "other") : "none",
462
+ channel: normalizeDiagnosticValue(evt.channel),
463
+ delivery_kind: normalizeDiagnosticValue(evt.deliveryKind, "other"),
464
+ error_category: evt.type === "message.delivery.error" ? normalizeDiagnosticValue(evt.errorCategory, "other") : "none",
482
465
  outcome: evt.type === "message.delivery.error" ? "error" : "completed"
483
466
  });
484
467
  store.histogram("openclaw_message_delivery_duration_seconds", "Outbound message delivery duration in seconds.", {
485
- channel: lowCardinalityLabel(evt.channel),
486
- delivery_kind: lowCardinalityLabel(evt.deliveryKind, "other"),
487
- error_category: evt.type === "message.delivery.error" ? lowCardinalityLabel(evt.errorCategory, "other") : "none",
468
+ channel: normalizeDiagnosticValue(evt.channel),
469
+ delivery_kind: normalizeDiagnosticValue(evt.deliveryKind, "other"),
470
+ error_category: evt.type === "message.delivery.error" ? normalizeDiagnosticValue(evt.errorCategory, "other") : "none",
488
471
  outcome: evt.type === "message.delivery.error" ? "error" : "completed"
489
472
  }, seconds(evt.durationMs));
490
473
  return;
491
474
  case "talk.event":
492
475
  store.counter("openclaw_talk_event_total", "Talk events emitted by type.", talkLabels(evt));
493
476
  store.histogram("openclaw_talk_event_duration_seconds", "Talk event duration in seconds when reported.", talkLabels(evt), seconds(evt.durationMs));
494
- store.histogram("openclaw_talk_audio_bytes", "Talk audio frame byte lengths.", talkLabels(evt), numericValue(evt.byteLength), BYTE_BUCKETS);
477
+ store.histogram("openclaw_talk_audio_bytes", "Talk audio frame byte lengths.", talkLabels(evt), asNonNegativeFiniteNumber(evt.byteLength), BYTE_BUCKETS);
495
478
  return;
496
479
  case "session.recovery.requested":
497
480
  case "session.recovery.completed":
@@ -500,15 +483,15 @@ function recordDiagnosticEvent(store, evt, metadata) {
500
483
  return;
501
484
  case "queue.lane.enqueue":
502
485
  case "queue.lane.dequeue":
503
- store.gauge("openclaw_queue_lane_size", "Current diagnostic queue lane size.", { lane: lowCardinalityQueueLaneLabel(evt.lane) }, numericValue(evt.queueSize));
504
- if (evt.type === "queue.lane.dequeue") store.histogram("openclaw_queue_lane_wait_seconds", "Queue lane wait time in seconds.", { lane: lowCardinalityQueueLaneLabel(evt.lane) }, seconds(evt.waitMs));
486
+ store.gauge("openclaw_queue_lane_size", "Current diagnostic queue lane size.", { lane: normalizeDiagnosticLane(evt.lane) }, asNonNegativeFiniteNumber(evt.queueSize));
487
+ if (evt.type === "queue.lane.dequeue") store.histogram("openclaw_queue_lane_wait_seconds", "Queue lane wait time in seconds.", { lane: normalizeDiagnosticLane(evt.lane) }, seconds(evt.waitMs));
505
488
  return;
506
489
  case "session.state":
507
490
  store.counter("openclaw_session_state_total", "Session state observations.", {
508
- reason: lowCardinalityLabel(evt.reason, "none"),
491
+ reason: normalizeDiagnosticValue(evt.reason, "none"),
509
492
  state: evt.state
510
493
  });
511
- if (evt.queueDepth !== void 0) store.gauge("openclaw_session_queue_depth", "Latest observed session queue depth.", { state: evt.state }, numericValue(evt.queueDepth));
494
+ if (evt.queueDepth !== void 0) store.gauge("openclaw_session_queue_depth", "Latest observed session queue depth.", { state: evt.state }, asNonNegativeFiniteNumber(evt.queueDepth));
512
495
  return;
513
496
  case "session.stuck":
514
497
  store.counter("openclaw_session_stuck_total", "Stale session bookkeeping observations with no active work.", sessionStuckLabels(evt));
@@ -516,8 +499,8 @@ function recordDiagnosticEvent(store, evt, metadata) {
516
499
  return;
517
500
  case "session.turn.created":
518
501
  store.counter("openclaw_session_turn_created_total", "Agent session turns created.", {
519
- agent: lowCardinalityLabel(evt.agentId),
520
- channel: lowCardinalityLabel(evt.channel),
502
+ agent: normalizeDiagnosticValue(evt.agentId),
503
+ channel: normalizeDiagnosticValue(evt.channel),
521
504
  trigger: evt.trigger
522
505
  });
523
506
  return;
@@ -525,7 +508,7 @@ function recordDiagnosticEvent(store, evt, metadata) {
525
508
  store.gauge("openclaw_memory_bytes", "Latest process memory usage by memory kind.", { kind: "rss" }, evt.memory.rssBytes);
526
509
  store.gauge("openclaw_memory_bytes", "Latest process memory usage by memory kind.", { kind: "heap_total" }, evt.memory.heapTotalBytes);
527
510
  store.gauge("openclaw_memory_bytes", "Latest process memory usage by memory kind.", { kind: "heap_used" }, evt.memory.heapUsedBytes);
528
- store.histogram("openclaw_memory_rss_bytes", "RSS memory sample distribution in bytes.", {}, numericValue(evt.memory.rssBytes), BYTE_BUCKETS);
511
+ store.histogram("openclaw_memory_rss_bytes", "RSS memory sample distribution in bytes.", {}, asNonNegativeFiniteNumber(evt.memory.rssBytes), BYTE_BUCKETS);
529
512
  return;
530
513
  case "diagnostic.memory.pressure":
531
514
  store.counter("openclaw_memory_pressure_total", "Memory pressure events by level and reason.", {
@@ -535,33 +518,33 @@ function recordDiagnosticEvent(store, evt, metadata) {
535
518
  return;
536
519
  case "diagnostic.liveness.warning":
537
520
  store.counter("openclaw_liveness_warning_total", "Diagnostic liveness warning events.", livenessLabels(evt));
538
- store.gauge("openclaw_liveness_sessions", "Latest session counts reported with diagnostic liveness warnings.", { state: "active" }, numericValue(evt.active));
539
- store.gauge("openclaw_liveness_sessions", "Latest session counts reported with diagnostic liveness warnings.", { state: "waiting" }, numericValue(evt.waiting));
540
- store.gauge("openclaw_liveness_sessions", "Latest session counts reported with diagnostic liveness warnings.", { state: "queued" }, numericValue(evt.queued));
521
+ store.gauge("openclaw_liveness_sessions", "Latest session counts reported with diagnostic liveness warnings.", { state: "active" }, asNonNegativeFiniteNumber(evt.active));
522
+ store.gauge("openclaw_liveness_sessions", "Latest session counts reported with diagnostic liveness warnings.", { state: "waiting" }, asNonNegativeFiniteNumber(evt.waiting));
523
+ store.gauge("openclaw_liveness_sessions", "Latest session counts reported with diagnostic liveness warnings.", { state: "queued" }, asNonNegativeFiniteNumber(evt.queued));
541
524
  store.histogram("openclaw_liveness_event_loop_delay_p99_seconds", "P99 event-loop delay reported by diagnostic liveness warnings in seconds.", livenessLabels(evt), seconds(evt.eventLoopDelayP99Ms));
542
525
  store.histogram("openclaw_liveness_event_loop_delay_max_seconds", "Maximum event-loop delay reported by diagnostic liveness warnings in seconds.", livenessLabels(evt), seconds(evt.eventLoopDelayMaxMs));
543
- store.histogram("openclaw_liveness_event_loop_utilization_ratio", "Event-loop utilization reported by diagnostic liveness warnings.", livenessLabels(evt), numericValue(evt.eventLoopUtilization), RATIO_BUCKETS);
544
- store.histogram("openclaw_liveness_cpu_core_ratio", "CPU core ratio reported by diagnostic liveness warnings.", livenessLabels(evt), numericValue(evt.cpuCoreRatio), RATIO_BUCKETS);
526
+ store.histogram("openclaw_liveness_event_loop_utilization_ratio", "Event-loop utilization reported by diagnostic liveness warnings.", livenessLabels(evt), asNonNegativeFiniteNumber(evt.eventLoopUtilization), RATIO_BUCKETS);
527
+ store.histogram("openclaw_liveness_cpu_core_ratio", "CPU core ratio reported by diagnostic liveness warnings.", livenessLabels(evt), asNonNegativeFiniteNumber(evt.cpuCoreRatio), RATIO_BUCKETS);
545
528
  return;
546
529
  case "diagnostic.async_queue.dropped":
547
- store.counter("openclaw_diagnostic_async_queue_dropped_total", "Async diagnostic queue drops by dropped event class.", { drop_class: "total" }, numericValue(evt.droppedEvents));
548
- if (evt.droppedTrustedEvents !== void 0) store.counter("openclaw_diagnostic_async_queue_dropped_total", "Async diagnostic queue drops by dropped event class.", { drop_class: "trusted" }, numericValue(evt.droppedTrustedEvents));
549
- if (evt.droppedUntrustedEvents !== void 0) store.counter("openclaw_diagnostic_async_queue_dropped_total", "Async diagnostic queue drops by dropped event class.", { drop_class: "untrusted" }, numericValue(evt.droppedUntrustedEvents));
550
- if (evt.droppedPriorityEvents !== void 0) store.counter("openclaw_diagnostic_async_queue_dropped_total", "Async diagnostic queue drops by dropped event class.", { drop_class: "priority" }, numericValue(evt.droppedPriorityEvents));
551
- store.gauge("openclaw_diagnostic_async_queue_length", "Latest async diagnostic queue length after a drop summary.", {}, numericValue(evt.queueLength));
530
+ store.counter("openclaw_diagnostic_async_queue_dropped_total", "Async diagnostic queue drops by dropped event class.", { drop_class: "total" }, asNonNegativeFiniteNumber(evt.droppedEvents));
531
+ if (evt.droppedTrustedEvents !== void 0) store.counter("openclaw_diagnostic_async_queue_dropped_total", "Async diagnostic queue drops by dropped event class.", { drop_class: "trusted" }, asNonNegativeFiniteNumber(evt.droppedTrustedEvents));
532
+ if (evt.droppedUntrustedEvents !== void 0) store.counter("openclaw_diagnostic_async_queue_dropped_total", "Async diagnostic queue drops by dropped event class.", { drop_class: "untrusted" }, asNonNegativeFiniteNumber(evt.droppedUntrustedEvents));
533
+ if (evt.droppedPriorityEvents !== void 0) store.counter("openclaw_diagnostic_async_queue_dropped_total", "Async diagnostic queue drops by dropped event class.", { drop_class: "priority" }, asNonNegativeFiniteNumber(evt.droppedPriorityEvents));
534
+ store.gauge("openclaw_diagnostic_async_queue_length", "Latest async diagnostic queue length after a drop summary.", {}, asNonNegativeFiniteNumber(evt.queueLength));
552
535
  break;
553
536
  case "diagnostic.heartbeat": break;
554
537
  case "telemetry.exporter":
555
538
  store.counter("openclaw_telemetry_exporter_total", "Telemetry exporter lifecycle events.", {
556
- exporter: lowCardinalityLabel(evt.exporter),
557
- reason: lowCardinalityLabel(evt.reason, "none"),
539
+ exporter: normalizeDiagnosticValue(evt.exporter),
540
+ reason: normalizeDiagnosticValue(evt.reason, "none"),
558
541
  signal: evt.signal,
559
542
  status: evt.status
560
543
  });
561
544
  return;
562
545
  case "payload.large":
563
546
  store.counter("openclaw_payload_large_total", "Oversized payload diagnostics by surface and action.", payloadLargeLabels(evt));
564
- store.histogram("openclaw_payload_large_bytes", "Oversized payload byte sizes by surface and action.", payloadLargeLabels(evt), numericValue(evt.bytes), BYTE_BUCKETS);
547
+ store.histogram("openclaw_payload_large_bytes", "Oversized payload byte sizes by surface and action.", payloadLargeLabels(evt), asNonNegativeFiniteNumber(evt.bytes), BYTE_BUCKETS);
565
548
  default:
566
549
  }
567
550
  }
@@ -577,6 +560,7 @@ function createMetricsHandler(store) {
577
560
  res.statusCode = 200;
578
561
  res.setHeader("Cache-Control", "no-store");
579
562
  res.setHeader("Content-Type", "text/plain; version=0.0.4; charset=utf-8");
563
+ res.setHeader("Content-Length", String(Buffer.byteLength(body)));
580
564
  if (req.method === "HEAD") {
581
565
  res.end();
582
566
  return true;
@@ -588,6 +572,12 @@ function createMetricsHandler(store) {
588
572
  function createDiagnosticsPrometheusExporter() {
589
573
  const store = createPrometheusMetricStore();
590
574
  let unsubscribe;
575
+ let internalDiagnostics;
576
+ const reportExporterHealth = (update) => {
577
+ try {
578
+ internalDiagnostics?.reportExporterHealth?.(update);
579
+ } catch {}
580
+ };
591
581
  return {
592
582
  handler: createMetricsHandler(store),
593
583
  render: () => renderPrometheusMetrics(store),
@@ -606,7 +596,14 @@ function createDiagnosticsPrometheusExporter() {
606
596
  ctx.logger.error(`diagnostics-prometheus: event handler failed (${event.type}): ${safeErrorMessage(err)}`);
607
597
  }
608
598
  });
609
- ctx.internalDiagnostics?.emit({
599
+ internalDiagnostics = ctx.internalDiagnostics;
600
+ reportExporterHealth({
601
+ signal: "metrics",
602
+ transport: "prometheus-scrape",
603
+ status: "started",
604
+ reason: "configured"
605
+ });
606
+ internalDiagnostics.emit({
610
607
  type: "telemetry.exporter",
611
608
  exporter: "diagnostics-prometheus",
612
609
  signal: "metrics",
@@ -617,6 +614,18 @@ function createDiagnosticsPrometheusExporter() {
617
614
  stop() {
618
615
  unsubscribe?.();
619
616
  unsubscribe = void 0;
617
+ reportExporterHealth({
618
+ signal: "metrics",
619
+ transport: "prometheus-scrape",
620
+ status: "dropped"
621
+ });
622
+ internalDiagnostics?.emit({
623
+ type: "telemetry.exporter",
624
+ exporter: "diagnostics-prometheus",
625
+ signal: "metrics",
626
+ status: "dropped"
627
+ });
628
+ internalDiagnostics = void 0;
620
629
  store.reset();
621
630
  }
622
631
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/diagnostics-prometheus",
3
- "version": "2026.7.2-beta.5",
3
+ "version": "2026.8.1-beta.2",
4
4
  "description": "OpenClaw diagnostics Prometheus exporter for runtime metrics.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,10 +18,10 @@
18
18
  "minHostVersion": ">=2026.4.25"
19
19
  },
20
20
  "compat": {
21
- "pluginApi": ">=2026.7.2-beta.5"
21
+ "pluginApi": ">=2026.8.1-beta.2"
22
22
  },
23
23
  "build": {
24
- "openclawVersion": "2026.7.2-beta.5"
24
+ "openclawVersion": "2026.8.1-beta.2"
25
25
  },
26
26
  "release": {
27
27
  "publishToClawHub": true,
@@ -37,7 +37,7 @@
37
37
  "README.md"
38
38
  ],
39
39
  "peerDependencies": {
40
- "openclaw": ">=2026.7.2-beta.5"
40
+ "openclaw": ">=2026.8.1-beta.2"
41
41
  },
42
42
  "peerDependenciesMeta": {
43
43
  "openclaw": {