coze_lab 0.1.28 → 0.1.31
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/index.js
CHANGED
|
@@ -5848,17 +5848,47 @@ async function verifyOpenClawTraceLink(cloud) {
|
|
|
5848
5848
|
return { success: false, status: 0, body: 'plugin authorization missing' };
|
|
5849
5849
|
}
|
|
5850
5850
|
|
|
5851
|
-
// 1) 用插件【实际配置的】token
|
|
5851
|
+
// 1) 用插件【实际配置的】token 打一条最小 OTLP trace ——这才是运行时真实用的那个 token。
|
|
5852
|
+
// 不能发空 resourceSpans,CozeLoop 会返回 "unknown event type",导致自检假失败。
|
|
5852
5853
|
const authHeader = pcfg.authorization; // 形如 "Bearer czu_xxx"
|
|
5853
5854
|
const tracesUrl = (pcfg.endpoint
|
|
5854
5855
|
? `${String(pcfg.endpoint).replace(/\/+$/, '')}/v1/traces`
|
|
5855
5856
|
: getOtelTracesUrl(cloud));
|
|
5856
5857
|
const workspaceId = pcfg.workspaceId || WORKSPACE_ID;
|
|
5858
|
+
const traceId = crypto.randomBytes(16).toString('hex');
|
|
5859
|
+
const spanId = crypto.randomBytes(8).toString('hex');
|
|
5860
|
+
const nowNs = String(Date.now() * 1_000_000);
|
|
5861
|
+
const pair = crypto.randomBytes(6).toString('hex');
|
|
5862
|
+
const otlpBody = {
|
|
5863
|
+
resourceSpans: [{
|
|
5864
|
+
resource: {
|
|
5865
|
+
attributes: [
|
|
5866
|
+
{ key: 'service.name', value: { stringValue: 'cozelab-onboard-openclaw' } },
|
|
5867
|
+
],
|
|
5868
|
+
},
|
|
5869
|
+
scopeSpans: [{
|
|
5870
|
+
scope: { name: 'cozelab-onboard-openclaw-selfcheck' },
|
|
5871
|
+
spans: [{
|
|
5872
|
+
traceId,
|
|
5873
|
+
spanId,
|
|
5874
|
+
name: 'cozelab-onboard-openclaw-selfcheck',
|
|
5875
|
+
kind: 1,
|
|
5876
|
+
startTimeUnixNano: nowNs,
|
|
5877
|
+
endTimeUnixNano: nowNs,
|
|
5878
|
+
status: { code: 1 },
|
|
5879
|
+
attributes: [
|
|
5880
|
+
{ key: 'pair_code', value: { stringValue: pair } },
|
|
5881
|
+
{ key: 'source', value: { stringValue: 'cozelab-onboard-openclaw' } },
|
|
5882
|
+
],
|
|
5883
|
+
}],
|
|
5884
|
+
}],
|
|
5885
|
+
}],
|
|
5886
|
+
};
|
|
5857
5887
|
let res;
|
|
5858
5888
|
try {
|
|
5859
5889
|
res = await httpsPost(
|
|
5860
5890
|
tracesUrl,
|
|
5861
|
-
|
|
5891
|
+
otlpBody,
|
|
5862
5892
|
{ Authorization: authHeader, 'cozeloop-workspace-id': String(workspaceId) },
|
|
5863
5893
|
);
|
|
5864
5894
|
} catch (e) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { trace, context, SpanKind, SpanStatusCode } from "@opentelemetry/api";
|
|
2
2
|
import { BasicTracerProvider, BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
3
|
-
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
4
3
|
import { Resource } from "@opentelemetry/resources";
|
|
5
4
|
import { ATTR_SERVICE_NAME, ATTR_SERVICE_INSTANCE_ID } from "@opentelemetry/semantic-conventions";
|
|
6
5
|
import { hostname } from "os";
|
|
@@ -8,6 +7,7 @@ import { basename, join } from "path";
|
|
|
8
7
|
import { createRequire } from "node:module";
|
|
9
8
|
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
10
9
|
import { homedir } from "os";
|
|
10
|
+
import http from "http";
|
|
11
11
|
import https from "https";
|
|
12
12
|
|
|
13
13
|
const require = createRequire(import.meta.url);
|
|
@@ -78,6 +78,193 @@ async function getRefreshedToken(currentAuthorization, opts = {}) {
|
|
|
78
78
|
}
|
|
79
79
|
// ─────────────────────────────────────────────────────────────────────────
|
|
80
80
|
|
|
81
|
+
const EXPORT_SUCCESS = 0;
|
|
82
|
+
const EXPORT_FAILED = 1;
|
|
83
|
+
|
|
84
|
+
function normalizeTraceUrl(endpoint) {
|
|
85
|
+
const base = String(endpoint || "").replace(/\/+$/, "");
|
|
86
|
+
return base.endsWith("/v1/traces") ? base : `${base}/v1/traces`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function hrTimeToUnixNano(time) {
|
|
90
|
+
if (Array.isArray(time)) {
|
|
91
|
+
return (BigInt(time[0]) * 1000000000n + BigInt(time[1])).toString();
|
|
92
|
+
}
|
|
93
|
+
const millis = time instanceof Date ? time.getTime() : Number(time || Date.now());
|
|
94
|
+
return (BigInt(Math.trunc(millis)) * 1000000n).toString();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function spanKindToOtlp(kind) {
|
|
98
|
+
switch (kind) {
|
|
99
|
+
case SpanKind.INTERNAL: return 1;
|
|
100
|
+
case SpanKind.SERVER: return 2;
|
|
101
|
+
case SpanKind.CLIENT: return 3;
|
|
102
|
+
case SpanKind.PRODUCER: return 4;
|
|
103
|
+
case SpanKind.CONSUMER: return 5;
|
|
104
|
+
default: return 0;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function scalarToAnyValue(value) {
|
|
109
|
+
if (value === undefined || value === null)
|
|
110
|
+
return undefined;
|
|
111
|
+
if (typeof value === "string")
|
|
112
|
+
return { stringValue: value };
|
|
113
|
+
if (typeof value === "boolean")
|
|
114
|
+
return { boolValue: value };
|
|
115
|
+
if (typeof value === "number") {
|
|
116
|
+
if (Number.isInteger(value))
|
|
117
|
+
return { intValue: String(value) };
|
|
118
|
+
return { doubleValue: value };
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
return { stringValue: JSON.stringify(value) };
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return { stringValue: String(value) };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function valueToAnyValue(value) {
|
|
129
|
+
if (Array.isArray(value)) {
|
|
130
|
+
const values = value.map(scalarToAnyValue).filter(Boolean);
|
|
131
|
+
return { arrayValue: { values } };
|
|
132
|
+
}
|
|
133
|
+
return scalarToAnyValue(value);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function attributesToOtlp(attributes) {
|
|
137
|
+
const out = [];
|
|
138
|
+
for (const [key, value] of Object.entries(attributes || {})) {
|
|
139
|
+
const otlpValue = valueToAnyValue(value);
|
|
140
|
+
if (otlpValue) {
|
|
141
|
+
out.push({ key, value: otlpValue });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function spanToOtlp(span) {
|
|
148
|
+
const spanContext = span.spanContext();
|
|
149
|
+
const otlpSpan = {
|
|
150
|
+
traceId: spanContext.traceId,
|
|
151
|
+
spanId: spanContext.spanId,
|
|
152
|
+
name: span.name,
|
|
153
|
+
kind: spanKindToOtlp(span.kind),
|
|
154
|
+
startTimeUnixNano: hrTimeToUnixNano(span.startTime),
|
|
155
|
+
endTimeUnixNano: hrTimeToUnixNano(span.endTime),
|
|
156
|
+
attributes: attributesToOtlp(span.attributes),
|
|
157
|
+
status: { code: span.status?.code ?? 0 },
|
|
158
|
+
};
|
|
159
|
+
if (span.parentSpanId) {
|
|
160
|
+
otlpSpan.parentSpanId = span.parentSpanId;
|
|
161
|
+
}
|
|
162
|
+
if (span.status?.message) {
|
|
163
|
+
otlpSpan.status.message = span.status.message;
|
|
164
|
+
}
|
|
165
|
+
return otlpSpan;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function buildOtlpTraceRequest(spans) {
|
|
169
|
+
const resourceGroups = new Map();
|
|
170
|
+
for (const span of spans) {
|
|
171
|
+
const resourceAttrs = attributesToOtlp(span.resource?.attributes || {});
|
|
172
|
+
const scope = span.instrumentationLibrary || {};
|
|
173
|
+
const scopeData = {
|
|
174
|
+
name: scope.name || "openclaw-cozeloop-trace",
|
|
175
|
+
version: scope.version || PLUGIN_VERSION,
|
|
176
|
+
};
|
|
177
|
+
if (scope.schemaUrl) {
|
|
178
|
+
scopeData.schemaUrl = scope.schemaUrl;
|
|
179
|
+
}
|
|
180
|
+
const resourceKey = JSON.stringify(resourceAttrs);
|
|
181
|
+
let resourceGroup = resourceGroups.get(resourceKey);
|
|
182
|
+
if (!resourceGroup) {
|
|
183
|
+
resourceGroup = {
|
|
184
|
+
resource: { attributes: resourceAttrs },
|
|
185
|
+
scopeGroups: new Map(),
|
|
186
|
+
};
|
|
187
|
+
resourceGroups.set(resourceKey, resourceGroup);
|
|
188
|
+
}
|
|
189
|
+
const scopeKey = JSON.stringify(scopeData);
|
|
190
|
+
let scopeGroup = resourceGroup.scopeGroups.get(scopeKey);
|
|
191
|
+
if (!scopeGroup) {
|
|
192
|
+
scopeGroup = { scope: scopeData, spans: [] };
|
|
193
|
+
resourceGroup.scopeGroups.set(scopeKey, scopeGroup);
|
|
194
|
+
}
|
|
195
|
+
scopeGroup.spans.push(spanToOtlp(span));
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
resourceSpans: Array.from(resourceGroups.values()).map((resourceGroup) => ({
|
|
199
|
+
resource: resourceGroup.resource,
|
|
200
|
+
scopeSpans: Array.from(resourceGroup.scopeGroups.values()),
|
|
201
|
+
})),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function postJson(url, body, headers) {
|
|
206
|
+
return new Promise((resolve, reject) => {
|
|
207
|
+
const data = JSON.stringify(body);
|
|
208
|
+
const u = new URL(url);
|
|
209
|
+
const client = u.protocol === "http:" ? http : https;
|
|
210
|
+
const req = client.request({
|
|
211
|
+
protocol: u.protocol,
|
|
212
|
+
hostname: u.hostname,
|
|
213
|
+
port: u.port || undefined,
|
|
214
|
+
path: u.pathname + u.search,
|
|
215
|
+
method: "POST",
|
|
216
|
+
headers: {
|
|
217
|
+
"Content-Type": "application/json",
|
|
218
|
+
"Content-Length": Buffer.byteLength(data),
|
|
219
|
+
...(headers || {}),
|
|
220
|
+
},
|
|
221
|
+
}, (res) => {
|
|
222
|
+
let buf = "";
|
|
223
|
+
res.on("data", c => buf += c);
|
|
224
|
+
res.on("end", () => resolve({ status: res.statusCode || 0, body: buf }));
|
|
225
|
+
});
|
|
226
|
+
req.on("error", reject);
|
|
227
|
+
req.setTimeout(15000, () => req.destroy(new Error("OTLP JSON export timed out")));
|
|
228
|
+
req.write(data);
|
|
229
|
+
req.end();
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
class JsonOtlpTraceExporter {
|
|
234
|
+
constructor(config) {
|
|
235
|
+
this.url = normalizeTraceUrl(config.url);
|
|
236
|
+
this.headers = config.headers || {};
|
|
237
|
+
this.logger = config.logger;
|
|
238
|
+
this.shutdownRequested = false;
|
|
239
|
+
}
|
|
240
|
+
export(spans, resultCallback) {
|
|
241
|
+
if (!spans || spans.length === 0 || this.shutdownRequested) {
|
|
242
|
+
resultCallback({ code: EXPORT_SUCCESS });
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
this.postSpans(spans)
|
|
246
|
+
.then(() => resultCallback({ code: EXPORT_SUCCESS }))
|
|
247
|
+
.catch((err) => {
|
|
248
|
+
this.logger?.error?.(`[CozeloopTrace] OTLP JSON export failed: ${err?.message || err}`);
|
|
249
|
+
resultCallback({ code: EXPORT_FAILED, error: err });
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
async postSpans(spans) {
|
|
253
|
+
const body = buildOtlpTraceRequest(spans);
|
|
254
|
+
const res = await postJson(this.url, body, this.headers);
|
|
255
|
+
if (res.status < 200 || res.status >= 300) {
|
|
256
|
+
const snippet = String(res.body || "").slice(0, 300);
|
|
257
|
+
throw new Error(`HTTP ${res.status}${snippet ? `: ${snippet}` : ""}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async forceFlush() {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
async shutdown() {
|
|
264
|
+
this.shutdownRequested = true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
81
268
|
export class CozeloopExporter {
|
|
82
269
|
config;
|
|
83
270
|
api;
|
|
@@ -173,8 +360,9 @@ export class CozeloopExporter {
|
|
|
173
360
|
const authorization = this.config.authorization;
|
|
174
361
|
const workspaceId = this.config.workspaceId;
|
|
175
362
|
this.api.logger.info(`[CozeloopTrace] Using authorization, workspaceId=${workspaceId}, tokenLength=${authorization?.length}`);
|
|
176
|
-
const exporter = new
|
|
363
|
+
const exporter = new JsonOtlpTraceExporter({
|
|
177
364
|
url: `${this.config.endpoint}/v1/traces`,
|
|
365
|
+
logger: this.api.logger,
|
|
178
366
|
headers: {
|
|
179
367
|
"Authorization": authorization,
|
|
180
368
|
"cozeloop-workspace-id": workspaceId,
|
|
@@ -441,12 +629,22 @@ export class CozeloopExporter {
|
|
|
441
629
|
}
|
|
442
630
|
async flush() {
|
|
443
631
|
if (this.provider) {
|
|
444
|
-
|
|
632
|
+
try {
|
|
633
|
+
await this.provider.forceFlush();
|
|
634
|
+
}
|
|
635
|
+
catch (err) {
|
|
636
|
+
this.api.logger.error(`[CozeloopTrace] Flush failed: ${err?.message || err}`);
|
|
637
|
+
}
|
|
445
638
|
}
|
|
446
639
|
}
|
|
447
640
|
async dispose() {
|
|
448
641
|
if (this.provider) {
|
|
449
|
-
|
|
642
|
+
try {
|
|
643
|
+
await this.provider.shutdown();
|
|
644
|
+
}
|
|
645
|
+
catch (err) {
|
|
646
|
+
this.api.logger.error(`[CozeloopTrace] Shutdown failed: ${err?.message || err}`);
|
|
647
|
+
}
|
|
450
648
|
}
|
|
451
649
|
}
|
|
452
650
|
}
|
|
@@ -481,15 +481,21 @@ function resolveCozeContext(input, sessionId) {
|
|
|
481
481
|
// use this to ensure every span lands in the same Trace.
|
|
482
482
|
let activeAgentCtx;
|
|
483
483
|
let activeAgentChannelId;
|
|
484
|
+
const activeAgentContextByKey = new Map();
|
|
484
485
|
// Latest user input captured from message_received, independent of any ctx.
|
|
485
486
|
// Used by ensureRootSpan as a reliable fallback for the root span's input.
|
|
486
487
|
let lastUserInput;
|
|
487
|
-
|
|
488
|
+
const lastUserContextByKey = new Map();
|
|
489
|
+
const pendingToolCallsByKey = new Map();
|
|
488
490
|
function resolveOpenclawSessionId(hookCtx, eventSessionId) {
|
|
489
491
|
const raw = hookCtx.sessionId?.trim() || eventSessionId?.trim();
|
|
490
|
-
if (
|
|
491
|
-
return
|
|
492
|
-
|
|
492
|
+
if (raw)
|
|
493
|
+
return raw;
|
|
494
|
+
const sessionKey = hookCtx.sessionKey?.trim();
|
|
495
|
+
const match = sessionKey?.match(/^agent:[^:]+:(.+)$/);
|
|
496
|
+
if (match?.[1])
|
|
497
|
+
return match[1];
|
|
498
|
+
return undefined;
|
|
493
499
|
}
|
|
494
500
|
const cozeloopTracePlugin = {
|
|
495
501
|
id: "openclaw-cozeloop-trace",
|
|
@@ -558,6 +564,15 @@ const cozeloopTracePlugin = {
|
|
|
558
564
|
const ctx = contextByRunId.get(runId);
|
|
559
565
|
return ctx?.originalChannelId || ctx?.channelId;
|
|
560
566
|
};
|
|
567
|
+
const sessionIdFromChannelId = (channelId) => {
|
|
568
|
+
const match = String(channelId || "").match(/^agent\/[^:]+:(.+)$/);
|
|
569
|
+
return match?.[1];
|
|
570
|
+
};
|
|
571
|
+
const canFallbackToLastUserContext = (rawChannelId) => {
|
|
572
|
+
const rawSessionId = sessionIdFromChannelId(rawChannelId);
|
|
573
|
+
const lastSessionId = lastUserTraceContext?.openclawSessionId || sessionIdFromChannelId(lastUserChannelId);
|
|
574
|
+
return !rawSessionId || !lastSessionId || rawSessionId === lastSessionId;
|
|
575
|
+
};
|
|
561
576
|
const startTurn = (runId, channelId, originalChannelId, openclawSessionId) => {
|
|
562
577
|
const traceId = generateId(32);
|
|
563
578
|
const ctx = {
|
|
@@ -594,7 +609,7 @@ const cozeloopTracePlugin = {
|
|
|
594
609
|
if (!activeCtx) {
|
|
595
610
|
activeCtx = getContextByRun(effectiveRunId);
|
|
596
611
|
}
|
|
597
|
-
if (!activeCtx && rawChannelId.startsWith("agent/") && lastUserTraceContext) {
|
|
612
|
+
if (!activeCtx && rawChannelId.startsWith("agent/") && lastUserTraceContext && canFallbackToLastUserContext(rawChannelId)) {
|
|
598
613
|
activeCtx = lastUserTraceContext;
|
|
599
614
|
channelId = lastUserChannelId || channelId;
|
|
600
615
|
contextByChannelId.set(rawChannelId, activeCtx);
|
|
@@ -616,19 +631,136 @@ const cozeloopTracePlugin = {
|
|
|
616
631
|
}
|
|
617
632
|
return { ctx: activeCtx, channelId, isNew };
|
|
618
633
|
};
|
|
634
|
+
const contextKeys = (hookCtx, channelId, eventSessionId) => {
|
|
635
|
+
const keys = [];
|
|
636
|
+
const add = (value) => {
|
|
637
|
+
const s = String(value || "").trim();
|
|
638
|
+
if (s && !keys.includes(s))
|
|
639
|
+
keys.push(s);
|
|
640
|
+
};
|
|
641
|
+
add(resolveOpenclawSessionId(hookCtx || {}, eventSessionId));
|
|
642
|
+
add(hookCtx?.sessionKey);
|
|
643
|
+
add(hookCtx?.conversationId);
|
|
644
|
+
add(channelId);
|
|
645
|
+
return keys;
|
|
646
|
+
};
|
|
647
|
+
const rememberLastUserContext = (hookCtx, channelId, ctx) => {
|
|
648
|
+
for (const key of contextKeys(hookCtx, channelId)) {
|
|
649
|
+
lastUserContextByKey.set(key, { ctx, channelId });
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
const resolveLastUserContext = (hookCtx, channelId) => {
|
|
653
|
+
for (const key of contextKeys(hookCtx, channelId)) {
|
|
654
|
+
const found = lastUserContextByKey.get(key);
|
|
655
|
+
if (found)
|
|
656
|
+
return found;
|
|
657
|
+
}
|
|
658
|
+
if (lastUserTraceContext) {
|
|
659
|
+
return { ctx: lastUserTraceContext, channelId: lastUserChannelId || channelId };
|
|
660
|
+
}
|
|
661
|
+
return undefined;
|
|
662
|
+
};
|
|
663
|
+
const rememberActiveAgentContext = (hookCtx, channelId, ctx) => {
|
|
664
|
+
for (const key of contextKeys(hookCtx, channelId, ctx.openclawSessionId)) {
|
|
665
|
+
activeAgentContextByKey.set(key, { ctx, channelId });
|
|
666
|
+
}
|
|
667
|
+
activeAgentCtx = ctx;
|
|
668
|
+
activeAgentChannelId = channelId;
|
|
669
|
+
};
|
|
670
|
+
const resolveActiveAgentContext = (hookCtx, channelId) => {
|
|
671
|
+
for (const key of contextKeys(hookCtx, channelId)) {
|
|
672
|
+
const found = activeAgentContextByKey.get(key);
|
|
673
|
+
if (found)
|
|
674
|
+
return found;
|
|
675
|
+
}
|
|
676
|
+
if (activeAgentCtx) {
|
|
677
|
+
return { ctx: activeAgentCtx, channelId: activeAgentChannelId || channelId };
|
|
678
|
+
}
|
|
679
|
+
return undefined;
|
|
680
|
+
};
|
|
681
|
+
const clearActiveAgentContext = (ctx) => {
|
|
682
|
+
for (const [key, found] of activeAgentContextByKey.entries()) {
|
|
683
|
+
if (found.ctx === ctx)
|
|
684
|
+
activeAgentContextByKey.delete(key);
|
|
685
|
+
}
|
|
686
|
+
if (activeAgentCtx === ctx) {
|
|
687
|
+
activeAgentCtx = undefined;
|
|
688
|
+
activeAgentChannelId = undefined;
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
const clearLastUserContext = (ctx) => {
|
|
692
|
+
for (const [key, found] of lastUserContextByKey.entries()) {
|
|
693
|
+
if (found.ctx === ctx)
|
|
694
|
+
lastUserContextByKey.delete(key);
|
|
695
|
+
}
|
|
696
|
+
if (lastUserTraceContext === ctx) {
|
|
697
|
+
lastUserTraceContext = undefined;
|
|
698
|
+
lastUserChannelId = undefined;
|
|
699
|
+
}
|
|
700
|
+
};
|
|
619
701
|
// Resolve context for hooks that fire between before_agent_start and
|
|
620
702
|
// agent_end. When an agent is active, always return that agent's context
|
|
621
703
|
// so every span ends up in the same Trace regardless of channelId drift.
|
|
622
|
-
const resolveActiveContext = (rawChannelId, runId, hookName) => {
|
|
623
|
-
|
|
704
|
+
const resolveActiveContext = (rawChannelId, runId, hookName, hookCtx) => {
|
|
705
|
+
const active = resolveActiveAgentContext(hookCtx || {}, rawChannelId);
|
|
706
|
+
if (active) {
|
|
624
707
|
if (config.debug) {
|
|
625
|
-
api.logger.info(`[CozeloopTrace] Using activeAgentCtx for ${hookName}: traceId=${
|
|
708
|
+
api.logger.info(`[CozeloopTrace] Using activeAgentCtx for ${hookName}: traceId=${active.ctx.traceId}, rootSpanId=${active.ctx.rootSpanId}`);
|
|
626
709
|
}
|
|
627
|
-
return
|
|
710
|
+
return active;
|
|
628
711
|
}
|
|
629
712
|
const { ctx, channelId } = getOrCreateContext(rawChannelId, runId, hookName);
|
|
630
713
|
return { ctx, channelId };
|
|
631
714
|
};
|
|
715
|
+
const getToolCallId = (event) => {
|
|
716
|
+
const candidates = [
|
|
717
|
+
event?.toolCallId,
|
|
718
|
+
event?.tool_call_id,
|
|
719
|
+
event?.callId,
|
|
720
|
+
event?.id,
|
|
721
|
+
event?.requestId,
|
|
722
|
+
event?.invocationId,
|
|
723
|
+
event?.params?.toolCallId,
|
|
724
|
+
event?.params?.tool_call_id,
|
|
725
|
+
];
|
|
726
|
+
const found = candidates.find((value) => value !== undefined && value !== null && String(value).trim());
|
|
727
|
+
return found === undefined ? undefined : String(found);
|
|
728
|
+
};
|
|
729
|
+
const pendingToolKey = (ctx, channelId, toolName, toolCallId) => {
|
|
730
|
+
const traceKey = ctx.rootSpanId || ctx.traceId || channelId;
|
|
731
|
+
if (toolCallId)
|
|
732
|
+
return `${traceKey}:id:${toolCallId}`;
|
|
733
|
+
return `${traceKey}:name:${toolName || "unknown_tool"}`;
|
|
734
|
+
};
|
|
735
|
+
const pushPendingToolCall = (ctx, channelId, pending) => {
|
|
736
|
+
const key = pendingToolKey(ctx, channelId, pending.toolName, pending.toolCallId);
|
|
737
|
+
const queue = pendingToolCallsByKey.get(key) || [];
|
|
738
|
+
queue.push(pending);
|
|
739
|
+
pendingToolCallsByKey.set(key, queue);
|
|
740
|
+
return queue.length;
|
|
741
|
+
};
|
|
742
|
+
const shiftPendingToolCall = (ctx, channelId, toolName, toolCallId) => {
|
|
743
|
+
const key = pendingToolKey(ctx, channelId, toolName, toolCallId);
|
|
744
|
+
const queue = pendingToolCallsByKey.get(key);
|
|
745
|
+
if (!queue || queue.length === 0)
|
|
746
|
+
return undefined;
|
|
747
|
+
const pending = queue.shift();
|
|
748
|
+
if (queue.length === 0) {
|
|
749
|
+
pendingToolCallsByKey.delete(key);
|
|
750
|
+
}
|
|
751
|
+
return pending;
|
|
752
|
+
};
|
|
753
|
+
const clearPendingToolCallsForContext = (ctx) => {
|
|
754
|
+
for (const [key, queue] of pendingToolCallsByKey.entries()) {
|
|
755
|
+
const kept = queue.filter((pending) => pending.traceContext !== ctx);
|
|
756
|
+
if (kept.length === 0) {
|
|
757
|
+
pendingToolCallsByKey.delete(key);
|
|
758
|
+
}
|
|
759
|
+
else if (kept.length !== queue.length) {
|
|
760
|
+
pendingToolCallsByKey.set(key, kept);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
};
|
|
632
764
|
const createSpan = (ctx, channelId, name, type, startTime, endTime, attributes = {}, input, output, parentSpanId) => {
|
|
633
765
|
return {
|
|
634
766
|
name,
|
|
@@ -871,41 +1003,43 @@ const cozeloopTracePlugin = {
|
|
|
871
1003
|
if (!role && event.from) {
|
|
872
1004
|
role = "user";
|
|
873
1005
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
if (!ctx.userInput) {
|
|
889
|
-
ctx.userInput = event.content;
|
|
890
|
-
}
|
|
891
|
-
if (!lastUserTraceContext) {
|
|
892
|
-
lastUserTraceContext = ctx;
|
|
893
|
-
lastUserChannelId = channelId;
|
|
1006
|
+
// coze-context 缓存对【所有 channel】生效,含 agent/ channel(Coze 群聊真实
|
|
1007
|
+
// 链路 channelId=agent/main:<session>)。此前只在 isNonAgentChannel 分支缓存,
|
|
1008
|
+
// 导致群聊消息的 <coze-context>(message_id/group_id 等)被整体跳过、root span
|
|
1009
|
+
// 拿不到 coze tag。parseCozeContext 解析不到时返回空、无副作用,故无条件 remember 安全。
|
|
1010
|
+
if (role === "user" || !role) {
|
|
1011
|
+
lastUserChannelId = channelId;
|
|
1012
|
+
lastUserTraceContext = ctx;
|
|
1013
|
+
rememberLastUserContext(hookCtx, channelId, ctx);
|
|
1014
|
+
ctx.userInput = event.content;
|
|
1015
|
+
lastUserInput = event.content;
|
|
1016
|
+
rememberCozeContext(event.content, ctx.openclawSessionId || lastOpenclawSessionId);
|
|
1017
|
+
if (config.debug) {
|
|
1018
|
+
api.logger.info(`[CozeloopTrace] Saved user context: channelId=${channelId}, traceId=${ctx.traceId}`);
|
|
894
1019
|
}
|
|
895
1020
|
}
|
|
1021
|
+
if (!ctx.userInput) {
|
|
1022
|
+
ctx.userInput = event.content;
|
|
1023
|
+
}
|
|
1024
|
+
if (!lastUserTraceContext) {
|
|
1025
|
+
lastUserTraceContext = ctx;
|
|
1026
|
+
lastUserChannelId = channelId;
|
|
1027
|
+
rememberLastUserContext(hookCtx, channelId, ctx);
|
|
1028
|
+
}
|
|
896
1029
|
});
|
|
897
1030
|
}
|
|
898
1031
|
if (shouldHookEnabled("message_sending")) {
|
|
899
1032
|
on("message_sending", async (event, hookCtx) => {
|
|
900
|
-
|
|
901
|
-
|
|
1033
|
+
const rawChannelId = resolveChannelId(hookCtx, event.to);
|
|
1034
|
+
const lastUser = resolveLastUserContext(hookCtx, rawChannelId);
|
|
1035
|
+
if (lastUser) {
|
|
1036
|
+
lastUser.ctx.lastOutput = event.content;
|
|
902
1037
|
if (config.debug) {
|
|
903
|
-
api.logger.info(`[CozeloopTrace] Captured output for root span: traceId=${
|
|
1038
|
+
api.logger.info(`[CozeloopTrace] Captured output for root span: traceId=${lastUser.ctx.traceId}, content=${typeof event.content === 'string' ? event.content.substring(0, 100) : 'non-string'}`);
|
|
904
1039
|
}
|
|
905
1040
|
}
|
|
906
1041
|
else {
|
|
907
|
-
const
|
|
908
|
-
const { ctx } = resolveActiveContext(rawChannelId, undefined, "message_sending");
|
|
1042
|
+
const { ctx } = resolveActiveContext(rawChannelId, undefined, "message_sending", hookCtx);
|
|
909
1043
|
ctx.lastOutput = event.content;
|
|
910
1044
|
if (config.debug) {
|
|
911
1045
|
api.logger.info(`[CozeloopTrace] Captured output (fallback) for root span: traceId=${ctx.traceId}`);
|
|
@@ -916,15 +1050,16 @@ const cozeloopTracePlugin = {
|
|
|
916
1050
|
if (shouldHookEnabled("message_sent")) {
|
|
917
1051
|
on("message_sent", async (event, hookCtx) => {
|
|
918
1052
|
if (event.content && event.success) {
|
|
919
|
-
|
|
920
|
-
|
|
1053
|
+
const rawChannelId = resolveChannelId(hookCtx, event.to);
|
|
1054
|
+
const lastUser = resolveLastUserContext(hookCtx, rawChannelId);
|
|
1055
|
+
if (lastUser) {
|
|
1056
|
+
lastUser.ctx.lastOutput = event.content;
|
|
921
1057
|
if (config.debug) {
|
|
922
|
-
api.logger.info(`[CozeloopTrace] Captured output from message_sent: traceId=${
|
|
1058
|
+
api.logger.info(`[CozeloopTrace] Captured output from message_sent: traceId=${lastUser.ctx.traceId}`);
|
|
923
1059
|
}
|
|
924
1060
|
}
|
|
925
1061
|
else {
|
|
926
|
-
const
|
|
927
|
-
const { ctx } = resolveActiveContext(rawChannelId, undefined, "message_sent");
|
|
1062
|
+
const { ctx } = resolveActiveContext(rawChannelId, undefined, "message_sent", hookCtx);
|
|
928
1063
|
ctx.lastOutput = event.content;
|
|
929
1064
|
if (config.debug) {
|
|
930
1065
|
api.logger.info(`[CozeloopTrace] Captured output from message_sent (fallback): traceId=${ctx.traceId}`);
|
|
@@ -942,7 +1077,7 @@ const cozeloopTracePlugin = {
|
|
|
942
1077
|
if (config.debug) {
|
|
943
1078
|
api.logger.info(`[CozeloopTrace] llm_input hookCtx: ${JSON.stringify({ channelId: hookCtx.channelId, sessionKey: hookCtx.sessionKey, conversationId: hookCtx.conversationId })}, event.runId=${event.runId}`);
|
|
944
1079
|
}
|
|
945
|
-
const { ctx } = resolveActiveContext(rawChannelId, event.runId, "llm_input");
|
|
1080
|
+
const { ctx, channelId } = resolveActiveContext(rawChannelId, event.runId, "llm_input", hookCtx);
|
|
946
1081
|
const ocSessionId = resolveOpenclawSessionId(hookCtx);
|
|
947
1082
|
if (ocSessionId) {
|
|
948
1083
|
ctx.openclawSessionId = ocSessionId;
|
|
@@ -967,9 +1102,10 @@ const cozeloopTracePlugin = {
|
|
|
967
1102
|
rememberCozeContext(event.prompt, ctx.openclawSessionId || lastOpenclawSessionId);
|
|
968
1103
|
// Fallback: ensure root + agent spans exist in case before_agent_start
|
|
969
1104
|
// was not fired (older OpenClaw versions or resumed sessions).
|
|
970
|
-
const
|
|
1105
|
+
const active = resolveActiveAgentContext(hookCtx, channelId);
|
|
1106
|
+
const channelIdForSpans = active?.channelId || channelId || rawChannelId;
|
|
971
1107
|
await ensureRootSpan(ctx, channelIdForSpans);
|
|
972
|
-
await ensureAgentSpan(ctx, channelIdForSpans);
|
|
1108
|
+
await ensureAgentSpan(ctx, channelIdForSpans, undefined, hookCtx);
|
|
973
1109
|
const messages = [];
|
|
974
1110
|
if (event.systemPrompt) {
|
|
975
1111
|
messages.push({ role: "system", content: safeClone(event.systemPrompt) });
|
|
@@ -1036,14 +1172,15 @@ const cozeloopTracePlugin = {
|
|
|
1036
1172
|
api.logger.info(`[CozeloopTrace][DEBUG] llm_output event keys=${JSON.stringify(Object.keys(event))}`);
|
|
1037
1173
|
api.logger.info(`[CozeloopTrace] llm_output hookCtx: ${JSON.stringify({ channelId: hookCtx.channelId, sessionKey: hookCtx.sessionKey, conversationId: hookCtx.conversationId })}, event.runId=${event.runId}`);
|
|
1038
1174
|
}
|
|
1039
|
-
const { ctx, channelId } = resolveActiveContext(rawChannelId, event.runId, "llm_output");
|
|
1175
|
+
const { ctx, channelId } = resolveActiveContext(rawChannelId, event.runId, "llm_output", hookCtx);
|
|
1040
1176
|
const now = Date.now();
|
|
1041
1177
|
const startTime = ctx.llmStartTime || lastLlmStartTime || now;
|
|
1042
1178
|
if (event.assistantTexts && event.assistantTexts.length > 0) {
|
|
1043
1179
|
const outputText = event.assistantTexts.join("\n");
|
|
1044
1180
|
ctx.lastOutput = outputText;
|
|
1045
|
-
|
|
1046
|
-
|
|
1181
|
+
const lastUser = resolveLastUserContext(hookCtx, channelId);
|
|
1182
|
+
if (lastUser) {
|
|
1183
|
+
lastUser.ctx.lastOutput = outputText;
|
|
1047
1184
|
}
|
|
1048
1185
|
if (config.debug) {
|
|
1049
1186
|
api.logger.info(`[CozeloopTrace] Captured output from llm_output (will use last): traceId=${ctx.traceId}, length=${outputText.length}`);
|
|
@@ -1129,34 +1266,39 @@ const cozeloopTracePlugin = {
|
|
|
1129
1266
|
if (config.debug) {
|
|
1130
1267
|
api.logger.info(`[CozeloopTrace] before_tool_call hookCtx: ${JSON.stringify({ channelId: hookCtx.channelId, sessionKey: hookCtx.sessionKey, conversationId: hookCtx.conversationId })}, toolName=${event.toolName}`);
|
|
1131
1268
|
}
|
|
1132
|
-
const { ctx, channelId } = resolveActiveContext(rawChannelId, undefined, "before_tool_call");
|
|
1133
|
-
pendingToolCall = {
|
|
1269
|
+
const { ctx, channelId } = resolveActiveContext(rawChannelId, undefined, "before_tool_call", hookCtx);
|
|
1270
|
+
const pendingToolCall = {
|
|
1134
1271
|
toolName: event.toolName,
|
|
1272
|
+
toolCallId: getToolCallId(event),
|
|
1135
1273
|
toolSpanId: generateId(16),
|
|
1136
1274
|
toolStartTime: Date.now(),
|
|
1137
1275
|
toolInput: event.params,
|
|
1138
1276
|
traceContext: ctx,
|
|
1139
1277
|
channelId: channelId,
|
|
1140
1278
|
};
|
|
1279
|
+
const pendingCount = pushPendingToolCall(ctx, channelId, pendingToolCall);
|
|
1141
1280
|
ctx.reactCount = (ctx.reactCount || 0) + 1;
|
|
1142
1281
|
if (config.debug) {
|
|
1143
|
-
api.logger.info(`[CozeloopTrace] Tool call started: ${event.toolName}, spanId=${pendingToolCall.toolSpanId}, traceId=${ctx.traceId}`);
|
|
1282
|
+
api.logger.info(`[CozeloopTrace] Tool call started: ${event.toolName}, toolCallId=${pendingToolCall.toolCallId || "none"}, spanId=${pendingToolCall.toolSpanId}, traceId=${ctx.traceId}, pendingCount=${pendingCount}`);
|
|
1144
1283
|
}
|
|
1145
1284
|
});
|
|
1146
1285
|
}
|
|
1147
1286
|
if (shouldHookEnabled("after_tool_call")) {
|
|
1148
1287
|
on("after_tool_call", async (event, hookCtx) => {
|
|
1288
|
+
const rawChannelId = resolveChannelId(hookCtx);
|
|
1289
|
+
const { ctx, channelId } = resolveActiveContext(rawChannelId, undefined, "after_tool_call", hookCtx);
|
|
1149
1290
|
if (config.debug) {
|
|
1150
1291
|
api.logger.info(`[CozeloopTrace] after_tool_call hookCtx: ${JSON.stringify({ channelId: hookCtx.channelId, sessionKey: hookCtx.sessionKey, conversationId: hookCtx.conversationId })}, toolName=${event.toolName}`);
|
|
1151
1292
|
}
|
|
1152
|
-
|
|
1293
|
+
const toolCallId = getToolCallId(event);
|
|
1294
|
+
const pendingToolCall = shiftPendingToolCall(ctx, channelId, event.toolName, toolCallId);
|
|
1295
|
+
if (!pendingToolCall) {
|
|
1153
1296
|
if (config.debug) {
|
|
1154
|
-
api.logger.info(`[CozeloopTrace] Skipping after_tool_call: no pending tool
|
|
1297
|
+
api.logger.info(`[CozeloopTrace] Skipping after_tool_call: no pending tool, toolName=${event.toolName}, toolCallId=${toolCallId || "none"}, pendingKeys=${pendingToolCallsByKey.size}`);
|
|
1155
1298
|
}
|
|
1156
1299
|
return;
|
|
1157
1300
|
}
|
|
1158
1301
|
const { toolName, toolSpanId, toolStartTime, toolInput, traceContext } = pendingToolCall;
|
|
1159
|
-
pendingToolCall = undefined;
|
|
1160
1302
|
const now = Date.now();
|
|
1161
1303
|
if (!traceContext.pendingToolSpans) {
|
|
1162
1304
|
traceContext.pendingToolSpans = [];
|
|
@@ -1178,11 +1320,12 @@ const cozeloopTracePlugin = {
|
|
|
1178
1320
|
// Helper: finalize a trace — end agent span (if open), end root span, flush,
|
|
1179
1321
|
// and clean up all state. Called from agent_end (normal path) and
|
|
1180
1322
|
// session_end (fallback for old OpenClaw versions that don't emit agent_end).
|
|
1181
|
-
|
|
1323
|
+
const finalizingTraces = new Set();
|
|
1182
1324
|
const finalizeTrace = (ctx, channelId, agentEndAttrs, agentOutput) => {
|
|
1183
|
-
|
|
1325
|
+
const finalizeKey = ctx.rootSpanId || ctx.traceId;
|
|
1326
|
+
if (finalizingTraces.has(finalizeKey))
|
|
1184
1327
|
return;
|
|
1185
|
-
|
|
1328
|
+
finalizingTraces.add(finalizeKey);
|
|
1186
1329
|
const now = Date.now();
|
|
1187
1330
|
// End agent span if still open.
|
|
1188
1331
|
if (ctx.agentSpanId) {
|
|
@@ -1216,20 +1359,17 @@ const cozeloopTracePlugin = {
|
|
|
1216
1359
|
}
|
|
1217
1360
|
await exporter.flush();
|
|
1218
1361
|
exporter.endTrace(rootSpanId);
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
activeAgentChannelId = undefined;
|
|
1222
|
-
}
|
|
1362
|
+
clearPendingToolCallsForContext(ctx);
|
|
1363
|
+
clearActiveAgentContext(ctx);
|
|
1223
1364
|
if (savedLastUserChannelId) {
|
|
1224
1365
|
endTurn(savedLastUserChannelId);
|
|
1225
1366
|
}
|
|
1226
1367
|
if (originalChannelId && originalChannelId !== savedLastUserChannelId) {
|
|
1227
1368
|
endTurn(originalChannelId);
|
|
1228
1369
|
}
|
|
1229
|
-
|
|
1230
|
-
lastUserTraceContext = undefined;
|
|
1370
|
+
clearLastUserContext(ctx);
|
|
1231
1371
|
lastUserInput = undefined;
|
|
1232
|
-
|
|
1372
|
+
finalizingTraces.delete(finalizeKey);
|
|
1233
1373
|
}, 200);
|
|
1234
1374
|
};
|
|
1235
1375
|
// OpenClaw runtime 周期性发送的心跳轮询消息,不是真实对话,整条 trace 丢弃。
|
|
@@ -1323,9 +1463,11 @@ const cozeloopTracePlugin = {
|
|
|
1323
1463
|
};
|
|
1324
1464
|
// Helper: ensure the agent span exists for a given context.
|
|
1325
1465
|
// Safe to call multiple times — only creates the span once.
|
|
1326
|
-
const ensureAgentSpan = async (ctx, channelId, agentId) => {
|
|
1327
|
-
if (ctx.agentSpanId)
|
|
1466
|
+
const ensureAgentSpan = async (ctx, channelId, agentId, hookCtx) => {
|
|
1467
|
+
if (ctx.agentSpanId) {
|
|
1468
|
+
rememberActiveAgentContext(hookCtx || {}, channelId, ctx);
|
|
1328
1469
|
return;
|
|
1470
|
+
}
|
|
1329
1471
|
const effectiveAgentId = agentId || "main";
|
|
1330
1472
|
const now = Date.now();
|
|
1331
1473
|
ctx.agentStartTime = now;
|
|
@@ -1348,8 +1490,7 @@ const cozeloopTracePlugin = {
|
|
|
1348
1490
|
};
|
|
1349
1491
|
await exporter.startSpan(spanData, ctx.agentSpanId);
|
|
1350
1492
|
// Set active agent context so all subsequent hooks use the same Trace.
|
|
1351
|
-
|
|
1352
|
-
activeAgentChannelId = channelId;
|
|
1493
|
+
rememberActiveAgentContext(hookCtx || {}, channelId, ctx);
|
|
1353
1494
|
if (config.debug) {
|
|
1354
1495
|
api.logger.info(`[CozeloopTrace] ensureAgentSpan: created agent span, agentId=${effectiveAgentId}, spanId=${ctx.agentSpanId}, traceId=${ctx.traceId}`);
|
|
1355
1496
|
}
|
|
@@ -1369,7 +1510,7 @@ const cozeloopTracePlugin = {
|
|
|
1369
1510
|
}
|
|
1370
1511
|
ctx.openclawSessionId = ctx.openclawSessionId || lastOpenclawSessionId;
|
|
1371
1512
|
await ensureRootSpan(ctx, channelId);
|
|
1372
|
-
await ensureAgentSpan(ctx, channelId, agentId);
|
|
1513
|
+
await ensureAgentSpan(ctx, channelId, agentId, hookCtx);
|
|
1373
1514
|
});
|
|
1374
1515
|
}
|
|
1375
1516
|
if (shouldHookEnabled("agent_end")) {
|
|
@@ -1379,8 +1520,9 @@ const cozeloopTracePlugin = {
|
|
|
1379
1520
|
api.logger.info(`[CozeloopTrace] agent_end hookCtx: ${JSON.stringify({ channelId: hookCtx.channelId, sessionKey: hookCtx.sessionKey, conversationId: hookCtx.conversationId })}`);
|
|
1380
1521
|
}
|
|
1381
1522
|
// Use activeAgentCtx if available, otherwise fall back to resolution.
|
|
1382
|
-
const
|
|
1383
|
-
const
|
|
1523
|
+
const active = resolveActiveAgentContext(hookCtx, rawChannelId);
|
|
1524
|
+
const ctx = active?.ctx || getOrCreateContext(rawChannelId, undefined, "agent_end").ctx;
|
|
1525
|
+
const channelId = active?.channelId || rawChannelId;
|
|
1384
1526
|
finalizeTrace(ctx, channelId, {
|
|
1385
1527
|
"agent.duration_ms": event.durationMs || 0,
|
|
1386
1528
|
"agent.message_count": event.messageCount || 0,
|
|
@@ -1398,9 +1540,11 @@ const cozeloopTracePlugin = {
|
|
|
1398
1540
|
if (config.debug) {
|
|
1399
1541
|
api.logger.info(`[CozeloopTrace] session_end: ${rawChannelId}`);
|
|
1400
1542
|
}
|
|
1401
|
-
const
|
|
1543
|
+
const active = resolveActiveAgentContext(hookCtx, rawChannelId);
|
|
1544
|
+
const lastUser = resolveLastUserContext(hookCtx, rawChannelId);
|
|
1545
|
+
const ctx = active?.ctx || lastUser?.ctx;
|
|
1402
1546
|
if (ctx && ctx.rootSpanStartTime) {
|
|
1403
|
-
const channelId =
|
|
1547
|
+
const channelId = active?.channelId || lastUser?.channelId || rawChannelId;
|
|
1404
1548
|
if (config.debug) {
|
|
1405
1549
|
api.logger.info(`[CozeloopTrace] session_end: finalizing trace as fallback, traceId=${ctx.traceId}`);
|
|
1406
1550
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cozeloop/openclaw-cozeloop-trace",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "OpenClaw Plugin for reporting traces to CozeLoop via OpenTelemetry",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -37,7 +37,6 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@opentelemetry/api": "^1.7.0",
|
|
40
|
-
"@opentelemetry/exporter-trace-otlp-proto": "^0.48.0",
|
|
41
40
|
"@opentelemetry/resources": "^1.22.0",
|
|
42
41
|
"@opentelemetry/sdk-trace-base": "^1.22.0",
|
|
43
42
|
"@opentelemetry/semantic-conventions": "^1.22.0"
|