coze_lab 0.1.5 → 0.1.7
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.
|
@@ -511,6 +511,31 @@ const cozeloopTracePlugin = {
|
|
|
511
511
|
enabledHooks: pluginConfig.enabledHooks,
|
|
512
512
|
};
|
|
513
513
|
const exporter = new CozeloopExporter(api, config);
|
|
514
|
+
// per-agent trace 放行:traceAgentIds 为 onboard 写入的 allowlist(小写归一)。
|
|
515
|
+
// 非空时,仅 allowlist 内的 agentId 上报 trace;为空 / 未配置则全部放行(本地全局模式)。
|
|
516
|
+
const traceAgentIds = Array.isArray(pluginConfig.traceAgentIds)
|
|
517
|
+
? pluginConfig.traceAgentIds.map((s) => String(s).trim().toLowerCase()).filter(Boolean)
|
|
518
|
+
: [];
|
|
519
|
+
const isAgentAllowed = (hookCtx) => {
|
|
520
|
+
if (traceAgentIds.length === 0)
|
|
521
|
+
return true; // 空 allowlist = 全部放行
|
|
522
|
+
const agentId = resolveAgentIdFromHookCtx(hookCtx || {});
|
|
523
|
+
const allowed = traceAgentIds.includes(agentId);
|
|
524
|
+
if (!allowed && config.debug) {
|
|
525
|
+
api.logger.info(`[CozeloopTrace] agent "${agentId}" not in traceAgentIds, skip trace`);
|
|
526
|
+
}
|
|
527
|
+
return allowed;
|
|
528
|
+
};
|
|
529
|
+
// 包裹 api.on:带 hookCtx 的 hook 在 agentId 不在 allowlist 时直接跳过,
|
|
530
|
+
// 不进入任何上报逻辑。gateway_start/stop 等无 hookCtx 的生命周期 hook 不拦截。
|
|
531
|
+
const rawOn = api.on.bind(api);
|
|
532
|
+
const on = (name, handler) => {
|
|
533
|
+
rawOn(name, async (event, hookCtx) => {
|
|
534
|
+
if (hookCtx !== undefined && !isAgentAllowed(hookCtx))
|
|
535
|
+
return;
|
|
536
|
+
return handler(event, hookCtx);
|
|
537
|
+
});
|
|
538
|
+
};
|
|
514
539
|
const contextByChannelId = new Map();
|
|
515
540
|
const contextByRunId = new Map();
|
|
516
541
|
const shouldHookEnabled = (hookName) => {
|
|
@@ -787,6 +812,14 @@ const cozeloopTracePlugin = {
|
|
|
787
812
|
});
|
|
788
813
|
if (shouldHookEnabled("gateway_start")) {
|
|
789
814
|
api.on("gateway_start", async (event) => {
|
|
815
|
+
// gateway_start 是进程级事件、无 agent 归属。云端 per-agent 模式
|
|
816
|
+
// (traceAgentIds 非空)下不上报这条无归属 span,避免越过 allowlist。
|
|
817
|
+
if (traceAgentIds.length > 0) {
|
|
818
|
+
if (config.debug) {
|
|
819
|
+
api.logger.info("[CozeloopTrace] traceAgentIds set, skip gateway_start span");
|
|
820
|
+
}
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
790
823
|
const now = Date.now();
|
|
791
824
|
const { ctx, channelId } = getOrCreateContext("system/gateway", undefined, "gateway_start");
|
|
792
825
|
const span = createSpan(ctx, channelId, "gateway_start", "gateway", now, now, {
|
|
@@ -800,7 +833,7 @@ const cozeloopTracePlugin = {
|
|
|
800
833
|
});
|
|
801
834
|
}
|
|
802
835
|
if (shouldHookEnabled("session_start")) {
|
|
803
|
-
|
|
836
|
+
on("session_start", async (event, hookCtx) => {
|
|
804
837
|
// Refresh token if expiring soon (< 10 min)
|
|
805
838
|
await exporter.refreshAuthIfNeeded();
|
|
806
839
|
const rawChannelId = resolveChannelId(hookCtx, event.sessionId);
|
|
@@ -817,7 +850,7 @@ const cozeloopTracePlugin = {
|
|
|
817
850
|
});
|
|
818
851
|
}
|
|
819
852
|
if (shouldHookEnabled("message_received")) {
|
|
820
|
-
|
|
853
|
+
on("message_received", async (event, hookCtx) => {
|
|
821
854
|
const rawChannelId = resolveChannelId(hookCtx, event.from || event.metadata?.senderId);
|
|
822
855
|
if (config.debug) {
|
|
823
856
|
api.logger.info(`[CozeloopTrace] message_received hookCtx: ${JSON.stringify({ channelId: hookCtx.channelId, sessionKey: hookCtx.sessionKey, conversationId: hookCtx.conversationId })}, event.from=${event.from}`);
|
|
@@ -858,7 +891,7 @@ const cozeloopTracePlugin = {
|
|
|
858
891
|
});
|
|
859
892
|
}
|
|
860
893
|
if (shouldHookEnabled("message_sending")) {
|
|
861
|
-
|
|
894
|
+
on("message_sending", async (event, hookCtx) => {
|
|
862
895
|
if (lastUserTraceContext) {
|
|
863
896
|
lastUserTraceContext.lastOutput = event.content;
|
|
864
897
|
if (config.debug) {
|
|
@@ -876,7 +909,7 @@ const cozeloopTracePlugin = {
|
|
|
876
909
|
});
|
|
877
910
|
}
|
|
878
911
|
if (shouldHookEnabled("message_sent")) {
|
|
879
|
-
|
|
912
|
+
on("message_sent", async (event, hookCtx) => {
|
|
880
913
|
if (event.content && event.success) {
|
|
881
914
|
if (lastUserTraceContext) {
|
|
882
915
|
lastUserTraceContext.lastOutput = event.content;
|
|
@@ -899,7 +932,7 @@ const cozeloopTracePlugin = {
|
|
|
899
932
|
let lastLlmStartTime = undefined;
|
|
900
933
|
let lastLlmSpanId = undefined;
|
|
901
934
|
if (shouldHookEnabled("llm_input")) {
|
|
902
|
-
|
|
935
|
+
on("llm_input", async (event, hookCtx) => {
|
|
903
936
|
const rawChannelId = resolveChannelId(hookCtx);
|
|
904
937
|
if (config.debug) {
|
|
905
938
|
api.logger.info(`[CozeloopTrace] llm_input hookCtx: ${JSON.stringify({ channelId: hookCtx.channelId, sessionKey: hookCtx.sessionKey, conversationId: hookCtx.conversationId })}, event.runId=${event.runId}`);
|
|
@@ -990,7 +1023,7 @@ const cozeloopTracePlugin = {
|
|
|
990
1023
|
});
|
|
991
1024
|
}
|
|
992
1025
|
if (shouldHookEnabled("llm_output")) {
|
|
993
|
-
|
|
1026
|
+
on("llm_output", async (event, hookCtx) => {
|
|
994
1027
|
const rawChannelId = resolveChannelId(hookCtx);
|
|
995
1028
|
if (config.debug) {
|
|
996
1029
|
api.logger.info(`[CozeloopTrace][DEBUG] llm_output event.usage=${JSON.stringify(event.usage)}`);
|
|
@@ -1086,7 +1119,7 @@ const cozeloopTracePlugin = {
|
|
|
1086
1119
|
});
|
|
1087
1120
|
}
|
|
1088
1121
|
if (shouldHookEnabled("before_tool_call")) {
|
|
1089
|
-
|
|
1122
|
+
on("before_tool_call", async (event, hookCtx) => {
|
|
1090
1123
|
const rawChannelId = resolveChannelId(hookCtx);
|
|
1091
1124
|
if (config.debug) {
|
|
1092
1125
|
api.logger.info(`[CozeloopTrace] before_tool_call hookCtx: ${JSON.stringify({ channelId: hookCtx.channelId, sessionKey: hookCtx.sessionKey, conversationId: hookCtx.conversationId })}, toolName=${event.toolName}`);
|
|
@@ -1107,7 +1140,7 @@ const cozeloopTracePlugin = {
|
|
|
1107
1140
|
});
|
|
1108
1141
|
}
|
|
1109
1142
|
if (shouldHookEnabled("after_tool_call")) {
|
|
1110
|
-
|
|
1143
|
+
on("after_tool_call", async (event, hookCtx) => {
|
|
1111
1144
|
if (config.debug) {
|
|
1112
1145
|
api.logger.info(`[CozeloopTrace] after_tool_call hookCtx: ${JSON.stringify({ channelId: hookCtx.channelId, sessionKey: hookCtx.sessionKey, conversationId: hookCtx.conversationId })}, toolName=${event.toolName}`);
|
|
1113
1146
|
}
|
|
@@ -1194,10 +1227,32 @@ const cozeloopTracePlugin = {
|
|
|
1194
1227
|
traceFinalized = false;
|
|
1195
1228
|
}, 200);
|
|
1196
1229
|
};
|
|
1230
|
+
// OpenClaw runtime 周期性发送的心跳轮询消息,不是真实对话,整条 trace 丢弃。
|
|
1231
|
+
const HEARTBEAT_INPUT = "[OpenClaw heartbeat poll]";
|
|
1232
|
+
const isHeartbeatInput = (input) => {
|
|
1233
|
+
if (typeof input === "string") {
|
|
1234
|
+
return input.trim() === HEARTBEAT_INPUT;
|
|
1235
|
+
}
|
|
1236
|
+
// content 可能是 [{type:'text', text:'...'}] 形式
|
|
1237
|
+
if (Array.isArray(input)) {
|
|
1238
|
+
return input.some((p) => p && typeof p === "object"
|
|
1239
|
+
&& typeof p.text === "string" && p.text.trim() === HEARTBEAT_INPUT);
|
|
1240
|
+
}
|
|
1241
|
+
return false;
|
|
1242
|
+
};
|
|
1197
1243
|
// Helper: ensure root openclaw_request span is started for a given context.
|
|
1198
1244
|
// Must be called before creating the agent span so that the exporter's
|
|
1199
1245
|
// currentRootContext is set and the agent span becomes a proper child.
|
|
1200
1246
|
const ensureRootSpan = async (ctx, channelId) => {
|
|
1247
|
+
// 心跳轮询消息:不是真实对话,整条 trace 不上报(沿用下方“无 coze-context 即 return”的同款范式)。
|
|
1248
|
+
const heartbeatInput = ctx.userInput
|
|
1249
|
+
|| lastUserTraceContext?.userInput || lastUserInput;
|
|
1250
|
+
if (isHeartbeatInput(heartbeatInput)) {
|
|
1251
|
+
if (config.debug) {
|
|
1252
|
+
api.logger.info(`[CozeloopTrace] skip heartbeat poll trace, traceId=${ctx.traceId}`);
|
|
1253
|
+
}
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1201
1256
|
// Resolve coze-context with SESSION-LEVEL inheritance: only the first
|
|
1202
1257
|
// user message of a Coze session carries the <coze-context> block, so
|
|
1203
1258
|
// /loop follow-ups and later turns have none. Fall back to the per-
|
|
@@ -1295,7 +1350,7 @@ const cozeloopTracePlugin = {
|
|
|
1295
1350
|
}
|
|
1296
1351
|
};
|
|
1297
1352
|
if (shouldHookEnabled("before_agent_start")) {
|
|
1298
|
-
|
|
1353
|
+
on("before_agent_start", async (event, hookCtx) => {
|
|
1299
1354
|
const rawChannelId = resolveChannelId(hookCtx);
|
|
1300
1355
|
const agentId = hookCtx.agentId || event.agentId || "main";
|
|
1301
1356
|
if (config.debug) {
|
|
@@ -1313,7 +1368,7 @@ const cozeloopTracePlugin = {
|
|
|
1313
1368
|
});
|
|
1314
1369
|
}
|
|
1315
1370
|
if (shouldHookEnabled("agent_end")) {
|
|
1316
|
-
|
|
1371
|
+
on("agent_end", async (event, hookCtx) => {
|
|
1317
1372
|
const rawChannelId = resolveChannelId(hookCtx);
|
|
1318
1373
|
if (config.debug) {
|
|
1319
1374
|
api.logger.info(`[CozeloopTrace] agent_end hookCtx: ${JSON.stringify({ channelId: hookCtx.channelId, sessionKey: hookCtx.sessionKey, conversationId: hookCtx.conversationId })}`);
|
|
@@ -1333,7 +1388,7 @@ const cozeloopTracePlugin = {
|
|
|
1333
1388
|
// versions), finalize the trace here so that agent + root spans get ended
|
|
1334
1389
|
// and exported.
|
|
1335
1390
|
if (shouldHookEnabled("session_end")) {
|
|
1336
|
-
|
|
1391
|
+
on("session_end", async (event, hookCtx) => {
|
|
1337
1392
|
const rawChannelId = resolveChannelId(hookCtx, event.sessionId);
|
|
1338
1393
|
if (config.debug) {
|
|
1339
1394
|
api.logger.info(`[CozeloopTrace] session_end: ${rawChannelId}`);
|