@openclaw/diagnostics-prometheus 2026.9.2 → 2026.9.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/dist/index.js +103 -72
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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";
|
|
3
|
+
import { isDiagnosticsEnabled, normalizeDiagnosticLane, normalizeDiagnosticValue } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
4
4
|
import { asNonNegativeFiniteNumber } from "openclaw/plugin-sdk/number-runtime";
|
|
5
|
+
import { getPluginRuntimeGatewayRequestScope } from "openclaw/plugin-sdk/plugin-runtime";
|
|
5
6
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
6
7
|
//#region extensions/diagnostics-prometheus/src/service.ts
|
|
7
8
|
const DURATION_BUCKETS_SECONDS = [
|
|
@@ -70,7 +71,9 @@ function seconds(ms) {
|
|
|
70
71
|
return value === void 0 ? void 0 : value / 1e3;
|
|
71
72
|
}
|
|
72
73
|
function sortedLabels(labels) {
|
|
73
|
-
|
|
74
|
+
const entries = Object.entries(labels);
|
|
75
|
+
entries.sort(([left], [right]) => left.localeCompare(right));
|
|
76
|
+
return entries;
|
|
74
77
|
}
|
|
75
78
|
function metricKey(name, labels) {
|
|
76
79
|
return `${name}|${JSON.stringify(sortedLabels(labels))}`;
|
|
@@ -81,10 +84,13 @@ function escapeHelp(value) {
|
|
|
81
84
|
function escapeLabelValue(value) {
|
|
82
85
|
return value.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/"/g, "\\\"");
|
|
83
86
|
}
|
|
87
|
+
function formatLabelEntry([key, value]) {
|
|
88
|
+
return `${key}="${escapeLabelValue(value)}"`;
|
|
89
|
+
}
|
|
84
90
|
function formatLabels(labels) {
|
|
85
91
|
const entries = sortedLabels(labels);
|
|
86
92
|
if (entries.length === 0) return "";
|
|
87
|
-
return `{${entries.map(
|
|
93
|
+
return `{${entries.map(formatLabelEntry).join(",")}}`;
|
|
88
94
|
}
|
|
89
95
|
function formatPrometheusNumber(value) {
|
|
90
96
|
if (!Number.isFinite(value)) return "0";
|
|
@@ -95,9 +101,8 @@ function createPrometheusMetricStore() {
|
|
|
95
101
|
const gauges = /* @__PURE__ */ new Map();
|
|
96
102
|
const histograms = /* @__PURE__ */ new Map();
|
|
97
103
|
let droppedSeries = 0;
|
|
98
|
-
const canCreateSeries = (map, key
|
|
104
|
+
const canCreateSeries = (map, key) => {
|
|
99
105
|
if (map.has(key)) return true;
|
|
100
|
-
if (metricName === DROPPED_SERIES_COUNTER_NAME) return true;
|
|
101
106
|
if (counters.size + gauges.size + histograms.size < MAX_PROMETHEUS_SERIES) return true;
|
|
102
107
|
droppedSeries += 1;
|
|
103
108
|
return false;
|
|
@@ -105,7 +110,7 @@ function createPrometheusMetricStore() {
|
|
|
105
110
|
const counter = (name, help, labels, amount = 1) => {
|
|
106
111
|
if (!Number.isFinite(amount) || amount <= 0) return;
|
|
107
112
|
const key = metricKey(name, labels);
|
|
108
|
-
if (!canCreateSeries(counters, key
|
|
113
|
+
if (!canCreateSeries(counters, key)) return;
|
|
109
114
|
const existing = counters.get(key);
|
|
110
115
|
if (existing) {
|
|
111
116
|
existing.value += amount;
|
|
@@ -120,7 +125,7 @@ function createPrometheusMetricStore() {
|
|
|
120
125
|
const gauge = (name, help, labels, value) => {
|
|
121
126
|
if (value === void 0 || !Number.isFinite(value)) return;
|
|
122
127
|
const key = metricKey(name, labels);
|
|
123
|
-
if (!canCreateSeries(gauges, key
|
|
128
|
+
if (!canCreateSeries(gauges, key)) return;
|
|
124
129
|
gauges.set(key, {
|
|
125
130
|
help,
|
|
126
131
|
labels,
|
|
@@ -130,7 +135,7 @@ function createPrometheusMetricStore() {
|
|
|
130
135
|
const histogram = (name, help, labels, value, buckets = DURATION_BUCKETS_SECONDS) => {
|
|
131
136
|
if (value === void 0 || !Number.isFinite(value) || value < 0) return;
|
|
132
137
|
const key = metricKey(name, labels);
|
|
133
|
-
if (!canCreateSeries(histograms, key
|
|
138
|
+
if (!canCreateSeries(histograms, key)) return;
|
|
134
139
|
let sample = histograms.get(key);
|
|
135
140
|
if (!sample) {
|
|
136
141
|
sample = {
|
|
@@ -151,16 +156,16 @@ function createPrometheusMetricStore() {
|
|
|
151
156
|
}
|
|
152
157
|
};
|
|
153
158
|
const snapshot = () => {
|
|
154
|
-
const counterSnapshot =
|
|
155
|
-
if (droppedSeries > 0) counterSnapshot.
|
|
159
|
+
const counterSnapshot = [...counters];
|
|
160
|
+
if (droppedSeries > 0) counterSnapshot.push([metricKey(DROPPED_SERIES_COUNTER_NAME, {}), {
|
|
156
161
|
help: "Prometheus metric series dropped because the exporter series cap was reached.",
|
|
157
162
|
labels: {},
|
|
158
163
|
value: droppedSeries
|
|
159
|
-
});
|
|
164
|
+
}]);
|
|
160
165
|
return {
|
|
161
166
|
counters: counterSnapshot,
|
|
162
|
-
gauges:
|
|
163
|
-
histograms:
|
|
167
|
+
gauges: [...gauges],
|
|
168
|
+
histograms: [...histograms]
|
|
164
169
|
};
|
|
165
170
|
};
|
|
166
171
|
const reset = () => {
|
|
@@ -194,36 +199,35 @@ function renderPrometheusMetrics(store) {
|
|
|
194
199
|
lines.push(`# HELP ${name} ${escapeHelp(help)}`);
|
|
195
200
|
lines.push(`# TYPE ${name} ${type}`);
|
|
196
201
|
};
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
for (const [key, sample] of gaugeEntries) {
|
|
205
|
-
const name = key.split("|", 1)[0] ?? "";
|
|
206
|
-
emitHeader(name, "gauge", sample.help);
|
|
207
|
-
lines.push(`${name}${formatLabels(sample.labels)} ${formatPrometheusNumber(sample.value)}`);
|
|
202
|
+
for (const [type, entries] of [["counter", snapshot.counters], ["gauge", snapshot.gauges]]) {
|
|
203
|
+
entries.sort(([left], [right]) => left.localeCompare(right));
|
|
204
|
+
for (const [key, sample] of entries) {
|
|
205
|
+
const name = key.split("|", 1)[0] ?? "";
|
|
206
|
+
emitHeader(name, type, sample.help);
|
|
207
|
+
lines.push(`${name}${formatLabels(sample.labels)} ${formatPrometheusNumber(sample.value)}`);
|
|
208
|
+
}
|
|
208
209
|
}
|
|
209
|
-
|
|
210
|
-
for (const [key, sample] of
|
|
210
|
+
snapshot.histograms.sort(([left], [right]) => left.localeCompare(right));
|
|
211
|
+
for (const [key, sample] of snapshot.histograms) {
|
|
211
212
|
const name = key.split("|", 1)[0] ?? "";
|
|
212
213
|
emitHeader(name, "histogram", sample.help);
|
|
214
|
+
const labels = formatLabels(sample.labels);
|
|
215
|
+
const bucketLabels = sortedLabels({
|
|
216
|
+
...sample.labels,
|
|
217
|
+
le: ""
|
|
218
|
+
});
|
|
219
|
+
const boundIndex = bucketLabels.findIndex(([labelKey]) => labelKey === "le");
|
|
220
|
+
const bucketFragments = bucketLabels.map(formatLabelEntry);
|
|
213
221
|
for (let index = 0; index < sample.buckets.length; index += 1) {
|
|
214
222
|
const bucket = sample.buckets[index];
|
|
215
223
|
if (bucket === void 0) continue;
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
le: String(bucket)
|
|
219
|
-
})} ${formatPrometheusNumber(sample.counts[index] ?? 0)}`);
|
|
224
|
+
bucketFragments[boundIndex] = `le="${String(bucket)}"`;
|
|
225
|
+
lines.push(`${name}_bucket{${bucketFragments.join(",")}} ${formatPrometheusNumber(sample.counts[index] ?? 0)}`);
|
|
220
226
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
lines.push(`${name}_sum${formatLabels(sample.labels)} ${formatPrometheusNumber(sample.sum)}`);
|
|
226
|
-
lines.push(`${name}_count${formatLabels(sample.labels)} ${formatPrometheusNumber(sample.count)}`);
|
|
227
|
+
bucketFragments[boundIndex] = "le=\"+Inf\"";
|
|
228
|
+
lines.push(`${name}_bucket{${bucketFragments.join(",")}} ${formatPrometheusNumber(sample.count)}`);
|
|
229
|
+
lines.push(`${name}_sum${labels} ${formatPrometheusNumber(sample.sum)}`);
|
|
230
|
+
lines.push(`${name}_count${labels} ${formatPrometheusNumber(sample.count)}`);
|
|
227
231
|
}
|
|
228
232
|
lines.push("");
|
|
229
233
|
return lines.join("\n");
|
|
@@ -373,6 +377,9 @@ function recordModelUsage(store, evt) {
|
|
|
373
377
|
function recordDiagnosticEvent(store, evt, metadata) {
|
|
374
378
|
if (!shouldRecordDiagnosticEvent(metadata)) return;
|
|
375
379
|
switch (evt.type) {
|
|
380
|
+
case "diagnostic.gc":
|
|
381
|
+
store.histogram("openclaw_gc_duration_seconds", "Elapsed garbage collection duration in seconds for the hosting JavaScript isolate.", {}, seconds(evt.durationMs));
|
|
382
|
+
return;
|
|
376
383
|
case "gateway.event_loop.sample":
|
|
377
384
|
store.histogram("openclaw_gateway_event_loop_delay_max_seconds", "Maximum event-loop delay per completed Gateway observation window in seconds.", {}, seconds(evt.delayMaxMs));
|
|
378
385
|
store.counter("openclaw_gateway_event_loop_observed_seconds_total", "Elapsed seconds covered by completed Gateway event-loop observation windows.", {}, evt.intervalMs / 1e3);
|
|
@@ -425,18 +432,16 @@ function recordDiagnosticEvent(store, evt, metadata) {
|
|
|
425
432
|
store.histogram("openclaw_harness_run_duration_seconds", "Agent harness run duration in seconds.", harnessLabels(evt), seconds(evt.durationMs));
|
|
426
433
|
store.counter("openclaw_harness_run_total", "Agent harness runs completed by outcome.", harnessLabels(evt));
|
|
427
434
|
return;
|
|
428
|
-
case "message.processed":
|
|
429
|
-
|
|
430
|
-
channel: normalizeDiagnosticValue(evt.channel),
|
|
431
|
-
outcome: evt.outcome,
|
|
432
|
-
reason: normalizeDiagnosticValue(evt.reason, "none")
|
|
433
|
-
});
|
|
434
|
-
store.histogram("openclaw_message_processed_duration_seconds", "Inbound message processing duration in seconds.", {
|
|
435
|
+
case "message.processed": {
|
|
436
|
+
const labels = {
|
|
435
437
|
channel: normalizeDiagnosticValue(evt.channel),
|
|
436
438
|
outcome: evt.outcome,
|
|
437
439
|
reason: normalizeDiagnosticValue(evt.reason, "none")
|
|
438
|
-
}
|
|
440
|
+
};
|
|
441
|
+
store.counter("openclaw_message_processed_total", "Inbound messages processed by outcome.", labels);
|
|
442
|
+
store.histogram("openclaw_message_processed_duration_seconds", "Inbound message processing duration in seconds.", labels, seconds(evt.durationMs));
|
|
439
443
|
return;
|
|
444
|
+
}
|
|
440
445
|
case "webhook.received":
|
|
441
446
|
store.counter("openclaw_webhook_received_total", "Webhook requests received by channel and update type.", webhookLabels(evt));
|
|
442
447
|
return;
|
|
@@ -464,35 +469,29 @@ function recordDiagnosticEvent(store, evt, metadata) {
|
|
|
464
469
|
source: normalizeDiagnosticValue(evt.source)
|
|
465
470
|
});
|
|
466
471
|
return;
|
|
467
|
-
case "message.dispatch.completed":
|
|
468
|
-
|
|
472
|
+
case "message.dispatch.completed": {
|
|
473
|
+
const labels = {
|
|
469
474
|
channel: normalizeDiagnosticValue(evt.channel),
|
|
470
475
|
outcome: evt.outcome,
|
|
471
476
|
reason: normalizeDiagnosticValue(evt.reason, "none"),
|
|
472
477
|
source: normalizeDiagnosticValue(evt.source)
|
|
473
|
-
}
|
|
474
|
-
store.
|
|
475
|
-
|
|
476
|
-
outcome: evt.outcome,
|
|
477
|
-
reason: normalizeDiagnosticValue(evt.reason, "none"),
|
|
478
|
-
source: normalizeDiagnosticValue(evt.source)
|
|
479
|
-
}, seconds(evt.durationMs));
|
|
478
|
+
};
|
|
479
|
+
store.counter("openclaw_message_dispatch_completed_total", "Inbound message dispatch attempts completed by outcome.", labels);
|
|
480
|
+
store.histogram("openclaw_message_dispatch_duration_seconds", "Inbound message dispatch duration in seconds.", labels, seconds(evt.durationMs));
|
|
480
481
|
return;
|
|
482
|
+
}
|
|
481
483
|
case "message.delivery.completed":
|
|
482
|
-
case "message.delivery.error":
|
|
483
|
-
|
|
484
|
+
case "message.delivery.error": {
|
|
485
|
+
const labels = {
|
|
484
486
|
channel: normalizeDiagnosticValue(evt.channel),
|
|
485
487
|
delivery_kind: normalizeDiagnosticValue(evt.deliveryKind, "other"),
|
|
486
488
|
error_category: evt.type === "message.delivery.error" ? normalizeDiagnosticValue(evt.errorCategory, "other") : "none",
|
|
487
489
|
outcome: evt.type === "message.delivery.error" ? "error" : "completed"
|
|
488
|
-
}
|
|
489
|
-
store.
|
|
490
|
-
|
|
491
|
-
delivery_kind: normalizeDiagnosticValue(evt.deliveryKind, "other"),
|
|
492
|
-
error_category: evt.type === "message.delivery.error" ? normalizeDiagnosticValue(evt.errorCategory, "other") : "none",
|
|
493
|
-
outcome: evt.type === "message.delivery.error" ? "error" : "completed"
|
|
494
|
-
}, seconds(evt.durationMs));
|
|
490
|
+
};
|
|
491
|
+
store.counter("openclaw_message_delivery_total", "Outbound message delivery attempts by outcome.", labels);
|
|
492
|
+
store.histogram("openclaw_message_delivery_duration_seconds", "Outbound message delivery duration in seconds.", labels, seconds(evt.durationMs));
|
|
495
493
|
return;
|
|
494
|
+
}
|
|
496
495
|
case "talk.event":
|
|
497
496
|
store.counter("openclaw_talk_event_total", "Talk events emitted by type.", talkLabels(evt));
|
|
498
497
|
store.histogram("openclaw_talk_event_duration_seconds", "Talk event duration in seconds when reported.", talkLabels(evt), seconds(evt.durationMs));
|
|
@@ -527,9 +526,11 @@ function recordDiagnosticEvent(store, evt, metadata) {
|
|
|
527
526
|
});
|
|
528
527
|
return;
|
|
529
528
|
case "diagnostic.memory.sample":
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
529
|
+
for (const [kind, field] of [
|
|
530
|
+
["rss", "rssBytes"],
|
|
531
|
+
["heap_total", "heapTotalBytes"],
|
|
532
|
+
["heap_used", "heapUsedBytes"]
|
|
533
|
+
]) store.gauge("openclaw_memory_bytes", "Latest process memory usage by memory kind.", { kind }, evt.memory[field]);
|
|
533
534
|
store.histogram("openclaw_memory_rss_bytes", "RSS memory sample distribution in bytes.", {}, asNonNegativeFiniteNumber(evt.memory.rssBytes), BYTE_BUCKETS);
|
|
534
535
|
return;
|
|
535
536
|
case "diagnostic.memory.pressure":
|
|
@@ -540,19 +541,26 @@ function recordDiagnosticEvent(store, evt, metadata) {
|
|
|
540
541
|
return;
|
|
541
542
|
case "diagnostic.liveness.warning":
|
|
542
543
|
store.counter("openclaw_liveness_warning_total", "Diagnostic liveness warning events.", livenessLabels(evt));
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
544
|
+
for (const state of [
|
|
545
|
+
"active",
|
|
546
|
+
"waiting",
|
|
547
|
+
"queued"
|
|
548
|
+
]) store.gauge("openclaw_liveness_sessions", "Latest session counts reported with diagnostic liveness warnings.", { state }, asNonNegativeFiniteNumber(evt[state]));
|
|
546
549
|
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));
|
|
547
550
|
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));
|
|
548
551
|
store.histogram("openclaw_liveness_event_loop_utilization_ratio", "Event-loop utilization reported by diagnostic liveness warnings.", livenessLabels(evt), asNonNegativeFiniteNumber(evt.eventLoopUtilization), RATIO_BUCKETS);
|
|
549
552
|
store.histogram("openclaw_liveness_cpu_core_ratio", "Whole-process CPU usage in core equivalents, including worker and native threads; can exceed 1.", livenessLabels(evt), asNonNegativeFiniteNumber(evt.cpuCoreRatio), RATIO_BUCKETS);
|
|
550
553
|
return;
|
|
551
554
|
case "diagnostic.async_queue.dropped":
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
555
|
+
for (const [dropClass, field] of [
|
|
556
|
+
["total", "droppedEvents"],
|
|
557
|
+
["trusted", "droppedTrustedEvents"],
|
|
558
|
+
["untrusted", "droppedUntrustedEvents"],
|
|
559
|
+
["priority", "droppedPriorityEvents"]
|
|
560
|
+
]) {
|
|
561
|
+
if (field !== "droppedEvents" && evt[field] === void 0) continue;
|
|
562
|
+
store.counter("openclaw_diagnostic_async_queue_dropped_total", "Async diagnostic queue drops by dropped event class.", { drop_class: dropClass }, asNonNegativeFiniteNumber(evt[field]));
|
|
563
|
+
}
|
|
556
564
|
store.gauge("openclaw_diagnostic_async_queue_length", "Latest async diagnostic queue length after a drop summary.", {}, asNonNegativeFiniteNumber(evt.queueLength));
|
|
557
565
|
break;
|
|
558
566
|
case "diagnostic.heartbeat": break;
|
|
@@ -569,8 +577,26 @@ function recordDiagnosticEvent(store, evt, metadata) {
|
|
|
569
577
|
store.histogram("openclaw_payload_large_bytes", "Oversized payload byte sizes by surface and action.", payloadLargeLabels(evt), asNonNegativeFiniteNumber(evt.bytes), BYTE_BUCKETS);
|
|
570
578
|
}
|
|
571
579
|
}
|
|
580
|
+
const METRICS_READ_SCOPE = "operator.read";
|
|
581
|
+
const METRICS_READ_IMPLYING_SCOPES = [
|
|
582
|
+
METRICS_READ_SCOPE,
|
|
583
|
+
"operator.write",
|
|
584
|
+
"operator.admin"
|
|
585
|
+
];
|
|
586
|
+
function hasMetricsReadScope() {
|
|
587
|
+
const runtimeScopes = getPluginRuntimeGatewayRequestScope()?.client?.connect?.scopes;
|
|
588
|
+
const scopes = Array.isArray(runtimeScopes) ? runtimeScopes : [];
|
|
589
|
+
return METRICS_READ_IMPLYING_SCOPES.some((scope) => scopes.includes(scope));
|
|
590
|
+
}
|
|
572
591
|
function createMetricsHandler(store) {
|
|
573
592
|
return (req, res) => {
|
|
593
|
+
if (!hasMetricsReadScope()) {
|
|
594
|
+
res.statusCode = 403;
|
|
595
|
+
res.setHeader("Cache-Control", "no-store");
|
|
596
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
597
|
+
res.end(`missing scope: ${METRICS_READ_SCOPE}`);
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
574
600
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
575
601
|
res.statusCode = 405;
|
|
576
602
|
res.setHeader("Allow", "GET, HEAD");
|
|
@@ -610,6 +636,11 @@ function createDiagnosticsPrometheusExporter() {
|
|
|
610
636
|
ctx.logger.error("diagnostics-prometheus: internal diagnostics capability unavailable");
|
|
611
637
|
return;
|
|
612
638
|
}
|
|
639
|
+
const identity = isDiagnosticsEnabled(ctx.config) ? ctx.internalDiagnostics?.getRuntimeIdentity?.() : void 0;
|
|
640
|
+
if (identity) store.gauge("openclaw_gateway_build_info", "Identity of the hosting process and its loaded build; not a health or exporter epoch.", {
|
|
641
|
+
process_instance_id: identity.processInstanceId,
|
|
642
|
+
...identity.buildId ? { build_id: identity.buildId } : {}
|
|
643
|
+
}, 1);
|
|
613
644
|
unsubscribe = subscribe((event, metadata) => {
|
|
614
645
|
try {
|
|
615
646
|
recordDiagnosticEvent(store, event, metadata);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/diagnostics-prometheus",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.3",
|
|
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.9.
|
|
21
|
+
"pluginApi": ">=2026.9.3"
|
|
22
22
|
},
|
|
23
23
|
"build": {
|
|
24
|
-
"openclawVersion": "2026.9.
|
|
24
|
+
"openclawVersion": "2026.9.3"
|
|
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.9.
|
|
40
|
+
"openclaw": ">=2026.9.3"
|
|
41
41
|
},
|
|
42
42
|
"peerDependenciesMeta": {
|
|
43
43
|
"openclaw": {
|