@probelabs/visor 0.1.182-ee → 0.1.183-ee
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/defaults/assistant.yaml +2 -1
- package/defaults/code-talk.yaml +6 -0
- package/defaults/skills/task-progress.yaml +39 -0
- package/dist/agent-protocol/task-evaluator.d.ts +2 -1
- package/dist/agent-protocol/task-evaluator.d.ts.map +1 -1
- package/dist/agent-protocol/task-progress-tool.d.ts +29 -0
- package/dist/agent-protocol/task-progress-tool.d.ts.map +1 -0
- package/dist/agent-protocol/task-store.d.ts +8 -0
- package/dist/agent-protocol/task-store.d.ts.map +1 -1
- package/dist/agent-protocol/tasks-cli-handler.d.ts.map +1 -1
- package/dist/agent-protocol/trace-serializer.d.ts +5 -2
- package/dist/agent-protocol/trace-serializer.d.ts.map +1 -1
- package/dist/agent-protocol/track-execution.d.ts +1 -1
- package/dist/agent-protocol/track-execution.d.ts.map +1 -1
- package/dist/ai-review-service.d.ts.map +1 -1
- package/dist/cli-main.d.ts.map +1 -1
- package/dist/debug-visualizer/trace-reader.d.ts.map +1 -1
- package/dist/defaults/assistant.yaml +2 -1
- package/dist/defaults/code-talk.yaml +6 -0
- package/dist/defaults/skills/task-progress.yaml +39 -0
- package/dist/docs/telemetry-live-spans-plan.md +510 -0
- package/dist/generated/config-schema.json +43 -6
- package/dist/index.js +3545 -701
- package/dist/providers/ai-check-provider.d.ts.map +1 -1
- package/dist/providers/git-checkout-provider.d.ts.map +1 -1
- package/dist/providers/mcp-custom-sse-server.d.ts.map +1 -1
- package/dist/reviewer.d.ts +2 -0
- package/dist/reviewer.d.ts.map +1 -1
- package/dist/runners/process-cli-handler.d.ts +2 -0
- package/dist/runners/process-cli-handler.d.ts.map +1 -0
- package/dist/runners/process-discovery.d.ts +29 -0
- package/dist/runners/process-discovery.d.ts.map +1 -0
- package/dist/sandbox/check-runner.d.ts.map +1 -1
- package/dist/sandbox/sandbox-telemetry.d.ts +7 -0
- package/dist/sandbox/sandbox-telemetry.d.ts.map +1 -1
- package/dist/sandbox/trace-ingester.d.ts +28 -15
- package/dist/sandbox/trace-ingester.d.ts.map +1 -1
- package/dist/scheduler/schedule-tool.d.ts +5 -0
- package/dist/scheduler/schedule-tool.d.ts.map +1 -1
- package/dist/sdk/{a2a-frontend-MU5EO2HZ.mjs → a2a-frontend-5YDHFQXD.mjs} +47 -8
- package/dist/sdk/{a2a-frontend-MU5EO2HZ.mjs.map → a2a-frontend-5YDHFQXD.mjs.map} +1 -1
- package/dist/sdk/{a2a-frontend-4LP3MLTS.mjs → a2a-frontend-6LWBIPMS.mjs} +19 -3
- package/dist/sdk/a2a-frontend-6LWBIPMS.mjs.map +1 -0
- package/dist/sdk/check-provider-registry-WSEVHJEV.mjs +31 -0
- package/dist/sdk/{check-provider-registry-I4BCWKRU.mjs → check-provider-registry-YRADEEQY.mjs} +6 -6
- package/dist/sdk/chunk-4BN2XI4X.mjs +459 -0
- package/dist/sdk/chunk-4BN2XI4X.mjs.map +1 -0
- package/dist/sdk/chunk-54KOAC4W.mjs +665 -0
- package/dist/sdk/chunk-54KOAC4W.mjs.map +1 -0
- package/dist/sdk/chunk-6C3R6E42.mjs +1700 -0
- package/dist/sdk/chunk-6C3R6E42.mjs.map +1 -0
- package/dist/sdk/{chunk-4I3TJ7UJ.mjs → chunk-7W5QCO4Y.mjs} +47 -10
- package/dist/sdk/chunk-7W5QCO4Y.mjs.map +1 -0
- package/dist/sdk/chunk-B2OUZAWY.mjs +237 -0
- package/dist/sdk/chunk-B2OUZAWY.mjs.map +1 -0
- package/dist/sdk/chunk-FWWLD555.mjs +244 -0
- package/dist/sdk/chunk-FWWLD555.mjs.map +1 -0
- package/dist/sdk/{chunk-QXT47ZHR.mjs → chunk-G7GSN3SK.mjs} +2 -2
- package/dist/sdk/{chunk-QXT47ZHR.mjs.map → chunk-G7GSN3SK.mjs.map} +1 -1
- package/dist/sdk/{chunk-DHETLQIX.mjs → chunk-GA2TYKSR.mjs} +5 -5
- package/dist/sdk/{chunk-6DPPP7LD.mjs → chunk-IDL3AA3G.mjs} +203 -42
- package/dist/sdk/chunk-IDL3AA3G.mjs.map +1 -0
- package/dist/sdk/chunk-MEB2TTIE.mjs +157 -0
- package/dist/sdk/chunk-MEB2TTIE.mjs.map +1 -0
- package/dist/sdk/{chunk-3JFK6KCD.mjs → chunk-MFXPJUUE.mjs} +150 -280
- package/dist/sdk/chunk-MFXPJUUE.mjs.map +1 -0
- package/dist/sdk/{chunk-KBGQJKIZ.mjs → chunk-NPSLGKXB.mjs} +3 -3
- package/dist/sdk/chunk-P2K4VOMU.mjs +825 -0
- package/dist/sdk/chunk-P2K4VOMU.mjs.map +1 -0
- package/dist/sdk/chunk-RI4ONH5X.mjs +482 -0
- package/dist/sdk/chunk-RI4ONH5X.mjs.map +1 -0
- package/dist/sdk/chunk-S5FSRHMY.mjs +139 -0
- package/dist/sdk/chunk-S5FSRHMY.mjs.map +1 -0
- package/dist/sdk/{chunk-7ERVRLDV.mjs → chunk-TFUQ2D5L.mjs} +13 -2
- package/dist/sdk/chunk-TFUQ2D5L.mjs.map +1 -0
- package/dist/sdk/{chunk-TQQNSHQV.mjs → chunk-UXB4XWEE.mjs} +1044 -179
- package/dist/sdk/chunk-UXB4XWEE.mjs.map +1 -0
- package/dist/sdk/{chunk-U6K5SK7X.mjs → chunk-V45TITKX.mjs} +2 -2
- package/dist/sdk/{chunk-ANUT54HW.mjs → chunk-WKLJ57WF.mjs} +6 -6
- package/dist/sdk/chunk-XOAEKFKB.mjs +1150 -0
- package/dist/sdk/chunk-XOAEKFKB.mjs.map +1 -0
- package/dist/sdk/chunk-ZPYODGYA.mjs +251 -0
- package/dist/sdk/chunk-ZPYODGYA.mjs.map +1 -0
- package/dist/sdk/command-executor-YNJOS77A.mjs +14 -0
- package/dist/sdk/{config-2STD74CJ.mjs → config-PCP6O6Y6.mjs} +4 -4
- package/dist/sdk/{failure-condition-evaluator-FFWJRAEQ.mjs → failure-condition-evaluator-H3PBFBYT.mjs} +4 -4
- package/dist/sdk/failure-condition-evaluator-IRFKTYZD.mjs +18 -0
- package/dist/sdk/github-auth-BJQBLK2V.mjs +196 -0
- package/dist/sdk/github-auth-BJQBLK2V.mjs.map +1 -0
- package/dist/sdk/{github-frontend-L3F5JXPJ.mjs → github-frontend-DECYOBRN.mjs} +8 -8
- package/dist/sdk/{github-frontend-KGV2R5Z6.mjs → github-frontend-TZRBOQCN.mjs} +4 -4
- package/dist/sdk/{host-QBJ7TOWG.mjs → host-CFM2ASDI.mjs} +4 -4
- package/dist/sdk/{host-X5ZZCEWN.mjs → host-T4LNVU2H.mjs} +3 -3
- package/dist/sdk/{knex-store-QCEW4I4R.mjs → knex-store-OEWSZEBY.mjs} +3 -3
- package/dist/sdk/lazy-otel-5RDTVS5L.mjs +24 -0
- package/dist/sdk/liquid-extensions-E3AKRX7P.mjs +25 -0
- package/dist/sdk/{loader-ZNKKJEZ3.mjs → loader-WRGI244P.mjs} +5 -5
- package/dist/sdk/memory-store-OHUIXCWJ.mjs +12 -0
- package/dist/sdk/metrics-MYUPQBBV.mjs +41 -0
- package/dist/sdk/{opa-policy-engine-QCSSIMUF.mjs → opa-policy-engine-IVMCGVNA.mjs} +3 -3
- package/dist/sdk/prompt-state-LN57DQF3.mjs +16 -0
- package/dist/sdk/renderer-schema-BT2IXMLW.mjs +51 -0
- package/dist/sdk/renderer-schema-BT2IXMLW.mjs.map +1 -0
- package/dist/sdk/routing-H2PQ57OA.mjs +26 -0
- package/dist/sdk/{routing-CVQT4KHX.mjs → routing-JMZ7HDCC.mjs} +5 -5
- package/dist/sdk/schedule-tool-2DPNSU63.mjs +37 -0
- package/dist/sdk/{schedule-tool-AECLFHSY.mjs → schedule-tool-4M45RK3E.mjs} +6 -6
- package/dist/sdk/{schedule-tool-handler-6QLZRTQA.mjs → schedule-tool-handler-KLHE2SOW.mjs} +6 -6
- package/dist/sdk/schedule-tool-handler-KLHE2SOW.mjs.map +1 -0
- package/dist/sdk/{schedule-tool-handler-J4NUETJ6.mjs → schedule-tool-handler-NBEO46RV.mjs} +16 -16
- package/dist/sdk/schedule-tool-handler-NBEO46RV.mjs.map +1 -0
- package/dist/sdk/sdk.d.mts +2 -0
- package/dist/sdk/sdk.d.ts +2 -0
- package/dist/sdk/sdk.js +3125 -666
- package/dist/sdk/sdk.js.map +1 -1
- package/dist/sdk/sdk.mjs +15 -15
- package/dist/sdk/slack-frontend-DF5VL4OF.mjs +929 -0
- package/dist/sdk/slack-frontend-DF5VL4OF.mjs.map +1 -0
- package/dist/sdk/{task-evaluator-HLNXKKVV.mjs → task-evaluator-GQYDOSGT.mjs} +138 -24
- package/dist/sdk/task-evaluator-GQYDOSGT.mjs.map +1 -0
- package/dist/sdk/task-evaluator-OVMG7S56.mjs +263 -0
- package/dist/sdk/task-evaluator-OVMG7S56.mjs.map +1 -0
- package/dist/sdk/{trace-helpers-WJXYVV4S.mjs → trace-helpers-26ZCAE2V.mjs} +7 -5
- package/dist/sdk/trace-helpers-26ZCAE2V.mjs.map +1 -0
- package/dist/sdk/{trace-helpers-3FFAI7X3.mjs → trace-helpers-XV5GAX5L.mjs} +3 -3
- package/dist/sdk/trace-helpers-XV5GAX5L.mjs.map +1 -0
- package/dist/sdk/{trace-reader-ZY77OFNM.mjs → trace-reader-OVE4DL2D.mjs} +6 -2
- package/dist/sdk/trace-reader-OVE4DL2D.mjs.map +1 -0
- package/dist/sdk/trace-serializer-KKBJHM7J.mjs +24 -0
- package/dist/sdk/trace-serializer-KKBJHM7J.mjs.map +1 -0
- package/dist/sdk/{track-execution-AMQQNXKE.mjs → track-execution-3EC24C2X.mjs} +68 -7
- package/dist/sdk/track-execution-3EC24C2X.mjs.map +1 -0
- package/dist/sdk/{track-execution-MKIQXP2C.mjs → track-execution-66RLL6QT.mjs} +10 -3
- package/dist/sdk/track-execution-66RLL6QT.mjs.map +1 -0
- package/dist/sdk/utcp-check-provider-WI3QZ3W6.mjs +16 -0
- package/dist/sdk/utcp-check-provider-WI3QZ3W6.mjs.map +1 -0
- package/dist/sdk/workflow-check-provider-X2UREEH7.mjs +31 -0
- package/dist/sdk/workflow-check-provider-X2UREEH7.mjs.map +1 -0
- package/dist/sdk/{workflow-check-provider-EXMC6JIS.mjs → workflow-check-provider-YXALZNAQ.mjs} +6 -6
- package/dist/sdk/workflow-check-provider-YXALZNAQ.mjs.map +1 -0
- package/dist/sdk/workflow-registry-YCZ3FCJC.mjs +12 -0
- package/dist/sdk/workflow-registry-YCZ3FCJC.mjs.map +1 -0
- package/dist/slack/socket-runner.d.ts.map +1 -1
- package/dist/state-machine/dispatch/sandbox-routing.d.ts.map +1 -1
- package/dist/state-machine/states/level-dispatch.d.ts.map +1 -1
- package/dist/telemetry/fallback-ndjson.d.ts +21 -0
- package/dist/telemetry/fallback-ndjson.d.ts.map +1 -1
- package/dist/telemetry/lazy-otel.d.ts +2 -0
- package/dist/telemetry/lazy-otel.d.ts.map +1 -1
- package/dist/telemetry/opentelemetry.d.ts +5 -0
- package/dist/telemetry/opentelemetry.d.ts.map +1 -1
- package/dist/telemetry/trace-helpers.d.ts +10 -0
- package/dist/telemetry/trace-helpers.d.ts.map +1 -1
- package/dist/test-runner/conversation-sugar.d.ts +7 -0
- package/dist/test-runner/conversation-sugar.d.ts.map +1 -1
- package/dist/test-runner/core/flow-stage.d.ts.map +1 -1
- package/dist/test-runner/index.d.ts.map +1 -1
- package/dist/test-runner/validator.d.ts.map +1 -1
- package/dist/types/git-checkout.d.ts +2 -0
- package/dist/types/git-checkout.d.ts.map +1 -1
- package/dist/utils/script-tool-environment.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/sdk/a2a-frontend-4LP3MLTS.mjs.map +0 -1
- package/dist/sdk/check-provider-registry-RRWCXSTG.mjs +0 -31
- package/dist/sdk/chunk-3JFK6KCD.mjs.map +0 -1
- package/dist/sdk/chunk-4I3TJ7UJ.mjs.map +0 -1
- package/dist/sdk/chunk-6DPPP7LD.mjs.map +0 -1
- package/dist/sdk/chunk-6VVXKXTI.mjs +0 -164
- package/dist/sdk/chunk-6VVXKXTI.mjs.map +0 -1
- package/dist/sdk/chunk-7ERVRLDV.mjs.map +0 -1
- package/dist/sdk/chunk-TQQNSHQV.mjs.map +0 -1
- package/dist/sdk/failure-condition-evaluator-5DZYMCGW.mjs +0 -18
- package/dist/sdk/routing-XALEDC2G.mjs +0 -26
- package/dist/sdk/schedule-tool-Z6QYL2B3.mjs +0 -37
- package/dist/sdk/task-evaluator-HLNXKKVV.mjs.map +0 -1
- package/dist/sdk/trace-reader-ZY77OFNM.mjs.map +0 -1
- package/dist/sdk/track-execution-AMQQNXKE.mjs.map +0 -1
- package/dist/sdk/track-execution-MKIQXP2C.mjs.map +0 -1
- package/dist/sdk/workflow-check-provider-VKYGI5GK.mjs +0 -31
- /package/dist/sdk/{check-provider-registry-I4BCWKRU.mjs.map → check-provider-registry-WSEVHJEV.mjs.map} +0 -0
- /package/dist/sdk/{check-provider-registry-RRWCXSTG.mjs.map → check-provider-registry-YRADEEQY.mjs.map} +0 -0
- /package/dist/sdk/{chunk-DHETLQIX.mjs.map → chunk-GA2TYKSR.mjs.map} +0 -0
- /package/dist/sdk/{chunk-ANUT54HW.mjs.map → chunk-NPSLGKXB.mjs.map} +0 -0
- /package/dist/sdk/{chunk-U6K5SK7X.mjs.map → chunk-V45TITKX.mjs.map} +0 -0
- /package/dist/sdk/{chunk-KBGQJKIZ.mjs.map → chunk-WKLJ57WF.mjs.map} +0 -0
- /package/dist/sdk/{config-2STD74CJ.mjs.map → command-executor-YNJOS77A.mjs.map} +0 -0
- /package/dist/sdk/{failure-condition-evaluator-5DZYMCGW.mjs.map → config-PCP6O6Y6.mjs.map} +0 -0
- /package/dist/sdk/{failure-condition-evaluator-FFWJRAEQ.mjs.map → failure-condition-evaluator-H3PBFBYT.mjs.map} +0 -0
- /package/dist/sdk/{routing-CVQT4KHX.mjs.map → failure-condition-evaluator-IRFKTYZD.mjs.map} +0 -0
- /package/dist/sdk/{github-frontend-KGV2R5Z6.mjs.map → github-frontend-DECYOBRN.mjs.map} +0 -0
- /package/dist/sdk/{github-frontend-L3F5JXPJ.mjs.map → github-frontend-TZRBOQCN.mjs.map} +0 -0
- /package/dist/sdk/{host-QBJ7TOWG.mjs.map → host-CFM2ASDI.mjs.map} +0 -0
- /package/dist/sdk/{host-X5ZZCEWN.mjs.map → host-T4LNVU2H.mjs.map} +0 -0
- /package/dist/sdk/{knex-store-QCEW4I4R.mjs.map → knex-store-OEWSZEBY.mjs.map} +0 -0
- /package/dist/sdk/{routing-XALEDC2G.mjs.map → lazy-otel-5RDTVS5L.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-AECLFHSY.mjs.map → liquid-extensions-E3AKRX7P.mjs.map} +0 -0
- /package/dist/sdk/{loader-ZNKKJEZ3.mjs.map → loader-WRGI244P.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-Z6QYL2B3.mjs.map → memory-store-OHUIXCWJ.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-handler-6QLZRTQA.mjs.map → metrics-MYUPQBBV.mjs.map} +0 -0
- /package/dist/sdk/{opa-policy-engine-QCSSIMUF.mjs.map → opa-policy-engine-IVMCGVNA.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-handler-J4NUETJ6.mjs.map → prompt-state-LN57DQF3.mjs.map} +0 -0
- /package/dist/sdk/{trace-helpers-3FFAI7X3.mjs.map → routing-H2PQ57OA.mjs.map} +0 -0
- /package/dist/sdk/{trace-helpers-WJXYVV4S.mjs.map → routing-JMZ7HDCC.mjs.map} +0 -0
- /package/dist/sdk/{workflow-check-provider-EXMC6JIS.mjs.map → schedule-tool-2DPNSU63.mjs.map} +0 -0
- /package/dist/sdk/{workflow-check-provider-VKYGI5GK.mjs.map → schedule-tool-4M45RK3E.mjs.map} +0 -0
|
@@ -0,0 +1,1700 @@
|
|
|
1
|
+
import {
|
|
2
|
+
init_logger,
|
|
3
|
+
logger
|
|
4
|
+
} from "./chunk-ZPYODGYA.mjs";
|
|
5
|
+
import {
|
|
6
|
+
__esm
|
|
7
|
+
} from "./chunk-J7LXIPZS.mjs";
|
|
8
|
+
|
|
9
|
+
// src/agent-protocol/trace-serializer.ts
|
|
10
|
+
import * as fs from "fs";
|
|
11
|
+
import * as readline from "readline";
|
|
12
|
+
import * as path from "path";
|
|
13
|
+
function resolveBackendConfig(overrides) {
|
|
14
|
+
const explicit = process.env.VISOR_TRACE_BACKEND;
|
|
15
|
+
return {
|
|
16
|
+
type: overrides?.type || explicit || "auto",
|
|
17
|
+
grafanaUrl: overrides?.grafanaUrl || process.env.GRAFANA_URL,
|
|
18
|
+
grafanaDatasourceId: overrides?.grafanaDatasourceId || process.env.GRAFANA_TEMPO_DATASOURCE_ID,
|
|
19
|
+
jaegerUrl: overrides?.jaegerUrl || process.env.JAEGER_URL,
|
|
20
|
+
traceDir: overrides?.traceDir || process.env.VISOR_TRACE_DIR || "output/traces",
|
|
21
|
+
authToken: overrides?.authToken || process.env.GRAFANA_TOKEN
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function getAutoBackendOrder() {
|
|
25
|
+
const sink = (process.env.VISOR_TELEMETRY_SINK || "").trim().toLowerCase();
|
|
26
|
+
const hasRemoteHints = !!process.env.GRAFANA_URL || !!process.env.JAEGER_URL || !!process.env.GRAFANA_TEMPO_DATASOURCE_ID || !!process.env.OTEL_EXPORTER_OTLP_ENDPOINT || !!process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT;
|
|
27
|
+
if (sink === "file") return ["file", "grafana", "jaeger"];
|
|
28
|
+
if (sink === "otlp" || hasRemoteHints) return ["grafana", "jaeger", "file"];
|
|
29
|
+
return ["file", "grafana", "jaeger"];
|
|
30
|
+
}
|
|
31
|
+
function getBackendOrder(cfg) {
|
|
32
|
+
if (cfg.type === "grafana") return ["grafana"];
|
|
33
|
+
if (cfg.type === "jaeger") return ["jaeger"];
|
|
34
|
+
if (cfg.type === "file") return ["file"];
|
|
35
|
+
return getAutoBackendOrder();
|
|
36
|
+
}
|
|
37
|
+
function isTraceFilePath(ref) {
|
|
38
|
+
return ref.includes("/") || ref.endsWith(".ndjson");
|
|
39
|
+
}
|
|
40
|
+
function parseOTLPResponse(data) {
|
|
41
|
+
const spans = [];
|
|
42
|
+
if (data.batches) {
|
|
43
|
+
for (const batch of data.batches) {
|
|
44
|
+
for (const ss of batch.scopeSpans || []) {
|
|
45
|
+
for (const s of ss.spans || []) {
|
|
46
|
+
spans.push(normalizeOTLPSpan(s));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return spans;
|
|
51
|
+
}
|
|
52
|
+
if (data.data && Array.isArray(data.data)) {
|
|
53
|
+
for (const trace of data.data) {
|
|
54
|
+
for (const s of trace.spans || []) {
|
|
55
|
+
spans.push(normalizeJaegerSpan(s, trace.traceID));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return spans;
|
|
59
|
+
}
|
|
60
|
+
return spans;
|
|
61
|
+
}
|
|
62
|
+
function normalizeOTLPSpan(s) {
|
|
63
|
+
const startNs = parseInt(s.startTimeUnixNano || "0", 10);
|
|
64
|
+
const endNs = parseInt(s.endTimeUnixNano || "0", 10);
|
|
65
|
+
const traceId = decodeOTLPId(s.traceId);
|
|
66
|
+
const spanId = decodeOTLPId(s.spanId);
|
|
67
|
+
const parentSpanId = s.parentSpanId ? decodeOTLPId(s.parentSpanId) : void 0;
|
|
68
|
+
const attributes = {};
|
|
69
|
+
for (const attr of s.attributes || []) {
|
|
70
|
+
const val = attr.value;
|
|
71
|
+
if (val.stringValue !== void 0) attributes[attr.key] = val.stringValue;
|
|
72
|
+
else if (val.intValue !== void 0) attributes[attr.key] = parseInt(val.intValue, 10);
|
|
73
|
+
else if (val.boolValue !== void 0) attributes[attr.key] = val.boolValue;
|
|
74
|
+
else if (val.doubleValue !== void 0) attributes[attr.key] = val.doubleValue;
|
|
75
|
+
}
|
|
76
|
+
const events = [];
|
|
77
|
+
for (const evt of s.events || []) {
|
|
78
|
+
const evtAttrs = {};
|
|
79
|
+
for (const a of evt.attributes || []) {
|
|
80
|
+
const v = a.value;
|
|
81
|
+
if (v.stringValue !== void 0) evtAttrs[a.key] = v.stringValue;
|
|
82
|
+
else if (v.intValue !== void 0) evtAttrs[a.key] = parseInt(v.intValue, 10);
|
|
83
|
+
}
|
|
84
|
+
events.push({ name: evt.name, attributes: evtAttrs });
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
traceId,
|
|
88
|
+
spanId,
|
|
89
|
+
parentSpanId,
|
|
90
|
+
name: s.name || "unknown",
|
|
91
|
+
startTimeMs: startNs / 1e6,
|
|
92
|
+
endTimeMs: endNs / 1e6,
|
|
93
|
+
durationMs: (endNs - startNs) / 1e6,
|
|
94
|
+
attributes,
|
|
95
|
+
events,
|
|
96
|
+
status: s.status?.code === 2 ? "error" : "ok"
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function normalizeJaegerSpan(s, traceId) {
|
|
100
|
+
const attributes = {};
|
|
101
|
+
for (const tag of s.tags || []) {
|
|
102
|
+
attributes[tag.key] = tag.value;
|
|
103
|
+
}
|
|
104
|
+
const events = [];
|
|
105
|
+
for (const log of s.logs || []) {
|
|
106
|
+
const evtAttrs = {};
|
|
107
|
+
for (const f of log.fields || []) evtAttrs[f.key] = f.value;
|
|
108
|
+
events.push({ name: evtAttrs["event"] || "log", attributes: evtAttrs });
|
|
109
|
+
}
|
|
110
|
+
const startUs = s.startTime || 0;
|
|
111
|
+
const durationUs = s.duration || 0;
|
|
112
|
+
return {
|
|
113
|
+
traceId,
|
|
114
|
+
spanId: s.spanID,
|
|
115
|
+
parentSpanId: s.references?.find((r) => r.refType === "CHILD_OF")?.spanID,
|
|
116
|
+
name: s.operationName || "unknown",
|
|
117
|
+
startTimeMs: startUs / 1e3,
|
|
118
|
+
endTimeMs: (startUs + durationUs) / 1e3,
|
|
119
|
+
durationMs: durationUs / 1e3,
|
|
120
|
+
attributes,
|
|
121
|
+
events,
|
|
122
|
+
status: attributes["otel.status_code"] === "ERROR" || attributes["error"] === true ? "error" : "ok"
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function decodeOTLPId(id) {
|
|
126
|
+
if (!id) return "";
|
|
127
|
+
if (/^[0-9a-f]+$/i.test(id)) return id.toLowerCase();
|
|
128
|
+
try {
|
|
129
|
+
return Buffer.from(id, "base64").toString("hex");
|
|
130
|
+
} catch {
|
|
131
|
+
return id;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function parseLocalNDJSONSpans(spans) {
|
|
135
|
+
return spans.map((s) => {
|
|
136
|
+
const startMs = timeValueToMs(s.startTime || [0, 0]);
|
|
137
|
+
const endMs = timeValueToMs(s.endTime || s.startTime || [0, 0]);
|
|
138
|
+
const events = (s.events || []).map((e) => ({
|
|
139
|
+
name: e.name,
|
|
140
|
+
attributes: e.attributes || {}
|
|
141
|
+
}));
|
|
142
|
+
return {
|
|
143
|
+
traceId: s.traceId || "",
|
|
144
|
+
spanId: s.spanId || "",
|
|
145
|
+
parentSpanId: s.parentSpanId || void 0,
|
|
146
|
+
name: s.name || "unknown",
|
|
147
|
+
startTimeMs: startMs,
|
|
148
|
+
endTimeMs: endMs,
|
|
149
|
+
durationMs: endMs - startMs,
|
|
150
|
+
attributes: s.attributes || {},
|
|
151
|
+
events,
|
|
152
|
+
status: s.status?.code === 2 ? "error" : "ok"
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function timeValueToMs(tv) {
|
|
157
|
+
return tv[0] * 1e3 + tv[1] / 1e6;
|
|
158
|
+
}
|
|
159
|
+
async function fetchTraceSpans(traceRef, config) {
|
|
160
|
+
const cfg = resolveBackendConfig(config);
|
|
161
|
+
const backendOrder = getBackendOrder(cfg);
|
|
162
|
+
const traceId = isTraceFilePath(traceRef) ? await readTraceIdFromFile(traceRef) : traceRef;
|
|
163
|
+
for (const backend of backendOrder) {
|
|
164
|
+
if (backend === "grafana" && traceId) {
|
|
165
|
+
const spans = await fetchFromGrafanaTempo(traceId, cfg);
|
|
166
|
+
if (spans && spans.length > 0) return spans;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (backend === "jaeger" && traceId) {
|
|
170
|
+
const spans = await fetchFromJaeger(traceId, cfg);
|
|
171
|
+
if (spans && spans.length > 0) return spans;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (backend === "file") {
|
|
175
|
+
const spans = await fetchFromLocalFiles(traceRef, cfg);
|
|
176
|
+
if (spans && spans.length > 0) return spans;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
async function fetchFromGrafanaTempo(traceId, cfg) {
|
|
183
|
+
let grafanaUrl = cfg.grafanaUrl;
|
|
184
|
+
if (!grafanaUrl) {
|
|
185
|
+
const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
186
|
+
if (otlpEndpoint) {
|
|
187
|
+
const url = new URL(otlpEndpoint);
|
|
188
|
+
const host = url.hostname;
|
|
189
|
+
for (const port of ["3000", "8001", "80"]) {
|
|
190
|
+
try {
|
|
191
|
+
const testUrl = `http://${host}:${port}/api/health`;
|
|
192
|
+
const resp = await httpGet(testUrl, cfg.authToken, 2e3);
|
|
193
|
+
if (resp && resp.includes('"database"')) {
|
|
194
|
+
grafanaUrl = `http://${host}:${port}`;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
} catch {
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (!grafanaUrl) return null;
|
|
203
|
+
try {
|
|
204
|
+
let dsId = cfg.grafanaDatasourceId;
|
|
205
|
+
if (!dsId) {
|
|
206
|
+
const dsResp = await httpGet(`${grafanaUrl}/api/datasources`, cfg.authToken);
|
|
207
|
+
if (dsResp) {
|
|
208
|
+
const datasources = JSON.parse(dsResp);
|
|
209
|
+
const tempo = datasources.find((d) => d.type === "tempo");
|
|
210
|
+
if (tempo) dsId = tempo.id;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (!dsId) return null;
|
|
214
|
+
const traceUrl = `${grafanaUrl}/api/datasources/proxy/${dsId}/api/traces/${traceId}`;
|
|
215
|
+
const resp = await httpGet(traceUrl, cfg.authToken);
|
|
216
|
+
if (!resp) return null;
|
|
217
|
+
const data = JSON.parse(resp);
|
|
218
|
+
return parseOTLPResponse(data);
|
|
219
|
+
} catch (err) {
|
|
220
|
+
logger.debug(`[TraceSerializer] Grafana Tempo fetch failed: ${err}`);
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function fetchFromJaeger(traceId, cfg) {
|
|
225
|
+
let jaegerUrl = cfg.jaegerUrl;
|
|
226
|
+
if (!jaegerUrl) {
|
|
227
|
+
const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
228
|
+
const host = otlpEndpoint ? new URL(otlpEndpoint).hostname : "localhost";
|
|
229
|
+
for (const port of ["16686"]) {
|
|
230
|
+
try {
|
|
231
|
+
const testUrl = `http://${host}:${port}/api/services`;
|
|
232
|
+
const resp = await httpGet(testUrl, void 0, 2e3);
|
|
233
|
+
if (resp && resp.includes('"data"')) {
|
|
234
|
+
jaegerUrl = `http://${host}:${port}`;
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (!jaegerUrl) return null;
|
|
242
|
+
try {
|
|
243
|
+
const traceUrl = `${jaegerUrl}/api/traces/${traceId}`;
|
|
244
|
+
const resp = await httpGet(traceUrl, cfg.authToken);
|
|
245
|
+
if (!resp) return null;
|
|
246
|
+
const data = JSON.parse(resp);
|
|
247
|
+
return parseOTLPResponse(data);
|
|
248
|
+
} catch (err) {
|
|
249
|
+
logger.debug(`[TraceSerializer] Jaeger fetch failed: ${err}`);
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async function fetchFromLocalFiles(traceRef, cfg) {
|
|
254
|
+
const traceFile = isTraceFilePath(traceRef) ? traceRef : await findTraceFile(traceRef, cfg.traceDir);
|
|
255
|
+
if (!traceFile) return null;
|
|
256
|
+
try {
|
|
257
|
+
const { parseNDJSONTrace } = await import("./trace-reader-OVE4DL2D.mjs");
|
|
258
|
+
const trace = await parseNDJSONTrace(traceFile);
|
|
259
|
+
return parseLocalNDJSONSpans(trace.spans);
|
|
260
|
+
} catch (err) {
|
|
261
|
+
logger.debug(`[TraceSerializer] Local file parse failed: ${err}`);
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async function httpGet(url, authToken, timeoutMs) {
|
|
266
|
+
try {
|
|
267
|
+
const controller = new AbortController();
|
|
268
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs || 1e4);
|
|
269
|
+
const headers = {};
|
|
270
|
+
if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
|
|
271
|
+
const resp = await fetch(url, {
|
|
272
|
+
signal: controller.signal,
|
|
273
|
+
headers
|
|
274
|
+
});
|
|
275
|
+
clearTimeout(timeout);
|
|
276
|
+
if (!resp.ok) return null;
|
|
277
|
+
return await resp.text();
|
|
278
|
+
} catch {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async function findTraceFile(traceId, traceDir) {
|
|
283
|
+
const dir = traceDir || process.env.VISOR_TRACE_DIR || "output/traces";
|
|
284
|
+
if (!fs.existsSync(dir)) return null;
|
|
285
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith(".ndjson"));
|
|
286
|
+
for (const file of files) {
|
|
287
|
+
const filePath = path.join(dir, file);
|
|
288
|
+
try {
|
|
289
|
+
const firstLine = await readFirstLine(filePath);
|
|
290
|
+
if (!firstLine) continue;
|
|
291
|
+
const parsed = JSON.parse(firstLine);
|
|
292
|
+
if (parsed.traceId === traceId) return filePath;
|
|
293
|
+
} catch {
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
async function readTraceIdFromFile(traceFile) {
|
|
299
|
+
try {
|
|
300
|
+
const rl = readline.createInterface({
|
|
301
|
+
input: fs.createReadStream(traceFile, { encoding: "utf8" }),
|
|
302
|
+
crlfDelay: Infinity
|
|
303
|
+
});
|
|
304
|
+
for await (const line of rl) {
|
|
305
|
+
const trimmed = line.trim();
|
|
306
|
+
if (!trimmed) continue;
|
|
307
|
+
try {
|
|
308
|
+
const parsed = JSON.parse(trimmed);
|
|
309
|
+
if (typeof parsed.traceId === "string" && parsed.traceId) {
|
|
310
|
+
rl.close();
|
|
311
|
+
return parsed.traceId;
|
|
312
|
+
}
|
|
313
|
+
} catch {
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
} catch {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
async function readFirstLine(filePath) {
|
|
322
|
+
return new Promise((resolve, reject) => {
|
|
323
|
+
const stream = fs.createReadStream(filePath, { encoding: "utf-8" });
|
|
324
|
+
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
325
|
+
let resolved = false;
|
|
326
|
+
rl.on("line", (line) => {
|
|
327
|
+
if (!resolved) {
|
|
328
|
+
resolved = true;
|
|
329
|
+
rl.close();
|
|
330
|
+
stream.destroy();
|
|
331
|
+
resolve(line.trim() || null);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
rl.on("close", () => {
|
|
335
|
+
if (!resolved) resolve(null);
|
|
336
|
+
});
|
|
337
|
+
rl.on("error", reject);
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
function isNoiseSpan(span) {
|
|
341
|
+
return NOISE_SPAN_NAMES.has(span.name);
|
|
342
|
+
}
|
|
343
|
+
function isWrapperSpan(span) {
|
|
344
|
+
return WRAPPER_SPAN_NAMES.has(span.name);
|
|
345
|
+
}
|
|
346
|
+
function buildSpanTree(spans) {
|
|
347
|
+
const filtered = spans.filter((s) => !isNoiseSpan(s));
|
|
348
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
349
|
+
for (const span of filtered) {
|
|
350
|
+
nodeMap.set(span.spanId, { span, children: [] });
|
|
351
|
+
}
|
|
352
|
+
let root;
|
|
353
|
+
const orphans = [];
|
|
354
|
+
for (const span of filtered) {
|
|
355
|
+
const node = nodeMap.get(span.spanId);
|
|
356
|
+
if (!span.parentSpanId) {
|
|
357
|
+
root = node;
|
|
358
|
+
} else {
|
|
359
|
+
let parentId = span.parentSpanId;
|
|
360
|
+
while (parentId && !nodeMap.has(parentId)) {
|
|
361
|
+
const parentSpan = spans.find((s) => s.spanId === parentId);
|
|
362
|
+
parentId = parentSpan?.parentSpanId;
|
|
363
|
+
}
|
|
364
|
+
if (parentId) {
|
|
365
|
+
const parent = nodeMap.get(parentId);
|
|
366
|
+
if (parent) parent.children.push(node);
|
|
367
|
+
} else if (!root) {
|
|
368
|
+
root = node;
|
|
369
|
+
} else {
|
|
370
|
+
orphans.push(node);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (!root) {
|
|
375
|
+
const sorted = [...nodeMap.values()].sort((a, b) => b.span.durationMs - a.span.durationMs);
|
|
376
|
+
root = sorted[0] || { span: filtered[0], children: [] };
|
|
377
|
+
}
|
|
378
|
+
if (orphans.length > 0) {
|
|
379
|
+
root.children.push(...orphans);
|
|
380
|
+
}
|
|
381
|
+
const sortChildren = (node) => {
|
|
382
|
+
node.children.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
|
|
383
|
+
node.children.forEach(sortChildren);
|
|
384
|
+
};
|
|
385
|
+
sortChildren(root);
|
|
386
|
+
const unwrap = (node) => {
|
|
387
|
+
node.children = node.children.map(unwrap);
|
|
388
|
+
const newChildren = [];
|
|
389
|
+
for (const child of node.children) {
|
|
390
|
+
if (isWrapperSpan(child.span)) {
|
|
391
|
+
newChildren.push(...child.children);
|
|
392
|
+
} else {
|
|
393
|
+
newChildren.push(child);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
node.children = newChildren;
|
|
397
|
+
return node;
|
|
398
|
+
};
|
|
399
|
+
unwrap(root);
|
|
400
|
+
const removeDelegateEchos = (node) => {
|
|
401
|
+
const hasDelegateChild = node.children.some((c) => c.span.name === "search.delegate");
|
|
402
|
+
if (hasDelegateChild) {
|
|
403
|
+
node.children = node.children.filter((c) => {
|
|
404
|
+
if (c.span.name !== "probe.event.tool.result") return true;
|
|
405
|
+
const toolName = c.span.attributes["tool.name"];
|
|
406
|
+
return toolName !== "search";
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
node.children.forEach(removeDelegateEchos);
|
|
410
|
+
};
|
|
411
|
+
removeDelegateEchos(root);
|
|
412
|
+
return root;
|
|
413
|
+
}
|
|
414
|
+
function dedupeKey(text) {
|
|
415
|
+
return text.replace(/\s+/g, " ").trim().slice(0, 100).toLowerCase();
|
|
416
|
+
}
|
|
417
|
+
function dedupeOrRegister(ctx, kind, text, spanName) {
|
|
418
|
+
if (!text || text.length < 20) return null;
|
|
419
|
+
const key = dedupeKey(text);
|
|
420
|
+
if (!key) return null;
|
|
421
|
+
const map = ctx[kind];
|
|
422
|
+
const existing = map.get(key);
|
|
423
|
+
if (existing && existing !== spanName) {
|
|
424
|
+
return existing;
|
|
425
|
+
}
|
|
426
|
+
const otherMap = kind === "outputs" ? ctx.intents : ctx.outputs;
|
|
427
|
+
const crossRef = otherMap.get(key);
|
|
428
|
+
if (crossRef && crossRef !== spanName) {
|
|
429
|
+
map.set(key, spanName);
|
|
430
|
+
return crossRef;
|
|
431
|
+
}
|
|
432
|
+
map.set(key, spanName);
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
function normalizeSpanName(name) {
|
|
436
|
+
return name.replace(/^child:\s*/, "");
|
|
437
|
+
}
|
|
438
|
+
function isChildSpan(span) {
|
|
439
|
+
return span.name.startsWith("child: ") || span.attributes["visor.sandbox.child_span"] === true;
|
|
440
|
+
}
|
|
441
|
+
function getConcreteCheckName(span) {
|
|
442
|
+
const attrs = span.attributes || {};
|
|
443
|
+
const normalizedName = normalizeSpanName(span.name || "");
|
|
444
|
+
const checkId = attrs["visor.check.id"];
|
|
445
|
+
if (checkId) return `visor.check.${String(checkId).replace(/^visor\.check\./, "")}`;
|
|
446
|
+
const lifecycle = getLifecycleSpanInfo(normalizedName);
|
|
447
|
+
if (lifecycle?.kind === "check") return lifecycle.baseName;
|
|
448
|
+
const match = normalizedName.match(/^visor\.check\.([^.]+)$/);
|
|
449
|
+
if (match) return `visor.check.${match[1]}`;
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
function getLifecycleSpanInfo(name) {
|
|
453
|
+
const normalizedName = normalizeSpanName(name);
|
|
454
|
+
const checkMatch = normalizedName.match(
|
|
455
|
+
/^visor\.check\.([^.]+)\.(started|completed|failed|progress)$/
|
|
456
|
+
);
|
|
457
|
+
if (checkMatch) {
|
|
458
|
+
return {
|
|
459
|
+
baseName: `visor.check.${checkMatch[1]}`,
|
|
460
|
+
phase: checkMatch[2],
|
|
461
|
+
kind: "check"
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
const sandboxChildMatch = normalizedName.match(
|
|
465
|
+
/^visor\.sandbox\.child\.(started|waiting|completed|failed)$/
|
|
466
|
+
);
|
|
467
|
+
if (sandboxChildMatch) {
|
|
468
|
+
return {
|
|
469
|
+
baseName: "visor.sandbox.child",
|
|
470
|
+
phase: sandboxChildMatch[1],
|
|
471
|
+
kind: "sandbox-child"
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
const engineerMatch = normalizedName.match(
|
|
475
|
+
/^visor\.engineer\.(started|sandbox_resolved|child_spawned|waiting_on_child|completed|failed|progress)$/
|
|
476
|
+
);
|
|
477
|
+
if (engineerMatch) {
|
|
478
|
+
return {
|
|
479
|
+
baseName: "visor.engineer",
|
|
480
|
+
phase: engineerMatch[1],
|
|
481
|
+
kind: "engineer"
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
const genericMatch = normalizedName.match(
|
|
485
|
+
/^(ai\.request|search\.delegate|visor\.ai_check|probe\.ai_request|probe\.search_delegate)\.(started|completed|failed)$/
|
|
486
|
+
);
|
|
487
|
+
if (genericMatch) {
|
|
488
|
+
const baseName = genericMatch[1] === "probe.ai_request" ? "ai.request" : genericMatch[1] === "probe.search_delegate" ? "search.delegate" : genericMatch[1];
|
|
489
|
+
return {
|
|
490
|
+
baseName,
|
|
491
|
+
phase: genericMatch[2],
|
|
492
|
+
kind: "generic"
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
function buildRenderContext(allSpans) {
|
|
498
|
+
const realSpanNames = /* @__PURE__ */ new Set();
|
|
499
|
+
let hasChildWorkSpans = false;
|
|
500
|
+
for (const span of allSpans) {
|
|
501
|
+
const normalizedName = normalizeSpanName(span.name);
|
|
502
|
+
const lifecycle = getLifecycleSpanInfo(normalizedName);
|
|
503
|
+
if (!lifecycle) {
|
|
504
|
+
const checkName = getConcreteCheckName(span);
|
|
505
|
+
if (checkName) realSpanNames.add(checkName);
|
|
506
|
+
else realSpanNames.add(normalizedName);
|
|
507
|
+
}
|
|
508
|
+
if (isChildSpan(span) && !lifecycle) {
|
|
509
|
+
hasChildWorkSpans = true;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return { realSpanNames, hasChildWorkSpans };
|
|
513
|
+
}
|
|
514
|
+
function shouldSkipLifecycleSpan(span, renderContext) {
|
|
515
|
+
const lifecycle = getLifecycleSpanInfo(span.name);
|
|
516
|
+
if (!lifecycle) return false;
|
|
517
|
+
if (lifecycle.kind === "sandbox-child") {
|
|
518
|
+
return renderContext.hasChildWorkSpans;
|
|
519
|
+
}
|
|
520
|
+
return renderContext.realSpanNames.has(lifecycle.baseName);
|
|
521
|
+
}
|
|
522
|
+
async function serializeTraceForPrompt(traceIdOrPath, maxChars, backendConfig, taskResponse, fallbackTraceId) {
|
|
523
|
+
let spans = [];
|
|
524
|
+
const cfg = resolveBackendConfig(backendConfig);
|
|
525
|
+
const backendOrder = getBackendOrder(cfg);
|
|
526
|
+
const isFilePath = isTraceFilePath(traceIdOrPath);
|
|
527
|
+
const localTracePath = isFilePath ? traceIdOrPath : void 0;
|
|
528
|
+
const remoteTraceId = fallbackTraceId || (!isFilePath ? traceIdOrPath : await readTraceIdFromFile(traceIdOrPath) || void 0);
|
|
529
|
+
const preferLocalFirst = backendOrder[0] === "file";
|
|
530
|
+
if (preferLocalFirst && localTracePath) {
|
|
531
|
+
spans = await fetchTraceSpans(localTracePath, { ...cfg, type: "file" });
|
|
532
|
+
}
|
|
533
|
+
if (spans.length === 0 && remoteTraceId) {
|
|
534
|
+
spans = await fetchTraceSpans(remoteTraceId, cfg);
|
|
535
|
+
}
|
|
536
|
+
if (spans.length === 0 && localTracePath) {
|
|
537
|
+
spans = await fetchTraceSpans(localTracePath, { ...cfg, type: "file" });
|
|
538
|
+
}
|
|
539
|
+
if (spans.length === 0) {
|
|
540
|
+
return "(no trace data available)";
|
|
541
|
+
}
|
|
542
|
+
const tree = buildSpanTree(spans);
|
|
543
|
+
const routeIntentTopic = extractRouteIntentTopic(spans);
|
|
544
|
+
const fullOutput = (maxChars ?? 4e3) > 1e5;
|
|
545
|
+
return renderSpanYaml(tree, spans, {
|
|
546
|
+
maxChars: maxChars ?? 4e3,
|
|
547
|
+
fallbackIntent: routeIntentTopic,
|
|
548
|
+
fullOutput,
|
|
549
|
+
taskResponse
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
function renderSpanTree(tree, opts) {
|
|
553
|
+
const maxChars = opts?.maxChars ?? 4e3;
|
|
554
|
+
const maxDepth = opts?.maxDepth ?? 20;
|
|
555
|
+
const lines = [];
|
|
556
|
+
const dedup = { outputs: /* @__PURE__ */ new Map(), intents: /* @__PURE__ */ new Map() };
|
|
557
|
+
renderNode(
|
|
558
|
+
tree,
|
|
559
|
+
"",
|
|
560
|
+
true,
|
|
561
|
+
0,
|
|
562
|
+
maxDepth,
|
|
563
|
+
lines,
|
|
564
|
+
void 0,
|
|
565
|
+
opts?.fallbackIntent,
|
|
566
|
+
opts?.fullOutput,
|
|
567
|
+
dedup
|
|
568
|
+
);
|
|
569
|
+
let result = lines.join("\n");
|
|
570
|
+
if (result.length > maxChars) {
|
|
571
|
+
result = result.slice(0, maxChars - 20) + "\n... (truncated)";
|
|
572
|
+
}
|
|
573
|
+
return result;
|
|
574
|
+
}
|
|
575
|
+
function renderTraceTree(tree, opts) {
|
|
576
|
+
const convert = (node) => ({
|
|
577
|
+
span: {
|
|
578
|
+
traceId: node.span?.traceId || "",
|
|
579
|
+
spanId: node.span?.spanId || "",
|
|
580
|
+
parentSpanId: node.span?.parentSpanId,
|
|
581
|
+
name: node.span?.name || node.checkId || "unknown",
|
|
582
|
+
startTimeMs: timeValueToMs(node.span?.startTime || [0, 0]),
|
|
583
|
+
endTimeMs: timeValueToMs(node.span?.endTime || [0, 0]),
|
|
584
|
+
durationMs: node.span?.duration || 0,
|
|
585
|
+
attributes: node.span?.attributes || {},
|
|
586
|
+
events: (node.span?.events || []).map((e) => ({
|
|
587
|
+
name: e.name,
|
|
588
|
+
attributes: e.attributes || {}
|
|
589
|
+
})),
|
|
590
|
+
status: node.status === "error" ? "error" : "ok"
|
|
591
|
+
},
|
|
592
|
+
children: (node.children || []).map(convert)
|
|
593
|
+
});
|
|
594
|
+
return renderSpanTree(convert(tree), opts);
|
|
595
|
+
}
|
|
596
|
+
function renderSpanYaml(tree, allSpans, opts) {
|
|
597
|
+
const fullOutput = opts?.fullOutput ?? false;
|
|
598
|
+
const maxLen = fullOutput ? 1e5 : 120;
|
|
599
|
+
const dedup = { outputs: /* @__PURE__ */ new Map(), intents: /* @__PURE__ */ new Map() };
|
|
600
|
+
const renderContext = buildRenderContext(allSpans);
|
|
601
|
+
const lines = [];
|
|
602
|
+
renderYamlNode(tree, 0, lines, dedup, renderContext, opts?.fallbackIntent, fullOutput, maxLen);
|
|
603
|
+
if (opts?.taskResponse) {
|
|
604
|
+
while (lines.length > 0 && /^\s*output:\s*=\s*\S+/.test(lines[lines.length - 1])) {
|
|
605
|
+
lines.pop();
|
|
606
|
+
}
|
|
607
|
+
const ml = fullOutput ? 1e5 : 500;
|
|
608
|
+
const text = opts.taskResponse.replace(/\*\*/g, "").replace(/`/g, "").trim();
|
|
609
|
+
if (fullOutput) {
|
|
610
|
+
lines.push(" response: |");
|
|
611
|
+
for (const line of text.split("\n")) {
|
|
612
|
+
lines.push(` ${line}`);
|
|
613
|
+
}
|
|
614
|
+
} else {
|
|
615
|
+
const truncated = truncate(text.replace(/\n/g, " "), ml);
|
|
616
|
+
lines.push(` response: ${truncated}`);
|
|
617
|
+
if (text.length > ml) {
|
|
618
|
+
lines.push(" # use --full for complete response");
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return lines.join("\n");
|
|
623
|
+
}
|
|
624
|
+
function renderYamlNode(node, indent, lines, dedup, renderContext, fallbackIntent, fullOutput, maxLen, parentSpan) {
|
|
625
|
+
if (shouldSkipLifecycleSpan(node.span, renderContext)) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
const pad = " ".repeat(indent);
|
|
629
|
+
const attrs = node.span.attributes;
|
|
630
|
+
const duration = formatDurationMs(node.span.durationMs);
|
|
631
|
+
const rawName = node.span.name;
|
|
632
|
+
const name = normalizeSpanName(rawName);
|
|
633
|
+
const lifecycle = getLifecycleSpanInfo(name);
|
|
634
|
+
const childSuffix = isChildSpan(node.span) ? " [child]" : "";
|
|
635
|
+
const ml = maxLen ?? 120;
|
|
636
|
+
const parentCheckId = parentSpan?.attributes["visor.check.id"];
|
|
637
|
+
const parentCheckName = parentCheckId ? String(parentCheckId).replace(/^visor\.check\./, "") : void 0;
|
|
638
|
+
const displayName = name === "ai.request" && parentCheckName ? parentCheckName : String(attrs["visor.check.id"] || name).replace(/^visor\.check\./, "");
|
|
639
|
+
if (lifecycle) {
|
|
640
|
+
if (lifecycle.kind === "check") {
|
|
641
|
+
const cleanName = lifecycle.baseName.replace(/^visor\.check\./, "");
|
|
642
|
+
lines.push(`${pad}${cleanName}${childSuffix} [${lifecycle.phase}] \u2014 ${duration}`);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
if (lifecycle.kind === "sandbox-child") {
|
|
646
|
+
const checkName = attrs["visor.check.name"] ? ` ${String(attrs["visor.check.name"])}` : "";
|
|
647
|
+
lines.push(
|
|
648
|
+
`${pad}sandbox.child${checkName}${childSuffix} [${lifecycle.phase}] \u2014 ${duration}`
|
|
649
|
+
);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
if (lifecycle.kind === "engineer") {
|
|
653
|
+
const sandbox = attrs["visor.sandbox.selected"] || attrs["visor.sandbox.name"];
|
|
654
|
+
const sandboxSuffix = sandbox ? ` sandbox=${String(sandbox)}` : "";
|
|
655
|
+
lines.push(`${pad}engineer${childSuffix} [${lifecycle.phase}]${sandboxSuffix} \u2014 ${duration}`);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
lines.push(`${pad}${lifecycle.baseName}${childSuffix} [${lifecycle.phase}] \u2014 ${duration}`);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const toolName = attrs["tool.name"] || attrs["visor.tool.name"];
|
|
662
|
+
if (toolName) {
|
|
663
|
+
const toolInput = extractToolInput(String(toolName), attrs);
|
|
664
|
+
const toolResultLen = attrs["tool.result.length"] || attrs["tool.result.count"];
|
|
665
|
+
const tn = String(toolName);
|
|
666
|
+
const isSearchTool = tn === "search" || tn === "searchCode" || tn === "search_code";
|
|
667
|
+
const numLen = toolResultLen ? Number(toolResultLen) : -1;
|
|
668
|
+
const noResults = isSearchTool && numLen >= 0 && numLen < 500;
|
|
669
|
+
const resultSize = noResults ? " \u2192 no results" : toolResultLen ? ` \u2192 ${formatSize(numLen)}` : "";
|
|
670
|
+
const successMark = attrs["tool.success"] === false ? " \u2717" : "";
|
|
671
|
+
lines.push(`${pad}- ${tn}(${toolInput})${childSuffix}${resultSize}${successMark}`);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (name === "search.delegate.dedup") {
|
|
675
|
+
const query = attrs["dedup.query"] || "";
|
|
676
|
+
const action = attrs["dedup.action"] || "?";
|
|
677
|
+
const reason = attrs["dedup.reason"] || "";
|
|
678
|
+
const rewritten = attrs["dedup.rewritten"] || "";
|
|
679
|
+
const prevCount = attrs["dedup.previous_count"] || "0";
|
|
680
|
+
let detail = `${action}`;
|
|
681
|
+
if (rewritten) detail += ` \u2192 "${truncate(String(rewritten), 60)}"`;
|
|
682
|
+
if (reason) detail += ` (${truncate(String(reason), 80)})`;
|
|
683
|
+
lines.push(
|
|
684
|
+
`${pad}dedup("${truncate(String(query), 60)}") [${prevCount} prior]: ${detail} \u2014 ${duration}`
|
|
685
|
+
);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
if (name === "search.delegate") {
|
|
689
|
+
const query = attrs["search.query"] || "";
|
|
690
|
+
const rewritten = attrs["search.query.rewritten"] || "";
|
|
691
|
+
const output = attrs["search.delegate.output"] || "";
|
|
692
|
+
const outputLen = attrs["search.delegate.output_length"] || "";
|
|
693
|
+
let header = `search.delegate("${truncate(String(query), 80)}")${childSuffix}`;
|
|
694
|
+
if (rewritten) header += ` \u2192 rewritten: "${truncate(String(rewritten), 60)}"`;
|
|
695
|
+
header += ` \u2014 ${duration}`;
|
|
696
|
+
lines.push(`${pad}${header}:`);
|
|
697
|
+
if (output) {
|
|
698
|
+
try {
|
|
699
|
+
const parsed = JSON.parse(String(output));
|
|
700
|
+
if (parsed.confidence) {
|
|
701
|
+
let confLine = `confidence: ${parsed.confidence}`;
|
|
702
|
+
if (parsed.reason) confLine += ` \u2014 ${truncate(String(parsed.reason), 100)}`;
|
|
703
|
+
lines.push(`${pad} ${confLine}`);
|
|
704
|
+
}
|
|
705
|
+
if (parsed.searches && Array.isArray(parsed.searches) && parsed.searches.length > 0) {
|
|
706
|
+
lines.push(`${pad} searches (${parsed.searches.length}):`);
|
|
707
|
+
for (const s of parsed.searches) {
|
|
708
|
+
const outcome = s.had_results ? "\u2713" : "\u2717";
|
|
709
|
+
lines.push(
|
|
710
|
+
`${pad} ${outcome} "${truncate(String(s.query || ""), 60)}" in ${truncate(String(s.path || "."), 40)}`
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (parsed.groups && Array.isArray(parsed.groups) && parsed.groups.length > 0) {
|
|
715
|
+
lines.push(`${pad} groups (${parsed.groups.length}):`);
|
|
716
|
+
for (const g of parsed.groups) {
|
|
717
|
+
const fileCount = g.files?.length || 0;
|
|
718
|
+
lines.push(`${pad} - ${truncate(String(g.reason || ""), 80)} (${fileCount} files)`);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
} catch {
|
|
722
|
+
if (outputLen) lines.push(`${pad} output: ${outputLen} chars`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
for (const child of node.children) {
|
|
726
|
+
renderYamlNode(
|
|
727
|
+
child,
|
|
728
|
+
indent + 1,
|
|
729
|
+
lines,
|
|
730
|
+
dedup,
|
|
731
|
+
renderContext,
|
|
732
|
+
fallbackIntent,
|
|
733
|
+
fullOutput,
|
|
734
|
+
maxLen,
|
|
735
|
+
node.span
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
if (name === "ai.request") {
|
|
741
|
+
const model = attrs["ai.model"] || attrs["gen_ai.request.model"] || "?";
|
|
742
|
+
const tokensIn = attrs["ai.input_length"] || attrs["gen_ai.usage.input_tokens"] || "";
|
|
743
|
+
const tokensOut = attrs["gen_ai.usage.output_tokens"] || "";
|
|
744
|
+
const tokenParts = [];
|
|
745
|
+
if (tokensIn) tokenParts.push(`${tokensIn} in`);
|
|
746
|
+
if (tokensOut) tokenParts.push(`${tokensOut} out`);
|
|
747
|
+
const tokenStr = tokenParts.length > 0 ? ` \u2014 ${tokenParts.join(", ")}` : "";
|
|
748
|
+
const hasChildren2 = node.children.length > 0;
|
|
749
|
+
lines.push(
|
|
750
|
+
`${pad}ai: ${model}${childSuffix} \u2014 ${duration}${tokenStr}${hasChildren2 ? ":" : ""}`
|
|
751
|
+
);
|
|
752
|
+
const aiInput = String(attrs["ai.input"] || "");
|
|
753
|
+
let intent = extractAIIntent(aiInput, ml);
|
|
754
|
+
if (!intent && parentSpan) {
|
|
755
|
+
const promptPreview = String(
|
|
756
|
+
parentSpan.attributes["visor.provider.request.prompt.preview"] || ""
|
|
757
|
+
);
|
|
758
|
+
if (promptPreview) intent = extractAIIntent(promptPreview, ml);
|
|
759
|
+
if (!intent) {
|
|
760
|
+
const inputOutputs = String(parentSpan.attributes["visor.check.input.outputs"] || "");
|
|
761
|
+
if (inputOutputs) {
|
|
762
|
+
try {
|
|
763
|
+
const o = JSON.parse(inputOutputs);
|
|
764
|
+
const t = o["route-intent"]?.topic;
|
|
765
|
+
if (t) intent = truncate(String(t), ml);
|
|
766
|
+
} catch {
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
if (!intent && fallbackIntent && parentSpan?.name !== "search.delegate") {
|
|
772
|
+
intent = fallbackIntent;
|
|
773
|
+
}
|
|
774
|
+
if (intent) {
|
|
775
|
+
const intentRef = dedupeOrRegister(dedup, "intents", intent, displayName);
|
|
776
|
+
if (intentRef) {
|
|
777
|
+
lines.push(`${pad} intent: = ${intentRef}`);
|
|
778
|
+
} else {
|
|
779
|
+
lines.push(`${pad} intent: ${intent}`);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
for (const child of node.children) {
|
|
783
|
+
renderYamlNode(
|
|
784
|
+
child,
|
|
785
|
+
indent + 1,
|
|
786
|
+
lines,
|
|
787
|
+
dedup,
|
|
788
|
+
renderContext,
|
|
789
|
+
fallbackIntent,
|
|
790
|
+
fullOutput,
|
|
791
|
+
maxLen,
|
|
792
|
+
node.span
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
if (parentSpan) {
|
|
796
|
+
const checkOutput = String(parentSpan.attributes["visor.check.output"] || "");
|
|
797
|
+
if (checkOutput) {
|
|
798
|
+
renderYamlOutput(
|
|
799
|
+
checkOutput,
|
|
800
|
+
`${pad} `,
|
|
801
|
+
"output",
|
|
802
|
+
displayName,
|
|
803
|
+
dedup,
|
|
804
|
+
lines,
|
|
805
|
+
fullOutput,
|
|
806
|
+
ml
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
if (name === "visor.run") {
|
|
813
|
+
const source = attrs["visor.run.source"] || "";
|
|
814
|
+
const visorVersion = attrs["visor.version"] || "";
|
|
815
|
+
const probeVersion = attrs["probe.version"] || "";
|
|
816
|
+
const slackUser = attrs["slack.user_id"] || "";
|
|
817
|
+
lines.push(`${pad}visor.run:`);
|
|
818
|
+
lines.push(`${pad} trace_id: ${node.span.traceId}`);
|
|
819
|
+
if (visorVersion) lines.push(`${pad} visor: ${visorVersion}`);
|
|
820
|
+
if (probeVersion) lines.push(`${pad} probe: ${probeVersion}`);
|
|
821
|
+
if (source) lines.push(`${pad} source: ${source}`);
|
|
822
|
+
if (slackUser) lines.push(`${pad} slack_user: ${slackUser}`);
|
|
823
|
+
lines.push(`${pad} duration: ${duration}`);
|
|
824
|
+
for (const child of node.children) {
|
|
825
|
+
renderYamlNode(
|
|
826
|
+
child,
|
|
827
|
+
indent + 1,
|
|
828
|
+
lines,
|
|
829
|
+
dedup,
|
|
830
|
+
renderContext,
|
|
831
|
+
fallbackIntent,
|
|
832
|
+
fullOutput,
|
|
833
|
+
maxLen,
|
|
834
|
+
node.span
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const checkId = attrs["visor.check.id"];
|
|
840
|
+
const checkType = attrs["visor.check.type"];
|
|
841
|
+
const concreteCheckName = getConcreteCheckName({ name, attributes: attrs });
|
|
842
|
+
if (checkId || concreteCheckName) {
|
|
843
|
+
const cleanName = String(checkId || concreteCheckName || name).replace(/^visor\.check\./, "");
|
|
844
|
+
const errMark2 = node.span.status === "error" ? " \u2717" : "";
|
|
845
|
+
lines.push(`${pad}${cleanName}${childSuffix}:${errMark2}`);
|
|
846
|
+
if (checkType) lines.push(`${pad} type: ${checkType}`);
|
|
847
|
+
lines.push(`${pad} duration: ${duration}`);
|
|
848
|
+
const inputContext = String(attrs["visor.check.input.context"] || "");
|
|
849
|
+
const inputOutputs = String(attrs["visor.check.input.outputs"] || "");
|
|
850
|
+
const question = extractQuestionFromContext(inputContext, inputOutputs);
|
|
851
|
+
if (question || inputOutputs && inputOutputs !== "{}") {
|
|
852
|
+
renderYamlInput(inputOutputs, `${pad} `, lines, fullOutput, ml, question);
|
|
853
|
+
}
|
|
854
|
+
for (const child of node.children) {
|
|
855
|
+
renderYamlNode(
|
|
856
|
+
child,
|
|
857
|
+
indent + 1,
|
|
858
|
+
lines,
|
|
859
|
+
dedup,
|
|
860
|
+
renderContext,
|
|
861
|
+
fallbackIntent,
|
|
862
|
+
fullOutput,
|
|
863
|
+
maxLen,
|
|
864
|
+
node.span
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
const hasDirectAiChild = node.children.some((c) => c.span.name === "ai.request");
|
|
868
|
+
if (!hasDirectAiChild) {
|
|
869
|
+
const output = String(attrs["visor.check.output"] || "");
|
|
870
|
+
if (output) {
|
|
871
|
+
renderYamlOutput(output, `${pad} `, "output", cleanName, dedup, lines, fullOutput, ml);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
const errMark = node.span.status === "error" ? " \u2717" : "";
|
|
877
|
+
const hasChildren = node.children.length > 0;
|
|
878
|
+
lines.push(`${pad}${name}${childSuffix} \u2014 ${duration}${errMark}${hasChildren ? ":" : ""}`);
|
|
879
|
+
for (const child of node.children) {
|
|
880
|
+
renderYamlNode(
|
|
881
|
+
child,
|
|
882
|
+
indent + 1,
|
|
883
|
+
lines,
|
|
884
|
+
dedup,
|
|
885
|
+
renderContext,
|
|
886
|
+
fallbackIntent,
|
|
887
|
+
fullOutput,
|
|
888
|
+
maxLen,
|
|
889
|
+
node.span
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function renderYamlOutput(rawOutput, pad, label, spanName, dedup, lines, fullOutput, maxLen) {
|
|
894
|
+
const ml = maxLen ?? 120;
|
|
895
|
+
let obj;
|
|
896
|
+
try {
|
|
897
|
+
obj = JSON.parse(rawOutput);
|
|
898
|
+
} catch {
|
|
899
|
+
obj = parseTruncatedJson(rawOutput);
|
|
900
|
+
}
|
|
901
|
+
if (obj === null || obj === void 0 || typeof obj !== "object") return;
|
|
902
|
+
if (typeof obj === "object" && !Array.isArray(obj)) {
|
|
903
|
+
const keys = Object.keys(obj);
|
|
904
|
+
if (keys.length === 1 && typeof obj[keys[0]] === "object" && obj[keys[0]] !== null) {
|
|
905
|
+
obj = obj[keys[0]];
|
|
906
|
+
}
|
|
907
|
+
const objKeys = Object.keys(obj);
|
|
908
|
+
if (objKeys.length === 1 && objKeys[0] === "text" && typeof obj.text === "string") {
|
|
909
|
+
const text = obj.text.replace(/\*\*/g, "").replace(/`/g, "").trim();
|
|
910
|
+
const flat = text.replace(/\n/g, " ");
|
|
911
|
+
const preview2 = fullOutput ? flat : truncate(flat, ml);
|
|
912
|
+
const ref2 = dedupeOrRegister(dedup, "outputs", truncate(flat, 100), spanName);
|
|
913
|
+
if (ref2) {
|
|
914
|
+
lines.push(`${pad}${label}: = ${ref2}`);
|
|
915
|
+
} else {
|
|
916
|
+
lines.push(`${pad}${label}: ${preview2}`);
|
|
917
|
+
}
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
const preview = formatJsonPreview(obj, 200);
|
|
922
|
+
if (!preview) return;
|
|
923
|
+
const ref = dedupeOrRegister(dedup, "outputs", preview, spanName);
|
|
924
|
+
if (ref) {
|
|
925
|
+
lines.push(`${pad}${label}: = ${ref}`);
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
renderYamlValue(obj, pad, label, lines, fullOutput, ml);
|
|
929
|
+
}
|
|
930
|
+
function renderYamlValue(val, pad, key, lines, fullOutput, maxLen, depth) {
|
|
931
|
+
const ml = maxLen ?? 120;
|
|
932
|
+
const d = depth ?? 0;
|
|
933
|
+
if (val === null || val === void 0) return;
|
|
934
|
+
if (typeof val === "boolean" || typeof val === "number") {
|
|
935
|
+
lines.push(`${pad}${key}: ${val}`);
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
if (typeof val === "string") {
|
|
939
|
+
if (val.startsWith("{") || val.startsWith("[")) return;
|
|
940
|
+
const clean = val.replace(/\*\*/g, "").replace(/`/g, "").trim();
|
|
941
|
+
if (fullOutput && clean.length > 100 && clean.includes("\n")) {
|
|
942
|
+
lines.push(`${pad}${key}: |`);
|
|
943
|
+
for (const line of clean.split("\n").slice(0, fullOutput ? 500 : 5)) {
|
|
944
|
+
lines.push(`${pad} ${line}`);
|
|
945
|
+
}
|
|
946
|
+
} else {
|
|
947
|
+
const flat = clean.replace(/\n/g, " ");
|
|
948
|
+
const truncVal = fullOutput ? flat : truncate(flat, ml);
|
|
949
|
+
lines.push(`${pad}${key}: ${truncVal}`);
|
|
950
|
+
}
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
if (Array.isArray(val)) {
|
|
954
|
+
if (val.length === 0) {
|
|
955
|
+
lines.push(`${pad}${key}: []`);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
if (val.every((v) => typeof v === "string") && val.join(", ").length < ml) {
|
|
959
|
+
lines.push(`${pad}${key}: [${val.join(", ")}]`);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
const maxItems = fullOutput ? 20 : 3;
|
|
963
|
+
lines.push(`${pad}${key}:`);
|
|
964
|
+
for (let i = 0; i < Math.min(val.length, maxItems); i++) {
|
|
965
|
+
const item = val[i];
|
|
966
|
+
if (typeof item === "object" && item !== null) {
|
|
967
|
+
const entries = Object.entries(item).filter(([k]) => k !== "raw" && k !== "tags");
|
|
968
|
+
if (entries.length > 0) {
|
|
969
|
+
const [firstKey, firstVal] = entries[0];
|
|
970
|
+
if (firstVal === null || firstVal === void 0 || typeof firstVal !== "object") {
|
|
971
|
+
const sv = typeof firstVal === "string" ? fullOutput ? firstVal.split("\n")[0] : truncate(firstVal.split("\n")[0], ml) : String(firstVal ?? "");
|
|
972
|
+
lines.push(`${pad} - ${firstKey}: ${sv}`);
|
|
973
|
+
} else {
|
|
974
|
+
lines.push(`${pad} - ${firstKey}:`);
|
|
975
|
+
for (const [ck, cv] of Object.entries(firstVal)) {
|
|
976
|
+
if (ck === "raw" || ck === "skills" || ck === "tags") continue;
|
|
977
|
+
renderYamlValue(cv, `${pad} `, ck, lines, fullOutput, ml, d + 2);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
for (let j = 1; j < entries.length; j++) {
|
|
981
|
+
const [k, v] = entries[j];
|
|
982
|
+
renderYamlValue(v, `${pad} `, k, lines, fullOutput, ml, d + 1);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
} else {
|
|
986
|
+
lines.push(`${pad} - ${truncate(String(item), ml)}`);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
if (val.length > maxItems) {
|
|
990
|
+
lines.push(`${pad} # ... ${val.length - maxItems} more`);
|
|
991
|
+
}
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
if (typeof val === "object") {
|
|
995
|
+
if (d > 3) {
|
|
996
|
+
const keys = Object.keys(val);
|
|
997
|
+
lines.push(`${pad}${key}: {${keys.slice(0, 4).join(", ")}${keys.length > 4 ? ", ..." : ""}}`);
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
lines.push(`${pad}${key}:`);
|
|
1001
|
+
for (const [k, v] of Object.entries(val)) {
|
|
1002
|
+
if (k === "raw" || k === "tags") continue;
|
|
1003
|
+
renderYamlValue(v, `${pad} `, k, lines, fullOutput, ml, d + 1);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
function extractQuestionFromContext(contextStr, inputOutputsStr) {
|
|
1008
|
+
if (!contextStr) return void 0;
|
|
1009
|
+
try {
|
|
1010
|
+
const ctx = JSON.parse(contextStr);
|
|
1011
|
+
const outputs = ctx.outputs || {};
|
|
1012
|
+
const routeIntent = outputs["route-intent"];
|
|
1013
|
+
if (routeIntent) {
|
|
1014
|
+
const topic = routeIntent.topic || routeIntent.intent || routeIntent.question;
|
|
1015
|
+
if (topic && typeof topic === "string") return topic;
|
|
1016
|
+
if (typeof routeIntent === "string") return routeIntent;
|
|
1017
|
+
}
|
|
1018
|
+
const args = ctx.args || {};
|
|
1019
|
+
if (args.topic && typeof args.topic === "string") return args.topic;
|
|
1020
|
+
if (args.question && typeof args.question === "string") return args.question;
|
|
1021
|
+
if (args.intent && typeof args.intent === "string") return args.intent;
|
|
1022
|
+
for (const key of Object.keys(outputs)) {
|
|
1023
|
+
const val = outputs[key];
|
|
1024
|
+
if (typeof val === "object" && val !== null) {
|
|
1025
|
+
if (val.topic && typeof val.topic === "string") {
|
|
1026
|
+
try {
|
|
1027
|
+
const depOutputs = JSON.parse(inputOutputsStr);
|
|
1028
|
+
if (depOutputs[key]) continue;
|
|
1029
|
+
} catch {
|
|
1030
|
+
}
|
|
1031
|
+
return val.topic;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
} catch {
|
|
1036
|
+
const topicMatch = contextStr.match(/"topic"\s*:\s*"([^"]+)"/);
|
|
1037
|
+
if (topicMatch) return topicMatch[1];
|
|
1038
|
+
}
|
|
1039
|
+
return void 0;
|
|
1040
|
+
}
|
|
1041
|
+
function renderYamlInput(inputOutputsStr, pad, lines, fullOutput, maxLen, question) {
|
|
1042
|
+
const ml = maxLen ?? 120;
|
|
1043
|
+
if (question) {
|
|
1044
|
+
lines.push(`${pad}input: ${truncate(question, fullOutput ? 1e5 : ml)}`);
|
|
1045
|
+
}
|
|
1046
|
+
try {
|
|
1047
|
+
const inputs = JSON.parse(inputOutputsStr);
|
|
1048
|
+
if (typeof inputs !== "object" || inputs === null) return;
|
|
1049
|
+
const keys = Object.keys(inputs);
|
|
1050
|
+
if (keys.length === 0) return;
|
|
1051
|
+
if (!question) lines.push(`${pad}input:`);
|
|
1052
|
+
for (const key of keys) {
|
|
1053
|
+
const val = inputs[key];
|
|
1054
|
+
if (typeof val === "object" && val !== null) {
|
|
1055
|
+
renderYamlValue(val, `${pad} `, key, lines, fullOutput, ml, 0);
|
|
1056
|
+
} else {
|
|
1057
|
+
lines.push(`${pad} ${key}: ${truncate(String(val), ml)}`);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
} catch {
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
function renderNode(node, prefix, isLast, depth, maxDepth, lines, parentSpan, fallbackIntent, fullOutput, dedup) {
|
|
1064
|
+
if (depth > maxDepth) return;
|
|
1065
|
+
const hasChildren = node.children.length > 0;
|
|
1066
|
+
const connector = depth === 0 ? "" : isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
1067
|
+
const { line: formatted, output: deferredOutput } = formatSpanLine(
|
|
1068
|
+
node.span,
|
|
1069
|
+
parentSpan,
|
|
1070
|
+
fallbackIntent,
|
|
1071
|
+
fullOutput,
|
|
1072
|
+
dedup,
|
|
1073
|
+
hasChildren
|
|
1074
|
+
);
|
|
1075
|
+
if (formatted) {
|
|
1076
|
+
lines.push(`${prefix}${connector}${formatted}`);
|
|
1077
|
+
}
|
|
1078
|
+
const childPrefix = depth === 0 ? "" : formatted ? prefix + (isLast ? " " : "\u2502 ") : prefix;
|
|
1079
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
1080
|
+
const isChildLast = i === node.children.length - 1 && !deferredOutput;
|
|
1081
|
+
renderNode(
|
|
1082
|
+
node.children[i],
|
|
1083
|
+
childPrefix,
|
|
1084
|
+
isChildLast,
|
|
1085
|
+
depth + (formatted ? 1 : 0),
|
|
1086
|
+
maxDepth,
|
|
1087
|
+
lines,
|
|
1088
|
+
node.span,
|
|
1089
|
+
fallbackIntent,
|
|
1090
|
+
fullOutput,
|
|
1091
|
+
dedup
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
if (deferredOutput) {
|
|
1095
|
+
lines.push(`${childPrefix}\u2514\u2500\u2192 ${deferredOutput}`);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
function formatSpanLine(span, parentSpan, fallbackIntent, fullOutput, dedup, hasChildren) {
|
|
1099
|
+
const duration = formatDurationMs(span.durationMs);
|
|
1100
|
+
const attrs = span.attributes;
|
|
1101
|
+
const name = span.name;
|
|
1102
|
+
const previewLimit = fullOutput ? 1e4 : 120;
|
|
1103
|
+
const displayName = String(attrs["visor.check.id"] || name).replace(/^visor\.check\./, "");
|
|
1104
|
+
const dedupeOutputStr = (rawOutput, label) => {
|
|
1105
|
+
if (!rawOutput) return "";
|
|
1106
|
+
if (!dedup) return rawOutput;
|
|
1107
|
+
const ref = dedupeOrRegister(dedup, "outputs", rawOutput, label);
|
|
1108
|
+
if (ref) return `(= ${ref})`;
|
|
1109
|
+
return rawOutput;
|
|
1110
|
+
};
|
|
1111
|
+
const dedupeIntentStr = (rawIntent, label) => {
|
|
1112
|
+
if (!rawIntent || !dedup) return rawIntent ? ` \u{1F4AC} ${rawIntent}` : "";
|
|
1113
|
+
const ref = dedupeOrRegister(dedup, "intents", rawIntent, label);
|
|
1114
|
+
if (ref) return ` \u{1F4AC} (= ${ref})`;
|
|
1115
|
+
return ` \u{1F4AC} ${rawIntent}`;
|
|
1116
|
+
};
|
|
1117
|
+
const makeResult = (line, outputPreview) => {
|
|
1118
|
+
if (!outputPreview) return { line };
|
|
1119
|
+
if (hasChildren) {
|
|
1120
|
+
return { line, output: outputPreview };
|
|
1121
|
+
}
|
|
1122
|
+
return { line: `${line} \u2192 ${outputPreview}` };
|
|
1123
|
+
};
|
|
1124
|
+
const toolName = attrs["tool.name"] || attrs["visor.tool.name"];
|
|
1125
|
+
if (toolName) {
|
|
1126
|
+
const toolInput = extractToolInput(String(toolName), attrs);
|
|
1127
|
+
const toolResultLen = attrs["tool.result.length"] || attrs["tool.result.count"];
|
|
1128
|
+
const toolSuccess = attrs["tool.success"];
|
|
1129
|
+
const tn = String(toolName);
|
|
1130
|
+
const isSearchTool = tn === "search" || tn === "searchCode" || tn === "search_code";
|
|
1131
|
+
const numLen = toolResultLen ? Number(toolResultLen) : -1;
|
|
1132
|
+
const noResults = isSearchTool && numLen >= 0 && numLen < 500;
|
|
1133
|
+
const resultSize = noResults ? "no results" : toolResultLen ? formatSize(numLen) : "";
|
|
1134
|
+
const durStr = Number(attrs["tool.duration_ms"]) > 0 ? ` (${formatDurationMs(Number(attrs["tool.duration_ms"]))})` : "";
|
|
1135
|
+
let successMark = toolSuccess === false ? " \u2717" : "";
|
|
1136
|
+
if (tn === "bash") {
|
|
1137
|
+
const toolResult = String(attrs["tool.result"] || "");
|
|
1138
|
+
const exitMatch = toolResult.match(/Exit Code: (\S+)/);
|
|
1139
|
+
const sigMatch = toolResult.match(/Signal: (\S+)/);
|
|
1140
|
+
if (sigMatch && sigMatch[1] !== "null") {
|
|
1141
|
+
successMark = ` [${sigMatch[1]}]`;
|
|
1142
|
+
} else if (exitMatch && exitMatch[1] !== "0" && exitMatch[1] !== "null") {
|
|
1143
|
+
successMark = ` [exit ${exitMatch[1]}]`;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return {
|
|
1147
|
+
line: `${toolName}(${toolInput})${durStr}${resultSize ? ` \u2192 ${resultSize}` : ""}${successMark}`
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
if (name === "search.delegate.dedup") {
|
|
1151
|
+
const query = attrs["dedup.query"] || "";
|
|
1152
|
+
const action = attrs["dedup.action"] || "?";
|
|
1153
|
+
const reason = attrs["dedup.reason"] || "";
|
|
1154
|
+
const rewritten = attrs["dedup.rewritten"] || "";
|
|
1155
|
+
let detail = `${action}`;
|
|
1156
|
+
if (rewritten) detail += ` \u2192 "${truncate(String(rewritten), 50)}"`;
|
|
1157
|
+
if (reason) detail += ` \u2014 ${truncate(String(reason), 60)}`;
|
|
1158
|
+
return { line: `dedup("${truncate(String(query), 50)}") ${detail} (${duration})` };
|
|
1159
|
+
}
|
|
1160
|
+
if (name === "search.delegate") {
|
|
1161
|
+
const query = attrs["search.query"] || "";
|
|
1162
|
+
const rewritten = attrs["search.query.rewritten"] || "";
|
|
1163
|
+
const output = attrs["search.delegate.output"] || "";
|
|
1164
|
+
let suffix = "";
|
|
1165
|
+
try {
|
|
1166
|
+
const parsed = JSON.parse(String(output));
|
|
1167
|
+
const parts = [];
|
|
1168
|
+
if (parsed.confidence) parts.push(parsed.confidence);
|
|
1169
|
+
if (parsed.groups?.length) parts.push(`${parsed.groups.length} groups`);
|
|
1170
|
+
if (parsed.searches?.length) parts.push(`${parsed.searches.length} searches`);
|
|
1171
|
+
if (parts.length > 0) suffix = ` \u2192 ${parts.join(", ")}`;
|
|
1172
|
+
} catch {
|
|
1173
|
+
}
|
|
1174
|
+
const rewriteStr = rewritten ? ` \u2192 "${truncate(String(rewritten), 40)}"` : "";
|
|
1175
|
+
return {
|
|
1176
|
+
line: `search.delegate("${truncate(String(query), 60)}"${rewriteStr}) (${duration})${suffix}`
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
if (name === "ai.request") {
|
|
1180
|
+
const model = attrs["ai.model"] || attrs["gen_ai.request.model"] || "?";
|
|
1181
|
+
const tokensIn = attrs["ai.input_length"] || attrs["gen_ai.usage.input_tokens"] || "";
|
|
1182
|
+
const tokensOut = attrs["gen_ai.usage.output_tokens"] || "";
|
|
1183
|
+
const tokenParts = [];
|
|
1184
|
+
if (tokensIn) tokenParts.push(`${tokensIn} in`);
|
|
1185
|
+
if (tokensOut) tokenParts.push(`${tokensOut} out`);
|
|
1186
|
+
const tokenStr = tokenParts.length > 0 ? ` [${tokenParts.join(", ")}]` : "";
|
|
1187
|
+
const aiInput = String(attrs["ai.input"] || "");
|
|
1188
|
+
let intent = extractAIIntent(aiInput, previewLimit);
|
|
1189
|
+
if (!intent && parentSpan) {
|
|
1190
|
+
const promptPreview = String(
|
|
1191
|
+
parentSpan.attributes["visor.provider.request.prompt.preview"] || ""
|
|
1192
|
+
);
|
|
1193
|
+
if (promptPreview) {
|
|
1194
|
+
intent = extractAIIntent(promptPreview, previewLimit);
|
|
1195
|
+
}
|
|
1196
|
+
if (!intent) {
|
|
1197
|
+
const inputOutputs = String(parentSpan.attributes["visor.check.input.outputs"] || "");
|
|
1198
|
+
if (inputOutputs) {
|
|
1199
|
+
try {
|
|
1200
|
+
const outputs = JSON.parse(inputOutputs);
|
|
1201
|
+
const topic = outputs["route-intent"]?.topic;
|
|
1202
|
+
if (topic) intent = truncate(String(topic), 150);
|
|
1203
|
+
} catch {
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
if (!intent && fallbackIntent && parentSpan?.name !== "search.delegate") {
|
|
1209
|
+
intent = fallbackIntent;
|
|
1210
|
+
}
|
|
1211
|
+
const intentStr = intent ? dedupeIntentStr(intent, displayName) : "";
|
|
1212
|
+
let outputPreview = "";
|
|
1213
|
+
if (parentSpan) {
|
|
1214
|
+
const checkOutput = String(parentSpan.attributes["visor.check.output"] || "");
|
|
1215
|
+
if (checkOutput) {
|
|
1216
|
+
const preview = extractOutputPreview(checkOutput, previewLimit);
|
|
1217
|
+
if (preview) outputPreview = dedupeOutputStr(preview, displayName);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
const mainLine = `ai ${model} (${duration})${tokenStr}${intentStr}`;
|
|
1221
|
+
return makeResult(mainLine, outputPreview);
|
|
1222
|
+
}
|
|
1223
|
+
const checkId = attrs["visor.check.id"];
|
|
1224
|
+
const checkType = attrs["visor.check.type"];
|
|
1225
|
+
if (checkId || name.startsWith("visor.check.")) {
|
|
1226
|
+
const cleanName = String(checkId || name).replace(/^visor\.check\./, "");
|
|
1227
|
+
const typeStr = checkType ? ` [${checkType}]` : "";
|
|
1228
|
+
const inputOutputs = String(attrs["visor.check.input.outputs"] || "");
|
|
1229
|
+
let inputStr = "";
|
|
1230
|
+
if (inputOutputs && inputOutputs !== "{}") {
|
|
1231
|
+
inputStr = " " + formatInputPreview(inputOutputs, previewLimit);
|
|
1232
|
+
}
|
|
1233
|
+
const output = String(attrs["visor.check.output"] || "");
|
|
1234
|
+
let outputPreview = "";
|
|
1235
|
+
if (output) {
|
|
1236
|
+
const preview = extractOutputPreview(output, previewLimit);
|
|
1237
|
+
if (preview) outputPreview = dedupeOutputStr(preview, cleanName);
|
|
1238
|
+
}
|
|
1239
|
+
const mainLine = `${cleanName}${typeStr} (${duration})${inputStr}`;
|
|
1240
|
+
return makeResult(mainLine, outputPreview);
|
|
1241
|
+
}
|
|
1242
|
+
if (name === "visor.run") {
|
|
1243
|
+
const source = attrs["visor.run.source"] || "";
|
|
1244
|
+
const sourceStr = source ? ` (${source})` : "";
|
|
1245
|
+
return { line: `visor.run${sourceStr} (${duration})` };
|
|
1246
|
+
}
|
|
1247
|
+
if (name === "probe.event.negotiated_timeout.observer" || name === "negotiated_timeout.observer") {
|
|
1248
|
+
let detail = "";
|
|
1249
|
+
const attrDecision = attrs["observer.decision"] || attrs["decision_reason"];
|
|
1250
|
+
if (attrDecision) {
|
|
1251
|
+
const reason = attrs["observer.reason"] || attrs["decision_reason"] || "";
|
|
1252
|
+
if (String(attrDecision) === "extended" || attrs["granted_ms"]) {
|
|
1253
|
+
const grantedMin = attrs["observer.granted_min"] || attrs["granted_min"] || (attrs["granted_ms"] ? Math.round(Number(attrs["granted_ms"]) / 6e4) : "?");
|
|
1254
|
+
detail = `extended +${grantedMin}min`;
|
|
1255
|
+
if (reason) detail += ` (${truncate(String(reason), 60)})`;
|
|
1256
|
+
const used = attrs["observer.extensions_used"] || attrs["extensions_used"];
|
|
1257
|
+
const max = attrs["observer.max_requests"] || attrs["max_requests"];
|
|
1258
|
+
if (used) detail += ` [${used}/${max || "?"} used]`;
|
|
1259
|
+
} else if (String(attrDecision) === "exhausted") {
|
|
1260
|
+
detail = "budget exhausted";
|
|
1261
|
+
} else {
|
|
1262
|
+
detail = `declined`;
|
|
1263
|
+
if (reason) detail += `: ${truncate(String(reason), 60)}`;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
if (!detail && span.events.length > 0) {
|
|
1267
|
+
for (const evt of span.events) {
|
|
1268
|
+
const evtName = evt.name || "";
|
|
1269
|
+
const ea = evt.attributes;
|
|
1270
|
+
if (evtName.includes("observer_extended")) {
|
|
1271
|
+
const grantedMin = ea["granted_min"] || (ea["granted_ms"] ? Math.round(Number(ea["granted_ms"]) / 6e4) : "?");
|
|
1272
|
+
detail = `extended +${grantedMin}min`;
|
|
1273
|
+
if (ea["decision_reason"]) detail += ` (${truncate(String(ea["decision_reason"]), 60)})`;
|
|
1274
|
+
if (ea["extensions_used"])
|
|
1275
|
+
detail += ` [${ea["extensions_used"]}/${ea["max_requests"] || "?"} used]`;
|
|
1276
|
+
break;
|
|
1277
|
+
}
|
|
1278
|
+
if (evtName.includes("observer_declined")) {
|
|
1279
|
+
detail = "declined";
|
|
1280
|
+
if (ea["decision_reason"]) detail += `: ${truncate(String(ea["decision_reason"]), 60)}`;
|
|
1281
|
+
break;
|
|
1282
|
+
}
|
|
1283
|
+
if (evtName.includes("observer_exhausted")) {
|
|
1284
|
+
const used = ea["extensions_used"] || "?";
|
|
1285
|
+
const max = ea["max_requests"] || "?";
|
|
1286
|
+
detail = `budget exhausted [${used}/${max} extensions]`;
|
|
1287
|
+
break;
|
|
1288
|
+
}
|
|
1289
|
+
if (evtName.includes("observer_invoked") && !detail) {
|
|
1290
|
+
const elapsed = ea["elapsed_min"] || "?";
|
|
1291
|
+
const tools = ea["active_tools_count"] || 0;
|
|
1292
|
+
detail = `${elapsed}min elapsed, ${tools} active tools`;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
if (!detail) {
|
|
1297
|
+
const elapsed = attrs["elapsed_min"];
|
|
1298
|
+
const activeTools = attrs["active_tools_count"] || attrs["active_tools"];
|
|
1299
|
+
if (elapsed) detail += `${elapsed}min elapsed`;
|
|
1300
|
+
if (activeTools)
|
|
1301
|
+
detail += detail ? `, ${activeTools} active tools` : `${activeTools} active tools`;
|
|
1302
|
+
}
|
|
1303
|
+
const label = detail ? `timeout.observer: ${detail}` : "timeout.observer";
|
|
1304
|
+
return { line: `${label} (${duration})` };
|
|
1305
|
+
}
|
|
1306
|
+
if (name.includes("negotiated_timeout.observer_")) {
|
|
1307
|
+
const suffix = name.replace(/.*negotiated_timeout\.observer_/, "");
|
|
1308
|
+
const reason = attrs["decision_reason"] || "";
|
|
1309
|
+
if (suffix === "extended") {
|
|
1310
|
+
const grantedMin = attrs["granted_min"] || (attrs["granted_ms"] ? Math.round(Number(attrs["granted_ms"]) / 6e4) : "?");
|
|
1311
|
+
const used = attrs["extensions_used"] || "?";
|
|
1312
|
+
const max = attrs["max_requests"] || "?";
|
|
1313
|
+
const reasonStr = reason ? ` (${truncate(String(reason), 60)})` : "";
|
|
1314
|
+
return { line: `timeout.extended: +${grantedMin}min${reasonStr} [${used}/${max} used]` };
|
|
1315
|
+
}
|
|
1316
|
+
if (suffix === "declined") {
|
|
1317
|
+
const reasonStr = reason ? `: ${truncate(String(reason), 60)}` : "";
|
|
1318
|
+
return { line: `timeout.declined${reasonStr}` };
|
|
1319
|
+
}
|
|
1320
|
+
if (suffix === "exhausted") {
|
|
1321
|
+
const used = attrs["extensions_used"] || "?";
|
|
1322
|
+
const max = attrs["max_requests"] || "?";
|
|
1323
|
+
return { line: `timeout.exhausted [${used}/${max} extensions, budget depleted]` };
|
|
1324
|
+
}
|
|
1325
|
+
if (suffix === "invoked") {
|
|
1326
|
+
const elapsed = attrs["elapsed_min"] || "?";
|
|
1327
|
+
const tools = attrs["active_tools_count"] || 0;
|
|
1328
|
+
return { line: `timeout.observer invoked (${elapsed}min elapsed, ${tools} active tools)` };
|
|
1329
|
+
}
|
|
1330
|
+
return { line: `timeout.${suffix} (${duration})` };
|
|
1331
|
+
}
|
|
1332
|
+
if (name.includes("negotiated_timeout.abort_summary")) {
|
|
1333
|
+
const summaryLen = attrs["summary_length"] || attrs["summary.length"];
|
|
1334
|
+
const lenStr = summaryLen ? ` \u2192 ${formatSize(Number(summaryLen))}` : "";
|
|
1335
|
+
return { line: `timeout.abort_summary (${duration})${lenStr}` };
|
|
1336
|
+
}
|
|
1337
|
+
if (name.includes("graceful_stop.initiated") || name.includes("graceful_stop.invoked")) {
|
|
1338
|
+
const reason = attrs["graceful_stop.reason"] || attrs["reason"] || "";
|
|
1339
|
+
const reasonStr = reason ? `: ${truncate(String(reason), 80)}` : "";
|
|
1340
|
+
return { line: `graceful_stop${reasonStr} (${duration})` };
|
|
1341
|
+
}
|
|
1342
|
+
return { line: `${name} (${duration})${span.status === "error" ? " \u2717" : ""}` };
|
|
1343
|
+
}
|
|
1344
|
+
function parseWorkspacePath(fullPath) {
|
|
1345
|
+
const wsMatch = fullPath.match(/\/visor-workspaces\/[^/]+\/([^/]+)(?:\/(.+))?/);
|
|
1346
|
+
if (wsMatch) {
|
|
1347
|
+
return { repo: wsMatch[1], filePath: wsMatch[2] };
|
|
1348
|
+
}
|
|
1349
|
+
const wtMatch = fullPath.match(/\.visor\/worktrees\/worktrees\/[^/]+\/(.+)/);
|
|
1350
|
+
if (wtMatch) {
|
|
1351
|
+
const segs = wtMatch[1].split("/");
|
|
1352
|
+
return { repo: segs[0], filePath: segs.length > 1 ? segs.slice(1).join("/") : void 0 };
|
|
1353
|
+
}
|
|
1354
|
+
return null;
|
|
1355
|
+
}
|
|
1356
|
+
function extractToolInput(toolName, attrs) {
|
|
1357
|
+
const result = String(attrs["tool.result"] || "");
|
|
1358
|
+
const explicitInput = String(attrs["tool.input"] || "");
|
|
1359
|
+
if (explicitInput) return truncate(explicitInput, 80);
|
|
1360
|
+
switch (toolName) {
|
|
1361
|
+
case "search": {
|
|
1362
|
+
const patMatch = result.match(/Pattern: (.+)/);
|
|
1363
|
+
const pathMatch = result.match(/Path: (\S+)/);
|
|
1364
|
+
const pattern = patMatch ? patMatch[1].trim() : "";
|
|
1365
|
+
const workspace = pathMatch ? parseWorkspacePath(pathMatch[1]) : null;
|
|
1366
|
+
const parts = [];
|
|
1367
|
+
if (pattern) parts.push(`"${truncate(pattern, 50)}"`);
|
|
1368
|
+
if (workspace?.repo) parts.push(workspace.repo);
|
|
1369
|
+
return parts.join(", ");
|
|
1370
|
+
}
|
|
1371
|
+
case "extract": {
|
|
1372
|
+
const fileMatch = result.match(/Files to extract:\n\s*(\S+)/);
|
|
1373
|
+
if (fileMatch) {
|
|
1374
|
+
const fullPath = fileMatch[1];
|
|
1375
|
+
const workspace = parseWorkspacePath(fullPath);
|
|
1376
|
+
if (workspace) {
|
|
1377
|
+
const parts = [];
|
|
1378
|
+
parts.push(workspace.filePath || workspace.repo || fullPath.split("/").pop() || "");
|
|
1379
|
+
if (workspace.repo) parts.push(workspace.repo);
|
|
1380
|
+
return parts.join(", ");
|
|
1381
|
+
}
|
|
1382
|
+
const segs = fullPath.split("/");
|
|
1383
|
+
return segs.length > 2 ? segs.slice(-2).join("/") : segs[segs.length - 1];
|
|
1384
|
+
}
|
|
1385
|
+
return "";
|
|
1386
|
+
}
|
|
1387
|
+
case "bash": {
|
|
1388
|
+
const cmdMatch = result.match(/^Command: (.+)/);
|
|
1389
|
+
if (cmdMatch) {
|
|
1390
|
+
let cmd = cmdMatch[1].trim();
|
|
1391
|
+
const pipes = cmd.split(/\s*\|\s*/);
|
|
1392
|
+
if (pipes.length > 2) {
|
|
1393
|
+
cmd = `${pipes[0]} | ... (${pipes.length} stages)`;
|
|
1394
|
+
}
|
|
1395
|
+
return truncate(cmd, 80);
|
|
1396
|
+
}
|
|
1397
|
+
const deniedMatch = result.match(/^Permission denied: Component "([^"]+)"/);
|
|
1398
|
+
if (deniedMatch) {
|
|
1399
|
+
return truncate(deniedMatch[1], 60) + " [denied]";
|
|
1400
|
+
}
|
|
1401
|
+
return "";
|
|
1402
|
+
}
|
|
1403
|
+
case "listFiles": {
|
|
1404
|
+
const pathMatch = result.match(/^(\S+):/);
|
|
1405
|
+
if (pathMatch) {
|
|
1406
|
+
const parts = pathMatch[1].split("/");
|
|
1407
|
+
return parts[parts.length - 1] || "";
|
|
1408
|
+
}
|
|
1409
|
+
return "";
|
|
1410
|
+
}
|
|
1411
|
+
default:
|
|
1412
|
+
return truncate(explicitInput, 60);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
function extractAIIntent(input, maxLen = 150) {
|
|
1416
|
+
if (!input || input.length < 20) return "";
|
|
1417
|
+
const qMatch = input.match(/<question>([\s\S]*?)<\/question>/);
|
|
1418
|
+
if (qMatch) return truncate(qMatch[1].trim(), maxLen);
|
|
1419
|
+
const crMatch = input.match(/## Current Request\s*\n(?:User: )?(.+)/);
|
|
1420
|
+
if (crMatch) return truncate(crMatch[1].trim(), maxLen);
|
|
1421
|
+
const userMatch = input.match(/(?:^|\n)User: (.+)/);
|
|
1422
|
+
if (userMatch) return truncate(userMatch[1].trim(), maxLen);
|
|
1423
|
+
const pmMatch = input.match(/Primary message[^:]*:\s*\n(.+)/);
|
|
1424
|
+
if (pmMatch) return truncate(pmMatch[1].trim(), maxLen);
|
|
1425
|
+
return "";
|
|
1426
|
+
}
|
|
1427
|
+
function formatJsonPreview(obj, maxLen) {
|
|
1428
|
+
if (obj === null || obj === void 0) return "";
|
|
1429
|
+
if (typeof obj !== "object") return truncate(String(obj), maxLen);
|
|
1430
|
+
if (Array.isArray(obj)) {
|
|
1431
|
+
if (obj.length === 0) return "[]";
|
|
1432
|
+
const first = typeof obj[0] === "object" && obj[0] !== null ? obj[0].project_id || obj[0].id || obj[0].name || Object.keys(obj[0])[0] || "..." : String(obj[0]);
|
|
1433
|
+
return `[${obj.length}] ${truncate(String(first), 30)}${obj.length > 1 ? ", ..." : ""}`;
|
|
1434
|
+
}
|
|
1435
|
+
const parts = [];
|
|
1436
|
+
let len = 2;
|
|
1437
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
1438
|
+
if (key === "raw" || key === "skills" || key === "tags") continue;
|
|
1439
|
+
let valStr;
|
|
1440
|
+
if (val === null || val === void 0) continue;
|
|
1441
|
+
if (typeof val === "boolean") valStr = String(val);
|
|
1442
|
+
else if (typeof val === "number") valStr = String(val);
|
|
1443
|
+
else if (typeof val === "string") {
|
|
1444
|
+
if (val.startsWith("{") || val.startsWith("[")) continue;
|
|
1445
|
+
const clean = val.replace(/\*\*/g, "").replace(/^#+\s*/gm, "").replace(/`/g, "").trim();
|
|
1446
|
+
valStr = `"${truncate(clean.split("\n")[0], Math.min(80, maxLen / 3))}"`;
|
|
1447
|
+
} else if (Array.isArray(val)) valStr = `[${val.length}]`;
|
|
1448
|
+
else if (typeof val === "object") valStr = `{${Object.keys(val).length} keys}`;
|
|
1449
|
+
else valStr = "...";
|
|
1450
|
+
const part = `${key}: ${valStr}`;
|
|
1451
|
+
if (len + part.length + 2 > maxLen) {
|
|
1452
|
+
parts.push("...");
|
|
1453
|
+
break;
|
|
1454
|
+
}
|
|
1455
|
+
parts.push(part);
|
|
1456
|
+
len += part.length + 2;
|
|
1457
|
+
}
|
|
1458
|
+
return `{${parts.join(", ")}}`;
|
|
1459
|
+
}
|
|
1460
|
+
function extractOutputPreview(output, maxLen = 120) {
|
|
1461
|
+
try {
|
|
1462
|
+
const obj = JSON.parse(output);
|
|
1463
|
+
return formatJsonPreview(obj, maxLen);
|
|
1464
|
+
} catch {
|
|
1465
|
+
return extractTruncatedJsonPreview(output, maxLen);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
function parseTruncatedJson(input) {
|
|
1469
|
+
let pos = 0;
|
|
1470
|
+
const len = input.length;
|
|
1471
|
+
function skipWhitespace() {
|
|
1472
|
+
while (pos < len && " \n\r".includes(input[pos])) pos++;
|
|
1473
|
+
}
|
|
1474
|
+
function parseString() {
|
|
1475
|
+
if (input[pos] !== '"') return "";
|
|
1476
|
+
pos++;
|
|
1477
|
+
let result = "";
|
|
1478
|
+
while (pos < len) {
|
|
1479
|
+
const ch = input[pos];
|
|
1480
|
+
if (ch === "\\" && pos + 1 < len) {
|
|
1481
|
+
const next = input[pos + 1];
|
|
1482
|
+
if (next === "n") {
|
|
1483
|
+
result += "\n";
|
|
1484
|
+
pos += 2;
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
if (next === "t") {
|
|
1488
|
+
result += " ";
|
|
1489
|
+
pos += 2;
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1492
|
+
if (next === '"') {
|
|
1493
|
+
result += '"';
|
|
1494
|
+
pos += 2;
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1497
|
+
if (next === "\\") {
|
|
1498
|
+
result += "\\";
|
|
1499
|
+
pos += 2;
|
|
1500
|
+
continue;
|
|
1501
|
+
}
|
|
1502
|
+
result += next;
|
|
1503
|
+
pos += 2;
|
|
1504
|
+
continue;
|
|
1505
|
+
}
|
|
1506
|
+
if (ch === '"') {
|
|
1507
|
+
pos++;
|
|
1508
|
+
return result;
|
|
1509
|
+
}
|
|
1510
|
+
result += ch;
|
|
1511
|
+
pos++;
|
|
1512
|
+
}
|
|
1513
|
+
return result;
|
|
1514
|
+
}
|
|
1515
|
+
function parseNumber() {
|
|
1516
|
+
const start = pos;
|
|
1517
|
+
if (input[pos] === "-") pos++;
|
|
1518
|
+
while (pos < len && input[pos] >= "0" && input[pos] <= "9") pos++;
|
|
1519
|
+
if (pos < len && input[pos] === ".") {
|
|
1520
|
+
pos++;
|
|
1521
|
+
while (pos < len && input[pos] >= "0" && input[pos] <= "9") pos++;
|
|
1522
|
+
}
|
|
1523
|
+
return Number(input.slice(start, pos));
|
|
1524
|
+
}
|
|
1525
|
+
function parseValue() {
|
|
1526
|
+
skipWhitespace();
|
|
1527
|
+
if (pos >= len) return void 0;
|
|
1528
|
+
const ch = input[pos];
|
|
1529
|
+
if (ch === '"') return parseString();
|
|
1530
|
+
if (ch === "{") return parseObject();
|
|
1531
|
+
if (ch === "[") return parseArray();
|
|
1532
|
+
if (ch === "t" && input.slice(pos, pos + 4) === "true") {
|
|
1533
|
+
pos += 4;
|
|
1534
|
+
return true;
|
|
1535
|
+
}
|
|
1536
|
+
if (ch === "f" && input.slice(pos, pos + 5) === "false") {
|
|
1537
|
+
pos += 5;
|
|
1538
|
+
return false;
|
|
1539
|
+
}
|
|
1540
|
+
if (ch === "n" && input.slice(pos, pos + 4) === "null") {
|
|
1541
|
+
pos += 4;
|
|
1542
|
+
return null;
|
|
1543
|
+
}
|
|
1544
|
+
if (ch === "-" || ch >= "0" && ch <= "9") return parseNumber();
|
|
1545
|
+
return void 0;
|
|
1546
|
+
}
|
|
1547
|
+
function parseObject() {
|
|
1548
|
+
const obj = {};
|
|
1549
|
+
pos++;
|
|
1550
|
+
skipWhitespace();
|
|
1551
|
+
while (pos < len && input[pos] !== "}") {
|
|
1552
|
+
skipWhitespace();
|
|
1553
|
+
if (pos >= len || input[pos] !== '"') break;
|
|
1554
|
+
const key = parseString();
|
|
1555
|
+
skipWhitespace();
|
|
1556
|
+
if (pos >= len || input[pos] !== ":") {
|
|
1557
|
+
obj[key] = void 0;
|
|
1558
|
+
break;
|
|
1559
|
+
}
|
|
1560
|
+
pos++;
|
|
1561
|
+
const val = parseValue();
|
|
1562
|
+
if (val !== void 0) obj[key] = val;
|
|
1563
|
+
skipWhitespace();
|
|
1564
|
+
if (pos < len && input[pos] === ",") pos++;
|
|
1565
|
+
}
|
|
1566
|
+
if (pos < len && input[pos] === "}") pos++;
|
|
1567
|
+
return obj;
|
|
1568
|
+
}
|
|
1569
|
+
function parseArray() {
|
|
1570
|
+
const arr = [];
|
|
1571
|
+
pos++;
|
|
1572
|
+
skipWhitespace();
|
|
1573
|
+
while (pos < len && input[pos] !== "]") {
|
|
1574
|
+
const val = parseValue();
|
|
1575
|
+
if (val !== void 0) arr.push(val);
|
|
1576
|
+
else break;
|
|
1577
|
+
skipWhitespace();
|
|
1578
|
+
if (pos < len && input[pos] === ",") pos++;
|
|
1579
|
+
skipWhitespace();
|
|
1580
|
+
}
|
|
1581
|
+
if (pos < len && input[pos] === "]") pos++;
|
|
1582
|
+
return arr;
|
|
1583
|
+
}
|
|
1584
|
+
return parseValue();
|
|
1585
|
+
}
|
|
1586
|
+
function extractTruncatedJsonPreview(output, maxLen) {
|
|
1587
|
+
if (!output.startsWith("{") && !output.startsWith("[")) return "";
|
|
1588
|
+
const parsed = parseTruncatedJson(output);
|
|
1589
|
+
if (!parsed || typeof parsed !== "object") return "";
|
|
1590
|
+
return formatJsonPreview(parsed, maxLen);
|
|
1591
|
+
}
|
|
1592
|
+
function formatInputPreview(inputOutputsStr, maxLen) {
|
|
1593
|
+
if (!inputOutputsStr) return "";
|
|
1594
|
+
try {
|
|
1595
|
+
const inputs = JSON.parse(inputOutputsStr);
|
|
1596
|
+
if (typeof inputs !== "object" || inputs === null) return "";
|
|
1597
|
+
const keys = Object.keys(inputs);
|
|
1598
|
+
if (keys.length === 0) return "";
|
|
1599
|
+
const parts = [];
|
|
1600
|
+
let len = 2;
|
|
1601
|
+
for (const key of keys) {
|
|
1602
|
+
const val = inputs[key];
|
|
1603
|
+
let valStr;
|
|
1604
|
+
if (typeof val === "object" && val !== null) {
|
|
1605
|
+
const vkeys = Object.keys(val);
|
|
1606
|
+
if (vkeys.length <= 3) {
|
|
1607
|
+
valStr = `{${vkeys.join(", ")}}`;
|
|
1608
|
+
} else {
|
|
1609
|
+
valStr = `{${vkeys.slice(0, 2).join(", ")}, ...${vkeys.length} keys}`;
|
|
1610
|
+
}
|
|
1611
|
+
} else {
|
|
1612
|
+
valStr = truncate(String(val), 30);
|
|
1613
|
+
}
|
|
1614
|
+
const part = `${key}: ${valStr}`;
|
|
1615
|
+
if (len + part.length + 2 > maxLen) {
|
|
1616
|
+
parts.push("...");
|
|
1617
|
+
break;
|
|
1618
|
+
}
|
|
1619
|
+
parts.push(part);
|
|
1620
|
+
len += part.length + 2;
|
|
1621
|
+
}
|
|
1622
|
+
return `\u2190 {${parts.join(", ")}}`;
|
|
1623
|
+
} catch {
|
|
1624
|
+
return "";
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
function extractRouteIntentTopic(spans) {
|
|
1628
|
+
const riSpan = spans.find((s) => s.attributes["visor.check.id"] === "route-intent");
|
|
1629
|
+
if (riSpan) {
|
|
1630
|
+
const output = String(riSpan.attributes["visor.check.output"] || "");
|
|
1631
|
+
if (output) {
|
|
1632
|
+
try {
|
|
1633
|
+
const obj = JSON.parse(output);
|
|
1634
|
+
if (obj.topic) return truncate(String(obj.topic), 150);
|
|
1635
|
+
} catch {
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
const classifySpan = spans.find((s) => s.attributes["visor.check.id"] === "classify");
|
|
1640
|
+
if (classifySpan) {
|
|
1641
|
+
const output = String(classifySpan.attributes["visor.check.output"] || "");
|
|
1642
|
+
if (output) {
|
|
1643
|
+
try {
|
|
1644
|
+
const obj = JSON.parse(output);
|
|
1645
|
+
if (obj.topic) return truncate(String(obj.topic), 150);
|
|
1646
|
+
} catch {
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
return void 0;
|
|
1651
|
+
}
|
|
1652
|
+
function formatSize(chars) {
|
|
1653
|
+
if (chars < 1e3) return `${chars} chars`;
|
|
1654
|
+
return `${(chars / 1e3).toFixed(1)}k chars`;
|
|
1655
|
+
}
|
|
1656
|
+
function formatDurationMs(ms) {
|
|
1657
|
+
if (ms < 0) return "0ms";
|
|
1658
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
1659
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
1660
|
+
const mins = Math.floor(ms / 6e4);
|
|
1661
|
+
const secs = Math.round(ms % 6e4 / 1e3);
|
|
1662
|
+
return `${mins}m ${secs}s`;
|
|
1663
|
+
}
|
|
1664
|
+
function truncate(str, max) {
|
|
1665
|
+
if (typeof str !== "string") return "";
|
|
1666
|
+
if (str.length <= max) return str;
|
|
1667
|
+
const tail = Math.min(100, Math.floor(max / 3));
|
|
1668
|
+
const head = max - tail - 19;
|
|
1669
|
+
if (head < 10) return str.slice(0, max - 3) + "...";
|
|
1670
|
+
return str.slice(0, head) + " ...[truncated]... " + str.slice(-tail);
|
|
1671
|
+
}
|
|
1672
|
+
var NOISE_SPAN_NAMES, WRAPPER_SPAN_NAMES;
|
|
1673
|
+
var init_trace_serializer = __esm({
|
|
1674
|
+
"src/agent-protocol/trace-serializer.ts"() {
|
|
1675
|
+
init_logger();
|
|
1676
|
+
NOISE_SPAN_NAMES = /* @__PURE__ */ new Set([
|
|
1677
|
+
"engine.state.init",
|
|
1678
|
+
"engine.state.waveplanning",
|
|
1679
|
+
"engine.state.planready",
|
|
1680
|
+
"visor.sandbox.stopAll"
|
|
1681
|
+
]);
|
|
1682
|
+
WRAPPER_SPAN_NAMES = /* @__PURE__ */ new Set([
|
|
1683
|
+
"engine.state.leveldispatch",
|
|
1684
|
+
"visor.ai_check",
|
|
1685
|
+
"probe.delegation"
|
|
1686
|
+
]);
|
|
1687
|
+
}
|
|
1688
|
+
});
|
|
1689
|
+
|
|
1690
|
+
export {
|
|
1691
|
+
fetchTraceSpans,
|
|
1692
|
+
findTraceFile,
|
|
1693
|
+
readTraceIdFromFile,
|
|
1694
|
+
serializeTraceForPrompt,
|
|
1695
|
+
renderSpanTree,
|
|
1696
|
+
renderTraceTree,
|
|
1697
|
+
renderSpanYaml,
|
|
1698
|
+
init_trace_serializer
|
|
1699
|
+
};
|
|
1700
|
+
//# sourceMappingURL=chunk-6C3R6E42.mjs.map
|