@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 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/agent-protocol/trace-serializer.ts","../../src/agent-protocol/task-evaluator.ts"],"sourcesContent":["/**\n * Trace serializer for LLM evaluation prompts and CLI display.\n *\n * Supports multiple trace backends:\n * 1. Grafana Tempo (via Grafana datasource proxy or direct Tempo API)\n * 2. Jaeger (via Jaeger HTTP API)\n * 3. Local NDJSON files (Visor's file-span-exporter OTEL format)\n *\n * Auto-detects the backend from environment variables:\n * - OTEL_EXPORTER_OTLP_ENDPOINT + VISOR_TELEMETRY_SINK=otlp → probe Grafana/Jaeger\n * - VISOR_TRACE_BACKEND=grafana|jaeger|file (explicit override)\n * - GRAFANA_URL, GRAFANA_TEMPO_DATASOURCE_ID → Grafana Tempo\n * - JAEGER_URL → Jaeger\n * - Falls back to local file scan\n */\n\nimport * as fs from 'fs';\nimport * as readline from 'readline';\nimport * as path from 'path';\nimport { logger } from '../logger';\n\n// ---------------------------------------------------------------------------\n// Trace backend configuration\n// ---------------------------------------------------------------------------\n\nexport interface TraceBackendConfig {\n type: 'grafana' | 'jaeger' | 'file' | 'auto';\n /** Grafana base URL, e.g. http://localhost:3000 */\n grafanaUrl?: string;\n /** Grafana Tempo datasource ID (default: auto-detect) */\n grafanaDatasourceId?: string | number;\n /** Jaeger base URL, e.g. http://localhost:16686 */\n jaegerUrl?: string;\n /** Local trace directory (default: output/traces) */\n traceDir?: string;\n /** Auth token for remote APIs */\n authToken?: string;\n}\n\nfunction resolveBackendConfig(overrides?: Partial<TraceBackendConfig>): TraceBackendConfig {\n const explicit = process.env.VISOR_TRACE_BACKEND as TraceBackendConfig['type'] | undefined;\n\n return {\n type: overrides?.type || explicit || 'auto',\n grafanaUrl: overrides?.grafanaUrl || process.env.GRAFANA_URL,\n grafanaDatasourceId: overrides?.grafanaDatasourceId || process.env.GRAFANA_TEMPO_DATASOURCE_ID,\n jaegerUrl: overrides?.jaegerUrl || process.env.JAEGER_URL,\n traceDir: overrides?.traceDir || process.env.VISOR_TRACE_DIR || 'output/traces',\n authToken: overrides?.authToken || process.env.GRAFANA_TOKEN,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Unified span type (normalized from all backends)\n// ---------------------------------------------------------------------------\n\ninterface NormalizedSpan {\n traceId: string;\n spanId: string;\n parentSpanId?: string;\n name: string;\n startTimeMs: number;\n endTimeMs: number;\n durationMs: number;\n attributes: Record<string, string | number | boolean>;\n events: Array<{ name: string; attributes: Record<string, string | number | boolean> }>;\n status: 'ok' | 'error';\n}\n\ninterface SpanTree {\n span: NormalizedSpan;\n children: SpanTree[];\n}\n\n// ---------------------------------------------------------------------------\n// OTLP response parsing (shared by Grafana Tempo and Jaeger)\n// ---------------------------------------------------------------------------\n\n/**\n * Parse OTLP JSON trace response (Tempo/Jaeger format) into normalized spans.\n * Format: { batches: [{ scopeSpans: [{ spans: [...] }] }] }\n * Jaeger variant: { data: [{ spans: [...] }] }\n */\nfunction parseOTLPResponse(data: any): NormalizedSpan[] {\n const spans: NormalizedSpan[] = [];\n\n // Grafana Tempo OTLP format\n if (data.batches) {\n for (const batch of data.batches) {\n for (const ss of batch.scopeSpans || []) {\n for (const s of ss.spans || []) {\n spans.push(normalizeOTLPSpan(s));\n }\n }\n }\n return spans;\n }\n\n // Jaeger native format\n if (data.data && Array.isArray(data.data)) {\n for (const trace of data.data) {\n for (const s of trace.spans || []) {\n spans.push(normalizeJaegerSpan(s, trace.traceID));\n }\n }\n return spans;\n }\n\n return spans;\n}\n\nfunction normalizeOTLPSpan(s: any): NormalizedSpan {\n const startNs = parseInt(s.startTimeUnixNano || '0', 10);\n const endNs = parseInt(s.endTimeUnixNano || '0', 10);\n\n // Decode base64 span/trace IDs if needed, or use hex strings\n const traceId = decodeOTLPId(s.traceId);\n const spanId = decodeOTLPId(s.spanId);\n const parentSpanId = s.parentSpanId ? decodeOTLPId(s.parentSpanId) : undefined;\n\n const attributes: Record<string, string | number | boolean> = {};\n for (const attr of s.attributes || []) {\n const val = attr.value;\n if (val.stringValue !== undefined) attributes[attr.key] = val.stringValue;\n else if (val.intValue !== undefined) attributes[attr.key] = parseInt(val.intValue, 10);\n else if (val.boolValue !== undefined) attributes[attr.key] = val.boolValue;\n else if (val.doubleValue !== undefined) attributes[attr.key] = val.doubleValue;\n }\n\n const events: NormalizedSpan['events'] = [];\n for (const evt of s.events || []) {\n const evtAttrs: Record<string, string | number | boolean> = {};\n for (const a of evt.attributes || []) {\n const v = a.value;\n if (v.stringValue !== undefined) evtAttrs[a.key] = v.stringValue;\n else if (v.intValue !== undefined) evtAttrs[a.key] = parseInt(v.intValue, 10);\n }\n events.push({ name: evt.name, attributes: evtAttrs });\n }\n\n return {\n traceId,\n spanId,\n parentSpanId,\n name: s.name || 'unknown',\n startTimeMs: startNs / 1e6,\n endTimeMs: endNs / 1e6,\n durationMs: (endNs - startNs) / 1e6,\n attributes,\n events,\n status: s.status?.code === 2 ? 'error' : 'ok',\n };\n}\n\nfunction normalizeJaegerSpan(s: any, traceId: string): NormalizedSpan {\n const attributes: Record<string, string | number | boolean> = {};\n for (const tag of s.tags || []) {\n attributes[tag.key] = tag.value;\n }\n\n const events: NormalizedSpan['events'] = [];\n for (const log of s.logs || []) {\n const evtAttrs: Record<string, string | number | boolean> = {};\n for (const f of log.fields || []) evtAttrs[f.key] = f.value;\n events.push({ name: (evtAttrs['event'] as string) || 'log', attributes: evtAttrs });\n }\n\n // Jaeger uses microseconds\n const startUs = s.startTime || 0;\n const durationUs = s.duration || 0;\n\n return {\n traceId,\n spanId: s.spanID,\n parentSpanId: s.references?.find((r: any) => r.refType === 'CHILD_OF')?.spanID,\n name: s.operationName || 'unknown',\n startTimeMs: startUs / 1000,\n endTimeMs: (startUs + durationUs) / 1000,\n durationMs: durationUs / 1000,\n attributes,\n events,\n status:\n attributes['otel.status_code'] === 'ERROR' || attributes['error'] === true ? 'error' : 'ok',\n };\n}\n\n/**\n * OTLP trace/span IDs may be base64-encoded. Decode to hex string.\n */\nfunction decodeOTLPId(id: string): string {\n if (!id) return '';\n // Already hex? (only hex chars)\n if (/^[0-9a-f]+$/i.test(id)) return id.toLowerCase();\n // Base64 → hex\n try {\n return Buffer.from(id, 'base64').toString('hex');\n } catch {\n return id;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Local NDJSON file parsing\n// ---------------------------------------------------------------------------\n\nfunction parseLocalNDJSONSpans(spans: any[]): NormalizedSpan[] {\n return spans.map(s => {\n const startMs = timeValueToMs(s.startTime || [0, 0]);\n const endMs = timeValueToMs(s.endTime || s.startTime || [0, 0]);\n const events: NormalizedSpan['events'] = (s.events || []).map((e: any) => ({\n name: e.name,\n attributes: e.attributes || {},\n }));\n\n return {\n traceId: s.traceId || '',\n spanId: s.spanId || '',\n parentSpanId: s.parentSpanId || undefined,\n name: s.name || 'unknown',\n startTimeMs: startMs,\n endTimeMs: endMs,\n durationMs: endMs - startMs,\n attributes: s.attributes || {},\n events,\n status: s.status?.code === 2 ? 'error' : 'ok',\n };\n });\n}\n\nfunction timeValueToMs(tv: [number, number]): number {\n return tv[0] * 1000 + tv[1] / 1e6;\n}\n\n// ---------------------------------------------------------------------------\n// Trace fetching — multi-backend\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch trace spans by trace ID. Tries backends in order:\n * 1. Explicit backend (config.type)\n * 2. Grafana Tempo (auto-detect)\n * 3. Jaeger (auto-detect)\n * 4. Local NDJSON files\n */\nexport async function fetchTraceSpans(\n traceId: string,\n config?: Partial<TraceBackendConfig>\n): Promise<NormalizedSpan[]> {\n const cfg = resolveBackendConfig(config);\n\n const tryGrafana = cfg.type === 'grafana' || cfg.type === 'auto';\n const tryJaeger = cfg.type === 'jaeger' || cfg.type === 'auto';\n const tryFile = cfg.type === 'file' || cfg.type === 'auto';\n\n // 1. Grafana Tempo\n if (tryGrafana) {\n const spans = await fetchFromGrafanaTempo(traceId, cfg);\n if (spans && spans.length > 0) return spans;\n }\n\n // 2. Jaeger\n if (tryJaeger) {\n const spans = await fetchFromJaeger(traceId, cfg);\n if (spans && spans.length > 0) return spans;\n }\n\n // 3. Local files\n if (tryFile) {\n const spans = await fetchFromLocalFiles(traceId, cfg);\n if (spans && spans.length > 0) return spans;\n }\n\n return [];\n}\n\nasync function fetchFromGrafanaTempo(\n traceId: string,\n cfg: TraceBackendConfig\n): Promise<NormalizedSpan[] | null> {\n // Auto-detect Grafana URL from OTLP endpoint\n let grafanaUrl = cfg.grafanaUrl;\n if (!grafanaUrl) {\n const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;\n if (otlpEndpoint) {\n // Common pattern: OTLP on 4318, Grafana on 3000 or mapped port\n // Try common Grafana ports on same host\n const url = new URL(otlpEndpoint);\n const host = url.hostname;\n for (const port of ['3000', '8001', '80']) {\n try {\n const testUrl = `http://${host}:${port}/api/health`;\n const resp = await httpGet(testUrl, cfg.authToken, 2000);\n if (resp && resp.includes('\"database\"')) {\n grafanaUrl = `http://${host}:${port}`;\n break;\n }\n } catch {}\n }\n }\n }\n\n if (!grafanaUrl) return null;\n\n try {\n // Auto-detect Tempo datasource ID\n let dsId = cfg.grafanaDatasourceId;\n if (!dsId) {\n const dsResp = await httpGet(`${grafanaUrl}/api/datasources`, cfg.authToken);\n if (dsResp) {\n const datasources = JSON.parse(dsResp);\n const tempo = datasources.find((d: any) => d.type === 'tempo');\n if (tempo) dsId = tempo.id;\n }\n }\n if (!dsId) return null;\n\n const traceUrl = `${grafanaUrl}/api/datasources/proxy/${dsId}/api/traces/${traceId}`;\n const resp = await httpGet(traceUrl, cfg.authToken);\n if (!resp) return null;\n\n const data = JSON.parse(resp);\n return parseOTLPResponse(data);\n } catch (err) {\n logger.debug(`[TraceSerializer] Grafana Tempo fetch failed: ${err}`);\n return null;\n }\n}\n\nasync function fetchFromJaeger(\n traceId: string,\n cfg: TraceBackendConfig\n): Promise<NormalizedSpan[] | null> {\n let jaegerUrl = cfg.jaegerUrl;\n if (!jaegerUrl) {\n // Auto-detect: try common Jaeger ports on localhost\n const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;\n const host = otlpEndpoint ? new URL(otlpEndpoint).hostname : 'localhost';\n for (const port of ['16686']) {\n try {\n const testUrl = `http://${host}:${port}/api/services`;\n const resp = await httpGet(testUrl, undefined, 2000);\n if (resp && resp.includes('\"data\"')) {\n jaegerUrl = `http://${host}:${port}`;\n break;\n }\n } catch {}\n }\n }\n if (!jaegerUrl) return null;\n\n try {\n const traceUrl = `${jaegerUrl}/api/traces/${traceId}`;\n const resp = await httpGet(traceUrl, cfg.authToken);\n if (!resp) return null;\n\n const data = JSON.parse(resp);\n return parseOTLPResponse(data);\n } catch (err) {\n logger.debug(`[TraceSerializer] Jaeger fetch failed: ${err}`);\n return null;\n }\n}\n\nasync function fetchFromLocalFiles(\n traceId: string,\n cfg: TraceBackendConfig\n): Promise<NormalizedSpan[] | null> {\n const traceFile = await findTraceFile(traceId, cfg.traceDir);\n if (!traceFile) return null;\n\n try {\n const { parseNDJSONTrace } = await import('../debug-visualizer/trace-reader');\n const trace = await parseNDJSONTrace(traceFile);\n return parseLocalNDJSONSpans(trace.spans as any[]);\n } catch (err) {\n logger.debug(`[TraceSerializer] Local file parse failed: ${err}`);\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// HTTP helper\n// ---------------------------------------------------------------------------\n\nasync function httpGet(\n url: string,\n authToken?: string,\n timeoutMs?: number\n): Promise<string | null> {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs || 10000);\n\n const headers: Record<string, string> = {};\n if (authToken) headers['Authorization'] = `Bearer ${authToken}`;\n\n const resp = await fetch(url, {\n signal: controller.signal,\n headers,\n });\n clearTimeout(timeout);\n if (!resp.ok) return null;\n return await resp.text();\n } catch {\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Trace file discovery (local NDJSON files)\n// ---------------------------------------------------------------------------\n\n/**\n * Find the NDJSON trace file matching a given trace ID.\n * Scans the first line of each `.ndjson` file in the trace directory.\n */\nexport async function findTraceFile(traceId: string, traceDir?: string): Promise<string | null> {\n const dir = traceDir || process.env.VISOR_TRACE_DIR || 'output/traces';\n if (!fs.existsSync(dir)) return null;\n\n const files = fs.readdirSync(dir).filter(f => f.endsWith('.ndjson'));\n\n for (const file of files) {\n const filePath = path.join(dir, file);\n try {\n const firstLine = await readFirstLine(filePath);\n if (!firstLine) continue;\n const parsed = JSON.parse(firstLine);\n if (parsed.traceId === traceId) return filePath;\n } catch {\n // skip malformed files\n }\n }\n\n return null;\n}\n\nasync function readFirstLine(filePath: string): Promise<string | null> {\n return new Promise((resolve, reject) => {\n const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });\n const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });\n let resolved = false;\n rl.on('line', (line: string) => {\n if (!resolved) {\n resolved = true;\n rl.close();\n stream.destroy();\n resolve(line.trim() || null);\n }\n });\n rl.on('close', () => {\n if (!resolved) resolve(null);\n });\n rl.on('error', reject);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Noise filtering\n// ---------------------------------------------------------------------------\n\n/** Span names that are pure infrastructure noise and should be filtered out */\nconst NOISE_SPAN_NAMES = new Set([\n 'engine.state.init',\n 'engine.state.waveplanning',\n 'engine.state.planready',\n 'visor.sandbox.stopAll',\n]);\n\n/** Span names that are redundant wrappers — lift their children up */\nconst WRAPPER_SPAN_NAMES = new Set([\n 'engine.state.leveldispatch',\n 'visor.ai_check',\n 'probe.delegation',\n]);\n\nfunction isNoiseSpan(span: NormalizedSpan): boolean {\n return NOISE_SPAN_NAMES.has(span.name);\n}\n\nfunction isWrapperSpan(span: NormalizedSpan): boolean {\n return WRAPPER_SPAN_NAMES.has(span.name);\n}\n\n// ---------------------------------------------------------------------------\n// Build span tree from flat list (with noise filtering)\n// ---------------------------------------------------------------------------\n\nfunction buildSpanTree(spans: NormalizedSpan[]): SpanTree {\n // First filter out pure noise spans\n const filtered = spans.filter(s => !isNoiseSpan(s));\n\n const nodeMap = new Map<string, SpanTree>();\n\n // Create all nodes\n for (const span of filtered) {\n nodeMap.set(span.spanId, { span, children: [] });\n }\n\n // Build parent-child relationships\n let root: SpanTree | undefined;\n const orphans: SpanTree[] = [];\n for (const span of filtered) {\n const node = nodeMap.get(span.spanId)!;\n if (!span.parentSpanId) {\n root = node;\n } else {\n // Walk up to find nearest non-filtered ancestor\n let parentId: string | undefined = span.parentSpanId;\n while (parentId && !nodeMap.has(parentId)) {\n // Parent was filtered — find grandparent from original spans\n const parentSpan = spans.find(s => s.spanId === parentId);\n parentId = parentSpan?.parentSpanId;\n }\n if (parentId) {\n const parent = nodeMap.get(parentId);\n if (parent) parent.children.push(node);\n } else if (!root) {\n root = node;\n } else {\n // Orphaned span — parent not in this trace; attach to root later\n orphans.push(node);\n }\n }\n }\n\n // If no root found, pick the span with the longest duration\n if (!root) {\n const sorted = [...nodeMap.values()].sort((a, b) => b.span.durationMs - a.span.durationMs);\n root = sorted[0] || { span: filtered[0], children: [] };\n }\n\n // Attach orphaned spans to root so they appear in the tree\n if (orphans.length > 0) {\n root.children.push(...orphans);\n }\n\n // Sort children by start time\n const sortChildren = (node: SpanTree) => {\n node.children.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);\n node.children.forEach(sortChildren);\n };\n sortChildren(root);\n\n // Unwrap wrapper spans — lift children up and discard the wrapper\n const unwrap = (node: SpanTree): SpanTree => {\n // Recursively process children first\n node.children = node.children.map(unwrap);\n\n // Flatten: replace wrapper children with their children\n const newChildren: SpanTree[] = [];\n for (const child of node.children) {\n if (isWrapperSpan(child.span)) {\n // Lift grandchildren up\n newChildren.push(...child.children);\n } else {\n newChildren.push(child);\n }\n }\n node.children = newChildren;\n return node;\n };\n unwrap(root);\n\n // Remove redundant probe.event.tool.result spans that are siblings of\n // search.delegate spans — these are aggregate results echoed back to the\n // parent AI and duplicate info already shown inside the delegate subtree\n const removeDelegateEchos = (node: SpanTree): void => {\n const hasDelegateChild = node.children.some(c => c.span.name === 'search.delegate');\n if (hasDelegateChild) {\n node.children = node.children.filter(c => {\n if (c.span.name !== 'probe.event.tool.result') return true;\n const toolName = c.span.attributes['tool.name'];\n // Remove search echoes — their details are inside search.delegate\n return toolName !== 'search';\n });\n }\n node.children.forEach(removeDelegateEchos);\n };\n removeDelegateEchos(root);\n\n return root;\n}\n\n// ---------------------------------------------------------------------------\n// Compact tree rendering\n// ---------------------------------------------------------------------------\n\nexport interface RenderTreeOptions {\n maxDepth?: number;\n maxChars?: number;\n /** Fallback intent text for AI spans whose input is too truncated */\n fallbackIntent?: string;\n /** When true, don't truncate output/intent previews */\n fullOutput?: boolean;\n /** Final task response from the task store (full, not OTEL-truncated) */\n taskResponse?: string;\n}\n\n/**\n * Tracks displayed outputs and intents to avoid repeating the same text.\n * When a duplicate is detected, shows \"= <first-span-name>\" instead.\n */\ninterface DeduplicationContext {\n /** Map from normalized output prefix → span name that first displayed it */\n outputs: Map<string, string>;\n /** Map from normalized intent prefix → span name that first displayed it */\n intents: Map<string, string>;\n}\n\n/** Normalize text for dedup comparison: lowercase, collapse whitespace, take prefix */\nfunction dedupeKey(text: string): string {\n return text.replace(/\\s+/g, ' ').trim().slice(0, 100).toLowerCase();\n}\n\n/** Check if text was already displayed; if so return the reference name. Otherwise register it. */\nfunction dedupeOrRegister(\n ctx: DeduplicationContext,\n kind: 'outputs' | 'intents',\n text: string,\n spanName: string\n): string | null {\n if (!text || text.length < 20) return null;\n const key = dedupeKey(text);\n if (!key) return null;\n const map = ctx[kind];\n const existing = map.get(key);\n if (existing && existing !== spanName) {\n return existing;\n }\n // Cross-check: intents against outputs and vice versa\n const otherMap = kind === 'outputs' ? ctx.intents : ctx.outputs;\n const crossRef = otherMap.get(key);\n if (crossRef && crossRef !== spanName) {\n map.set(key, spanName);\n return crossRef;\n }\n map.set(key, spanName);\n return null;\n}\n\n/**\n * Serialize a trace (by ID) into a compact text tree for LLM prompts.\n * Auto-detects the backend (Grafana Tempo, Jaeger, local files).\n */\nexport async function serializeTraceForPrompt(\n traceIdOrPath: string,\n maxChars?: number,\n backendConfig?: Partial<TraceBackendConfig>,\n /** Final task response from the task store (not truncated by OTEL) */\n taskResponse?: string,\n /** Trace ID to try remote backends first (preferred over local file) */\n fallbackTraceId?: string\n): Promise<string> {\n let spans: NormalizedSpan[] = [];\n const isFilePath = traceIdOrPath.includes('/') || traceIdOrPath.endsWith('.ndjson');\n\n // Always try remote backends first (Grafana Tempo, Jaeger) — they have\n // full span structure with IDs, timestamps, and parent-child relationships.\n // The local NDJSON fallback file only has minimal event markers.\n const remoteTraceId = fallbackTraceId || (!isFilePath ? traceIdOrPath : undefined);\n if (remoteTraceId) {\n spans = await fetchTraceSpans(remoteTraceId, backendConfig);\n }\n\n // Fall back to local file only when remote returned nothing\n if (spans.length === 0 && isFilePath) {\n try {\n const { parseNDJSONTrace } = await import('../debug-visualizer/trace-reader');\n const trace = await parseNDJSONTrace(traceIdOrPath);\n spans = parseLocalNDJSONSpans(trace.spans as any[]);\n } catch {\n // file may not exist or be malformed\n }\n }\n\n // Last resort: if traceIdOrPath wasn't tried as a trace ID yet, try it\n if (spans.length === 0 && !remoteTraceId && !isFilePath) {\n spans = await fetchTraceSpans(traceIdOrPath, backendConfig);\n }\n\n if (spans.length === 0) {\n return '(no trace data available)';\n }\n\n const tree = buildSpanTree(spans);\n\n // Extract the route-intent topic as a global fallback for AI intent\n const routeIntentTopic = extractRouteIntentTopic(spans);\n\n // maxChars > 100k signals \"full\" mode (no truncation of previews)\n const fullOutput = (maxChars ?? 4000) > 100000;\n\n return renderSpanYaml(tree, spans, {\n maxChars: maxChars ?? 4000,\n fallbackIntent: routeIntentTopic,\n fullOutput,\n taskResponse,\n });\n}\n\n/**\n * Render a span tree as a compact ASCII tree with durations and details.\n */\nexport function renderSpanTree(tree: SpanTree, opts?: RenderTreeOptions): string {\n const maxChars = opts?.maxChars ?? 4000;\n const maxDepth = opts?.maxDepth ?? 20;\n const lines: string[] = [];\n\n const dedup: DeduplicationContext = { outputs: new Map(), intents: new Map() };\n renderNode(\n tree,\n '',\n true,\n 0,\n maxDepth,\n lines,\n undefined,\n opts?.fallbackIntent,\n opts?.fullOutput,\n dedup\n );\n\n let result = lines.join('\\n');\n if (result.length > maxChars) {\n result = result.slice(0, maxChars - 20) + '\\n... (truncated)';\n }\n return result;\n}\n\n// Keep backward compat — renderTraceTree delegates to renderSpanTree with adapter\nexport function renderTraceTree(\n tree: any, // ExecutionNode from trace-reader\n opts?: RenderTreeOptions\n): string {\n // Convert ExecutionNode tree to SpanTree\n const convert = (node: any): SpanTree => ({\n span: {\n traceId: node.span?.traceId || '',\n spanId: node.span?.spanId || '',\n parentSpanId: node.span?.parentSpanId,\n name: node.span?.name || node.checkId || 'unknown',\n startTimeMs: timeValueToMs(node.span?.startTime || [0, 0]),\n endTimeMs: timeValueToMs(node.span?.endTime || [0, 0]),\n durationMs: node.span?.duration || 0,\n attributes: node.span?.attributes || {},\n events: (node.span?.events || []).map((e: any) => ({\n name: e.name,\n attributes: e.attributes || {},\n })),\n status: node.status === 'error' ? 'error' : 'ok',\n },\n children: (node.children || []).map(convert),\n });\n return renderSpanTree(convert(tree), opts);\n}\n\n// ---------------------------------------------------------------------------\n// YAML-based renderer\n// ---------------------------------------------------------------------------\n\n/**\n * Render a span tree as YAML-like structured output.\n * More readable than ASCII tree for JSON inputs/outputs.\n */\nexport function renderSpanYaml(\n tree: SpanTree,\n allSpans: NormalizedSpan[],\n opts?: RenderTreeOptions\n): string {\n const fullOutput = opts?.fullOutput ?? false;\n const maxLen = fullOutput ? 100000 : 120;\n const dedup: DeduplicationContext = { outputs: new Map(), intents: new Map() };\n const lines: string[] = [];\n\n renderYamlNode(tree, 0, lines, dedup, opts?.fallbackIntent, fullOutput, maxLen);\n\n // Append the final task response from the task store (not OTEL-truncated)\n if (opts?.taskResponse) {\n // Remove any trailing dedup line like \"output: = generate-response\"\n while (lines.length > 0 && /^\\s*output:\\s*=\\s*\\S+/.test(lines[lines.length - 1])) {\n lines.pop();\n }\n const ml = fullOutput ? 100000 : 500;\n const text = opts.taskResponse.replace(/\\*\\*/g, '').replace(/`/g, '').trim();\n if (fullOutput) {\n lines.push(' response: |');\n for (const line of text.split('\\n')) {\n lines.push(` ${line}`);\n }\n } else {\n const truncated = truncate(text.replace(/\\n/g, ' '), ml);\n lines.push(` response: ${truncated}`);\n if (text.length > ml) {\n lines.push(' # use --full for complete response');\n }\n }\n }\n\n return lines.join('\\n');\n}\n\nfunction renderYamlNode(\n node: SpanTree,\n indent: number,\n lines: string[],\n dedup: DeduplicationContext,\n fallbackIntent?: string,\n fullOutput?: boolean,\n maxLen?: number,\n parentSpan?: NormalizedSpan\n): void {\n const pad = ' '.repeat(indent);\n const attrs = node.span.attributes;\n const duration = formatDurationMs(node.span.durationMs);\n const name = node.span.name;\n const ml = maxLen ?? 120;\n\n // Helper: get display name — for AI spans, use parent check name\n const parentCheckId = parentSpan?.attributes['visor.check.id'];\n const parentCheckName = parentCheckId\n ? String(parentCheckId).replace(/^visor\\.check\\./, '')\n : undefined;\n const displayName =\n name === 'ai.request' && parentCheckName\n ? parentCheckName\n : String(attrs['visor.check.id'] || name).replace(/^visor\\.check\\./, '');\n\n // --- Tool calls ---\n const toolName = attrs['tool.name'] || attrs['visor.tool.name'];\n if (toolName) {\n const toolInput = extractToolInput(String(toolName), attrs);\n const toolResultLen = attrs['tool.result.length'] || attrs['tool.result.count'];\n const tn = String(toolName);\n // Detect \"no results\" for search tools: probe header is ~350-450 chars,\n // so result.length < 500 for search means no actual matches\n const isSearchTool = tn === 'search' || tn === 'searchCode' || tn === 'search_code';\n const numLen = toolResultLen ? Number(toolResultLen) : -1;\n const noResults = isSearchTool && numLen >= 0 && numLen < 500;\n const resultSize = noResults\n ? ' → no results'\n : toolResultLen\n ? ` → ${formatSize(numLen)}`\n : '';\n const successMark = attrs['tool.success'] === false ? ' ✗' : '';\n lines.push(`${pad}- ${tn}(${toolInput})${resultSize}${successMark}`);\n return;\n }\n\n // --- Search delegate dedup decision ---\n if (name === 'search.delegate.dedup') {\n const query = attrs['dedup.query'] || '';\n const action = attrs['dedup.action'] || '?';\n const reason = attrs['dedup.reason'] || '';\n const rewritten = attrs['dedup.rewritten'] || '';\n const prevCount = attrs['dedup.previous_count'] || '0';\n let detail = `${action}`;\n if (rewritten) detail += ` → \"${truncate(String(rewritten), 60)}\"`;\n if (reason) detail += ` (${truncate(String(reason), 80)})`;\n lines.push(\n `${pad}dedup(\"${truncate(String(query), 60)}\") [${prevCount} prior]: ${detail} — ${duration}`\n );\n return;\n }\n\n // --- Search delegate ---\n if (name === 'search.delegate') {\n const query = attrs['search.query'] || '';\n const rewritten = attrs['search.query.rewritten'] || '';\n const output = attrs['search.delegate.output'] || '';\n const outputLen = attrs['search.delegate.output_length'] || '';\n\n let header = `search.delegate(\"${truncate(String(query), 80)}\")`;\n if (rewritten) header += ` → rewritten: \"${truncate(String(rewritten), 60)}\"`;\n header += ` — ${duration}`;\n lines.push(`${pad}${header}:`);\n\n // Parse structured output to show confidence, searches, and groups summary\n if (output) {\n try {\n const parsed = JSON.parse(String(output));\n if (parsed.confidence) {\n let confLine = `confidence: ${parsed.confidence}`;\n if (parsed.reason) confLine += ` — ${truncate(String(parsed.reason), 100)}`;\n lines.push(`${pad} ${confLine}`);\n }\n if (parsed.searches && Array.isArray(parsed.searches) && parsed.searches.length > 0) {\n lines.push(`${pad} searches (${parsed.searches.length}):`);\n for (const s of parsed.searches) {\n const outcome = s.had_results ? '✓' : '✗';\n lines.push(\n `${pad} ${outcome} \"${truncate(String(s.query || ''), 60)}\" in ${truncate(String(s.path || '.'), 40)}`\n );\n }\n }\n if (parsed.groups && Array.isArray(parsed.groups) && parsed.groups.length > 0) {\n lines.push(`${pad} groups (${parsed.groups.length}):`);\n for (const g of parsed.groups) {\n const fileCount = g.files?.length || 0;\n lines.push(`${pad} - ${truncate(String(g.reason || ''), 80)} (${fileCount} files)`);\n }\n }\n } catch {\n // Not JSON — show raw output length\n if (outputLen) lines.push(`${pad} output: ${outputLen} chars`);\n }\n }\n\n for (const child of node.children) {\n renderYamlNode(\n child,\n indent + 1,\n lines,\n dedup,\n fallbackIntent,\n fullOutput,\n maxLen,\n node.span\n );\n }\n return;\n }\n\n // --- AI request ---\n if (name === 'ai.request') {\n const model = attrs['ai.model'] || attrs['gen_ai.request.model'] || '?';\n const tokensIn = attrs['ai.input_length'] || attrs['gen_ai.usage.input_tokens'] || '';\n const tokensOut = attrs['gen_ai.usage.output_tokens'] || '';\n const tokenParts: string[] = [];\n if (tokensIn) tokenParts.push(`${tokensIn} in`);\n if (tokensOut) tokenParts.push(`${tokensOut} out`);\n const tokenStr = tokenParts.length > 0 ? ` — ${tokenParts.join(', ')}` : '';\n\n const hasChildren = node.children.length > 0;\n lines.push(`${pad}ai: ${model} — ${duration}${tokenStr}${hasChildren ? ':' : ''}`);\n\n // Intent\n const aiInput = String(attrs['ai.input'] || '');\n let intent = extractAIIntent(aiInput, ml);\n if (!intent && parentSpan) {\n const promptPreview = String(\n parentSpan.attributes['visor.provider.request.prompt.preview'] || ''\n );\n if (promptPreview) intent = extractAIIntent(promptPreview, ml);\n if (!intent) {\n const inputOutputs = String(parentSpan.attributes['visor.check.input.outputs'] || '');\n if (inputOutputs) {\n try {\n const o = JSON.parse(inputOutputs);\n const t = o['route-intent']?.topic;\n if (t) intent = truncate(String(t), ml);\n } catch {}\n }\n }\n }\n if (!intent && fallbackIntent && parentSpan?.name !== 'search.delegate') {\n intent = fallbackIntent;\n }\n if (intent) {\n const intentRef = dedupeOrRegister(dedup, 'intents', intent, displayName);\n if (intentRef) {\n lines.push(`${pad} intent: = ${intentRef}`);\n } else {\n lines.push(`${pad} intent: ${intent}`);\n }\n }\n\n // Children (tool calls etc.)\n for (const child of node.children) {\n renderYamlNode(\n child,\n indent + 1,\n lines,\n dedup,\n fallbackIntent,\n fullOutput,\n maxLen,\n node.span\n );\n }\n\n // Output from parent check span (AI provider stores output on the check span, not ai.request)\n if (parentSpan) {\n const checkOutput = String(parentSpan.attributes['visor.check.output'] || '');\n if (checkOutput) {\n renderYamlOutput(\n checkOutput,\n `${pad} `,\n 'output',\n displayName,\n dedup,\n lines,\n fullOutput,\n ml\n );\n }\n }\n return;\n }\n\n // --- visor.run ---\n if (name === 'visor.run') {\n const source = attrs['visor.run.source'] || '';\n const visorVersion = attrs['visor.version'] || '';\n const probeVersion = attrs['probe.version'] || '';\n const slackUser = attrs['slack.user_id'] || '';\n lines.push(`${pad}visor.run:`);\n lines.push(`${pad} trace_id: ${node.span.traceId}`);\n if (visorVersion) lines.push(`${pad} visor: ${visorVersion}`);\n if (probeVersion) lines.push(`${pad} probe: ${probeVersion}`);\n if (source) lines.push(`${pad} source: ${source}`);\n if (slackUser) lines.push(`${pad} slack_user: ${slackUser}`);\n lines.push(`${pad} duration: ${duration}`);\n for (const child of node.children) {\n renderYamlNode(\n child,\n indent + 1,\n lines,\n dedup,\n fallbackIntent,\n fullOutput,\n maxLen,\n node.span\n );\n }\n return;\n }\n\n // --- Visor check ---\n const checkId = attrs['visor.check.id'];\n const checkType = attrs['visor.check.type'];\n if (checkId || name.startsWith('visor.check.')) {\n const cleanName = String(checkId || name).replace(/^visor\\.check\\./, '');\n const errMark = node.span.status === 'error' ? ' ✗' : '';\n lines.push(`${pad}${cleanName}:${errMark}`);\n if (checkType) lines.push(`${pad} type: ${checkType}`);\n lines.push(`${pad} duration: ${duration}`);\n\n // Input — show the actual question/topic first, then dependency outputs\n const inputContext = String(attrs['visor.check.input.context'] || '');\n const inputOutputs = String(attrs['visor.check.input.outputs'] || '');\n const question = extractQuestionFromContext(inputContext, inputOutputs);\n if (question || (inputOutputs && inputOutputs !== '{}')) {\n renderYamlInput(inputOutputs, `${pad} `, lines, fullOutput, ml, question);\n }\n\n // Children\n for (const child of node.children) {\n renderYamlNode(\n child,\n indent + 1,\n lines,\n dedup,\n fallbackIntent,\n fullOutput,\n maxLen,\n node.span\n );\n }\n\n // Output (after children)\n // Skip if check has a direct AI child — it already rendered this check's output\n const hasDirectAiChild = node.children.some(c => c.span.name === 'ai.request');\n if (!hasDirectAiChild) {\n const output = String(attrs['visor.check.output'] || '');\n if (output) {\n renderYamlOutput(output, `${pad} `, 'output', cleanName, dedup, lines, fullOutput, ml);\n }\n }\n return;\n }\n\n // --- Generic span ---\n const errMark = node.span.status === 'error' ? ' ✗' : '';\n const hasChildren = node.children.length > 0;\n lines.push(`${pad}${name} — ${duration}${errMark}${hasChildren ? ':' : ''}`);\n for (const child of node.children) {\n renderYamlNode(child, indent + 1, lines, dedup, fallbackIntent, fullOutput, maxLen, node.span);\n }\n}\n\n/**\n * Render a JSON output as YAML key-value pairs under a given prefix.\n * Handles deduplication — if the output was already shown, prints \"output: = <name>\".\n */\nfunction renderYamlOutput(\n rawOutput: string,\n pad: string,\n label: string,\n spanName: string,\n dedup: DeduplicationContext,\n lines: string[],\n fullOutput?: boolean,\n maxLen?: number\n): void {\n const ml = maxLen ?? 120;\n\n // Try to parse as JSON for structured display\n let obj: any;\n try {\n obj = JSON.parse(rawOutput);\n } catch {\n // Truncated JSON — use tolerant parser to extract what we can\n obj = parseTruncatedJson(rawOutput);\n }\n if (obj === null || obj === undefined || typeof obj !== 'object') return;\n\n // Unwrap single-key wrapper objects: {answer: {text: \"...\"}} → {text: \"...\"}\n // and {text: \"...\"} → render text inline\n if (typeof obj === 'object' && !Array.isArray(obj)) {\n const keys = Object.keys(obj);\n if (keys.length === 1 && typeof obj[keys[0]] === 'object' && obj[keys[0]] !== null) {\n obj = obj[keys[0]]; // unwrap {answer: {...}} → {...}\n }\n // If single text key, render inline\n const objKeys = Object.keys(obj);\n if (objKeys.length === 1 && objKeys[0] === 'text' && typeof obj.text === 'string') {\n const text = obj.text.replace(/\\*\\*/g, '').replace(/`/g, '').trim();\n const flat = text.replace(/\\n/g, ' ');\n const preview2 = fullOutput ? flat : truncate(flat, ml);\n const ref2 = dedupeOrRegister(dedup, 'outputs', truncate(flat, 100), spanName);\n if (ref2) {\n lines.push(`${pad}${label}: = ${ref2}`);\n } else {\n lines.push(`${pad}${label}: ${preview2}`);\n }\n return;\n }\n }\n\n // Get a dedup key from the formatted preview\n const preview = formatJsonPreview(obj, 200);\n if (!preview) return;\n const ref = dedupeOrRegister(dedup, 'outputs', preview, spanName);\n if (ref) {\n lines.push(`${pad}${label}: = ${ref}`);\n return;\n }\n\n // Render as YAML structure\n renderYamlValue(obj, pad, label, lines, fullOutput, ml);\n}\n\n/**\n * Render a JSON value as YAML structure.\n */\nfunction renderYamlValue(\n val: any,\n pad: string,\n key: string,\n lines: string[],\n fullOutput?: boolean,\n maxLen?: number,\n depth?: number\n): void {\n const ml = maxLen ?? 120;\n const d = depth ?? 0;\n\n if (val === null || val === undefined) return;\n\n if (typeof val === 'boolean' || typeof val === 'number') {\n lines.push(`${pad}${key}: ${val}`);\n return;\n }\n\n if (typeof val === 'string') {\n // Skip stringified JSON\n if (val.startsWith('{') || val.startsWith('[')) return;\n const clean = val.replace(/\\*\\*/g, '').replace(/`/g, '').trim();\n if (fullOutput && clean.length > 100 && clean.includes('\\n')) {\n // Multiline YAML\n lines.push(`${pad}${key}: |`);\n for (const line of clean.split('\\n').slice(0, fullOutput ? 500 : 5)) {\n lines.push(`${pad} ${line}`);\n }\n } else {\n const flat = clean.replace(/\\n/g, ' ');\n const truncVal = fullOutput ? flat : truncate(flat, ml);\n lines.push(`${pad}${key}: ${truncVal}`);\n }\n return;\n }\n\n if (Array.isArray(val)) {\n if (val.length === 0) {\n lines.push(`${pad}${key}: []`);\n return;\n }\n // Short string arrays: render inline like [a, b, c]\n if (val.every((v: any) => typeof v === 'string') && val.join(', ').length < ml) {\n lines.push(`${pad}${key}: [${val.join(', ')}]`);\n return;\n }\n // Limit array depth\n const maxItems = fullOutput ? 20 : 3;\n lines.push(`${pad}${key}:`);\n for (let i = 0; i < Math.min(val.length, maxItems); i++) {\n const item = val[i];\n if (typeof item === 'object' && item !== null) {\n const entries = Object.entries(item).filter(([k]) => k !== 'raw' && k !== 'tags');\n if (entries.length > 0) {\n // Put first key on same line as dash\n const [firstKey, firstVal] = entries[0];\n if (firstVal === null || firstVal === undefined || typeof firstVal !== 'object') {\n const sv =\n typeof firstVal === 'string'\n ? fullOutput\n ? firstVal.split('\\n')[0]\n : truncate(firstVal.split('\\n')[0], ml)\n : String(firstVal ?? '');\n lines.push(`${pad} - ${firstKey}: ${sv}`);\n } else {\n // Complex first value — render key on dash line, value below\n lines.push(`${pad} - ${firstKey}:`);\n for (const [ck, cv] of Object.entries(firstVal)) {\n if (ck === 'raw' || ck === 'skills' || ck === 'tags') continue;\n renderYamlValue(cv, `${pad} `, ck, lines, fullOutput, ml, d + 2);\n }\n }\n for (let j = 1; j < entries.length; j++) {\n const [k, v] = entries[j];\n renderYamlValue(v, `${pad} `, k, lines, fullOutput, ml, d + 1);\n }\n }\n } else {\n lines.push(`${pad} - ${truncate(String(item), ml)}`);\n }\n }\n if (val.length > maxItems) {\n lines.push(`${pad} # ... ${val.length - maxItems} more`);\n }\n return;\n }\n\n if (typeof val === 'object') {\n // Limit nesting depth\n if (d > 3) {\n const keys = Object.keys(val);\n lines.push(`${pad}${key}: {${keys.slice(0, 4).join(', ')}${keys.length > 4 ? ', ...' : ''}}`);\n return;\n }\n lines.push(`${pad}${key}:`);\n for (const [k, v] of Object.entries(val)) {\n if (k === 'raw' || k === 'tags') continue;\n renderYamlValue(v, `${pad} `, k, lines, fullOutput, ml, d + 1);\n }\n }\n}\n\n/**\n * Extract the actual user question/topic from the check's input context.\n * The context contains the full execution context including outputs from\n * dependency checks. We look for the route-intent topic, args.topic, or\n * other indicators of the user's question.\n */\nfunction extractQuestionFromContext(\n contextStr: string,\n inputOutputsStr: string\n): string | undefined {\n if (!contextStr) return undefined;\n\n try {\n const ctx = JSON.parse(contextStr);\n\n // 1. Check outputs.route-intent.topic (most common)\n const outputs = ctx.outputs || {};\n const routeIntent = outputs['route-intent'];\n if (routeIntent) {\n const topic = routeIntent.topic || routeIntent.intent || routeIntent.question;\n if (topic && typeof topic === 'string') return topic;\n // route-intent might be a string itself\n if (typeof routeIntent === 'string') return routeIntent;\n }\n\n // 2. Check args.topic or args.question\n const args = ctx.args || {};\n if (args.topic && typeof args.topic === 'string') return args.topic;\n if (args.question && typeof args.question === 'string') return args.question;\n if (args.intent && typeof args.intent === 'string') return args.intent;\n\n // 3. Check any output with a topic field not already in inputOutputs\n for (const key of Object.keys(outputs)) {\n const val = outputs[key];\n if (typeof val === 'object' && val !== null) {\n if (val.topic && typeof val.topic === 'string') {\n // Skip if this output is already shown in inputOutputs\n try {\n const depOutputs = JSON.parse(inputOutputsStr);\n if (depOutputs[key]) continue;\n } catch {}\n return val.topic;\n }\n }\n }\n } catch {\n // Truncated JSON — try regex extraction\n const topicMatch = contextStr.match(/\"topic\"\\s*:\\s*\"([^\"]+)\"/);\n if (topicMatch) return topicMatch[1];\n }\n\n return undefined;\n}\n\n/**\n * Render input dependencies as YAML.\n */\nfunction renderYamlInput(\n inputOutputsStr: string,\n pad: string,\n lines: string[],\n fullOutput?: boolean,\n maxLen?: number,\n question?: string\n): void {\n const ml = maxLen ?? 120;\n\n // Show the question/topic prominently first\n if (question) {\n lines.push(`${pad}input: ${truncate(question, fullOutput ? 100000 : ml)}`);\n }\n\n try {\n const inputs = JSON.parse(inputOutputsStr);\n if (typeof inputs !== 'object' || inputs === null) return;\n const keys = Object.keys(inputs);\n if (keys.length === 0) return;\n\n if (!question) lines.push(`${pad}input:`);\n for (const key of keys) {\n const val = inputs[key];\n if (typeof val === 'object' && val !== null) {\n renderYamlValue(val, `${pad} `, key, lines, fullOutput, ml, 0);\n } else {\n lines.push(`${pad} ${key}: ${truncate(String(val), ml)}`);\n }\n }\n } catch {\n // Can't parse — skip\n }\n}\n\n// ---------------------------------------------------------------------------\n// ASCII tree renderer (kept for backward compat / tests)\n// ---------------------------------------------------------------------------\n\nfunction renderNode(\n node: SpanTree,\n prefix: string,\n isLast: boolean,\n depth: number,\n maxDepth: number,\n lines: string[],\n parentSpan?: NormalizedSpan,\n fallbackIntent?: string,\n fullOutput?: boolean,\n dedup?: DeduplicationContext\n): void {\n if (depth > maxDepth) return;\n\n const hasChildren = node.children.length > 0;\n const connector = depth === 0 ? '' : isLast ? '└── ' : '├── ';\n const { line: formatted, output: deferredOutput } = formatSpanLine(\n node.span,\n parentSpan,\n fallbackIntent,\n fullOutput,\n dedup,\n hasChildren\n );\n\n // Skip if formatSpanLine returns empty (filtered)\n if (formatted) {\n lines.push(`${prefix}${connector}${formatted}`);\n }\n\n const childPrefix = depth === 0 ? '' : formatted ? prefix + (isLast ? ' ' : '│ ') : prefix; // no indent increase if this node was skipped\n\n for (let i = 0; i < node.children.length; i++) {\n const isChildLast = i === node.children.length - 1 && !deferredOutput;\n renderNode(\n node.children[i],\n childPrefix,\n isChildLast,\n depth + (formatted ? 1 : 0),\n maxDepth,\n lines,\n node.span,\n fallbackIntent,\n fullOutput,\n dedup\n );\n }\n\n // Show deferred output at the bottom, after all children\n if (deferredOutput) {\n lines.push(`${childPrefix}└─→ ${deferredOutput}`);\n }\n}\n\ninterface FormatResult {\n line: string;\n /** Output to show after children (deferred to bottom) */\n output?: string;\n}\n\nfunction formatSpanLine(\n span: NormalizedSpan,\n parentSpan?: NormalizedSpan,\n fallbackIntent?: string,\n fullOutput?: boolean,\n dedup?: DeduplicationContext,\n hasChildren?: boolean\n): FormatResult {\n const duration = formatDurationMs(span.durationMs);\n const attrs = span.attributes;\n const name = span.name;\n const previewLimit = fullOutput ? 10000 : 120;\n\n // Helper: get a display name for this span (used in dedup references)\n const displayName = String(attrs['visor.check.id'] || name).replace(/^visor\\.check\\./, '');\n\n // Helper: deduplicate an output string — returns the display string (without → prefix)\n const dedupeOutputStr = (rawOutput: string, label: string): string => {\n if (!rawOutput) return '';\n if (!dedup) return rawOutput;\n const ref = dedupeOrRegister(dedup, 'outputs', rawOutput, label);\n if (ref) return `(= ${ref})`;\n return rawOutput;\n };\n\n // Helper: deduplicate an intent string — returns the display string\n const dedupeIntentStr = (rawIntent: string, label: string): string => {\n if (!rawIntent || !dedup) return rawIntent ? ` 💬 ${rawIntent}` : '';\n const ref = dedupeOrRegister(dedup, 'intents', rawIntent, label);\n if (ref) return ` 💬 (= ${ref})`;\n return ` 💬 ${rawIntent}`;\n };\n\n // Helper: format output as inline or deferred depending on whether node has children\n const makeResult = (line: string, outputPreview: string): FormatResult => {\n if (!outputPreview) return { line };\n if (hasChildren) {\n // Defer output to bottom — show after children\n return { line, output: outputPreview };\n }\n // Inline for leaf nodes\n return { line: `${line} → ${outputPreview}` };\n };\n\n // --- Tool calls: extract real inputs ---\n const toolName = attrs['tool.name'] || attrs['visor.tool.name'];\n if (toolName) {\n const toolInput = extractToolInput(String(toolName), attrs);\n const toolResultLen = attrs['tool.result.length'] || attrs['tool.result.count'];\n const toolSuccess = attrs['tool.success'];\n const tn = String(toolName);\n const isSearchTool = tn === 'search' || tn === 'searchCode' || tn === 'search_code';\n const numLen = toolResultLen ? Number(toolResultLen) : -1;\n const noResults = isSearchTool && numLen >= 0 && numLen < 500;\n const resultSize = noResults ? 'no results' : toolResultLen ? formatSize(numLen) : '';\n const durStr =\n Number(attrs['tool.duration_ms']) > 0\n ? ` (${formatDurationMs(Number(attrs['tool.duration_ms']))})`\n : '';\n\n // Bash: show exit code / signal instead of generic success mark\n let successMark = toolSuccess === false ? ' ✗' : '';\n if (tn === 'bash') {\n const toolResult = String(attrs['tool.result'] || '');\n const exitMatch = toolResult.match(/Exit Code: (\\S+)/);\n const sigMatch = toolResult.match(/Signal: (\\S+)/);\n if (sigMatch && sigMatch[1] !== 'null') {\n successMark = ` [${sigMatch[1]}]`;\n } else if (exitMatch && exitMatch[1] !== '0' && exitMatch[1] !== 'null') {\n successMark = ` [exit ${exitMatch[1]}]`;\n }\n }\n\n return {\n line: `${toolName}(${toolInput})${durStr}${resultSize ? ` → ${resultSize}` : ''}${successMark}`,\n };\n }\n\n // --- Search delegate dedup ---\n if (name === 'search.delegate.dedup') {\n const query = attrs['dedup.query'] || '';\n const action = attrs['dedup.action'] || '?';\n const reason = attrs['dedup.reason'] || '';\n const rewritten = attrs['dedup.rewritten'] || '';\n let detail = `${action}`;\n if (rewritten) detail += ` → \"${truncate(String(rewritten), 50)}\"`;\n if (reason) detail += ` — ${truncate(String(reason), 60)}`;\n return { line: `dedup(\"${truncate(String(query), 50)}\") ${detail} (${duration})` };\n }\n\n // --- Search delegate: show the search query + confidence ---\n if (name === 'search.delegate') {\n const query = attrs['search.query'] || '';\n const rewritten = attrs['search.query.rewritten'] || '';\n const output = attrs['search.delegate.output'] || '';\n let suffix = '';\n // Try to extract confidence and group/search counts from output\n try {\n const parsed = JSON.parse(String(output));\n const parts: string[] = [];\n if (parsed.confidence) parts.push(parsed.confidence);\n if (parsed.groups?.length) parts.push(`${parsed.groups.length} groups`);\n if (parsed.searches?.length) parts.push(`${parsed.searches.length} searches`);\n if (parts.length > 0) suffix = ` → ${parts.join(', ')}`;\n } catch {}\n const rewriteStr = rewritten ? ` → \"${truncate(String(rewritten), 40)}\"` : '';\n return {\n line: `search.delegate(\"${truncate(String(query), 60)}\"${rewriteStr}) (${duration})${suffix}`,\n };\n }\n\n // --- AI request: show model + input summary ---\n if (name === 'ai.request') {\n const model = attrs['ai.model'] || attrs['gen_ai.request.model'] || '?';\n const tokensIn = attrs['ai.input_length'] || attrs['gen_ai.usage.input_tokens'] || '';\n const tokensOut = attrs['gen_ai.usage.output_tokens'] || '';\n const tokenParts: string[] = [];\n if (tokensIn) tokenParts.push(`${tokensIn} in`);\n if (tokensOut) tokenParts.push(`${tokensOut} out`);\n const tokenStr = tokenParts.length > 0 ? ` [${tokenParts.join(', ')}]` : '';\n\n // Extract a short intent/question from ai.input — inline on same line\n const aiInput = String(attrs['ai.input'] || '');\n let intent = extractAIIntent(aiInput, previewLimit);\n\n // Fallback: if ai.input was truncated, try parent check's attributes\n if (!intent && parentSpan) {\n // Try prompt preview first\n const promptPreview = String(\n parentSpan.attributes['visor.provider.request.prompt.preview'] || ''\n );\n if (promptPreview) {\n intent = extractAIIntent(promptPreview, previewLimit);\n }\n // Try extracting topic from parent check's input context (route-intent output)\n if (!intent) {\n const inputOutputs = String(parentSpan.attributes['visor.check.input.outputs'] || '');\n if (inputOutputs) {\n try {\n const outputs = JSON.parse(inputOutputs);\n const topic = outputs['route-intent']?.topic;\n if (topic) intent = truncate(String(topic), 150);\n } catch {}\n }\n }\n }\n // Global fallback: use the route-intent topic — but only for top-level AI,\n // not for sub-delegates whose intent is the search.delegate query\n if (!intent && fallbackIntent && parentSpan?.name !== 'search.delegate') {\n intent = fallbackIntent;\n }\n const intentStr = intent ? dedupeIntentStr(intent, displayName) : '';\n\n // Get output preview from parent check's output\n let outputPreview = '';\n if (parentSpan) {\n const checkOutput = String(parentSpan.attributes['visor.check.output'] || '');\n if (checkOutput) {\n const preview = extractOutputPreview(checkOutput, previewLimit);\n if (preview) outputPreview = dedupeOutputStr(preview, displayName);\n }\n }\n\n const mainLine = `ai ${model} (${duration})${tokenStr}${intentStr}`;\n return makeResult(mainLine, outputPreview);\n }\n\n // --- Visor check: clean name, show type, show input/output preview ---\n const checkId = attrs['visor.check.id'];\n const checkType = attrs['visor.check.type'];\n if (checkId || name.startsWith('visor.check.')) {\n const cleanName = String(checkId || name).replace(/^visor\\.check\\./, '');\n const typeStr = checkType ? ` [${checkType}]` : '';\n\n // Show input dependencies\n const inputOutputs = String(attrs['visor.check.input.outputs'] || '');\n let inputStr = '';\n if (inputOutputs && inputOutputs !== '{}') {\n inputStr = ' ' + formatInputPreview(inputOutputs, previewLimit);\n }\n\n // Get output preview for checks that produce meaningful output\n const output = String(attrs['visor.check.output'] || '');\n let outputPreview = '';\n if (output) {\n const preview = extractOutputPreview(output, previewLimit);\n if (preview) outputPreview = dedupeOutputStr(preview, cleanName);\n }\n\n const mainLine = `${cleanName}${typeStr} (${duration})${inputStr}`;\n return makeResult(mainLine, outputPreview);\n }\n\n // --- visor.run: show source info ---\n if (name === 'visor.run') {\n const source = attrs['visor.run.source'] || '';\n const sourceStr = source ? ` (${source})` : '';\n return { line: `visor.run${sourceStr} (${duration})` };\n }\n\n // --- Negotiated timeout observer: show decision details ---\n if (\n name === 'probe.event.negotiated_timeout.observer' ||\n name === 'negotiated_timeout.observer'\n ) {\n // Decision data lives in span events emitted by Probe inside the observer span.\n // Look through span.events for observer_extended, observer_declined, observer_exhausted.\n let detail = '';\n\n // First try span attributes (future Probe versions may set these directly)\n const attrDecision = attrs['observer.decision'] || attrs['decision_reason'];\n if (attrDecision) {\n const reason = attrs['observer.reason'] || attrs['decision_reason'] || '';\n if (String(attrDecision) === 'extended' || attrs['granted_ms']) {\n const grantedMin =\n attrs['observer.granted_min'] ||\n attrs['granted_min'] ||\n (attrs['granted_ms'] ? Math.round(Number(attrs['granted_ms']) / 60000) : '?');\n detail = `extended +${grantedMin}min`;\n if (reason) detail += ` (${truncate(String(reason), 60)})`;\n const used = attrs['observer.extensions_used'] || attrs['extensions_used'];\n const max = attrs['observer.max_requests'] || attrs['max_requests'];\n if (used) detail += ` [${used}/${max || '?'} used]`;\n } else if (String(attrDecision) === 'exhausted') {\n detail = 'budget exhausted';\n } else {\n detail = `declined`;\n if (reason) detail += `: ${truncate(String(reason), 60)}`;\n }\n }\n\n // Then try span events (current Probe versions emit these)\n if (!detail && span.events.length > 0) {\n for (const evt of span.events) {\n const evtName = evt.name || '';\n const ea = evt.attributes;\n if (evtName.includes('observer_extended')) {\n const grantedMin =\n ea['granted_min'] ||\n (ea['granted_ms'] ? Math.round(Number(ea['granted_ms']) / 60000) : '?');\n detail = `extended +${grantedMin}min`;\n if (ea['decision_reason']) detail += ` (${truncate(String(ea['decision_reason']), 60)})`;\n if (ea['extensions_used'])\n detail += ` [${ea['extensions_used']}/${ea['max_requests'] || '?'} used]`;\n break;\n }\n if (evtName.includes('observer_declined')) {\n detail = 'declined';\n if (ea['decision_reason']) detail += `: ${truncate(String(ea['decision_reason']), 60)}`;\n break;\n }\n if (evtName.includes('observer_exhausted')) {\n const used = ea['extensions_used'] || '?';\n const max = ea['max_requests'] || '?';\n detail = `budget exhausted [${used}/${max} extensions]`;\n break;\n }\n if (evtName.includes('observer_invoked') && !detail) {\n // Show invocation context as fallback if no decision event follows\n const elapsed = ea['elapsed_min'] || '?';\n const tools = ea['active_tools_count'] || 0;\n detail = `${elapsed}min elapsed, ${tools} active tools`;\n // Don't break — keep looking for a decision event\n }\n }\n }\n\n // Final fallback: span attributes for elapsed/active tools\n if (!detail) {\n const elapsed = attrs['elapsed_min'];\n const activeTools = attrs['active_tools_count'] || attrs['active_tools'];\n if (elapsed) detail += `${elapsed}min elapsed`;\n if (activeTools)\n detail += detail ? `, ${activeTools} active tools` : `${activeTools} active tools`;\n }\n\n const label = detail ? `timeout.observer: ${detail}` : 'timeout.observer';\n return { line: `${label} (${duration})` };\n }\n\n // --- Negotiated timeout sub-events (promoted to individual spans) ---\n if (name.includes('negotiated_timeout.observer_')) {\n const suffix = name.replace(/.*negotiated_timeout\\.observer_/, '');\n const reason = attrs['decision_reason'] || '';\n if (suffix === 'extended') {\n const grantedMin =\n attrs['granted_min'] ||\n (attrs['granted_ms'] ? Math.round(Number(attrs['granted_ms']) / 60000) : '?');\n const used = attrs['extensions_used'] || '?';\n const max = attrs['max_requests'] || '?';\n const reasonStr = reason ? ` (${truncate(String(reason), 60)})` : '';\n return { line: `timeout.extended: +${grantedMin}min${reasonStr} [${used}/${max} used]` };\n }\n if (suffix === 'declined') {\n const reasonStr = reason ? `: ${truncate(String(reason), 60)}` : '';\n return { line: `timeout.declined${reasonStr}` };\n }\n if (suffix === 'exhausted') {\n const used = attrs['extensions_used'] || '?';\n const max = attrs['max_requests'] || '?';\n return { line: `timeout.exhausted [${used}/${max} extensions, budget depleted]` };\n }\n // observer_invoked, observer_response — less important, show compact\n if (suffix === 'invoked') {\n const elapsed = attrs['elapsed_min'] || '?';\n const tools = attrs['active_tools_count'] || 0;\n return { line: `timeout.observer invoked (${elapsed}min elapsed, ${tools} active tools)` };\n }\n return { line: `timeout.${suffix} (${duration})` };\n }\n\n // --- Negotiated timeout abort summary: show that final response was generated under timeout ---\n if (name.includes('negotiated_timeout.abort_summary')) {\n const summaryLen = attrs['summary_length'] || attrs['summary.length'];\n const lenStr = summaryLen ? ` → ${formatSize(Number(summaryLen))}` : '';\n return { line: `timeout.abort_summary (${duration})${lenStr}` };\n }\n\n // --- Graceful stop events ---\n if (name.includes('graceful_stop.initiated') || name.includes('graceful_stop.invoked')) {\n const reason = attrs['graceful_stop.reason'] || attrs['reason'] || '';\n const reasonStr = reason ? `: ${truncate(String(reason), 80)}` : '';\n return { line: `graceful_stop${reasonStr} (${duration})` };\n }\n\n // --- Generic span ---\n return { line: `${name} (${duration})${span.status === 'error' ? ' ✗' : ''}` };\n}\n\n/**\n * Extract a meaningful input description for a tool call.\n * Parse a workspace path like /tmp/visor-workspaces/<session>/<repo>/path/to/file\n * into { repo, filePath } components.\n */\nfunction parseWorkspacePath(fullPath: string): { repo: string; filePath?: string } | null {\n // Match /tmp/visor-workspaces/<session-id>/<repo>/...\n const wsMatch = fullPath.match(/\\/visor-workspaces\\/[^/]+\\/([^/]+)(?:\\/(.+))?/);\n if (wsMatch) {\n return { repo: wsMatch[1], filePath: wsMatch[2] };\n }\n // Match .visor/worktrees/worktrees/<worktree-id>/<path>\n const wtMatch = fullPath.match(/\\.visor\\/worktrees\\/worktrees\\/[^/]+\\/(.+)/);\n if (wtMatch) {\n const segs = wtMatch[1].split('/');\n return { repo: segs[0], filePath: segs.length > 1 ? segs.slice(1).join('/') : undefined };\n }\n return null;\n}\n\n/**\n * Parses the Pattern/Path from tool.result for search tools,\n * and file paths for extract tools.\n */\nfunction extractToolInput(\n toolName: string,\n attrs: Record<string, string | number | boolean>\n): string {\n const result = String(attrs['tool.result'] || '');\n const explicitInput = String(attrs['tool.input'] || '');\n\n if (explicitInput) return truncate(explicitInput, 80);\n\n switch (toolName) {\n case 'search': {\n // Parse \"Pattern: ...\" and \"Path: ...\" from Probe search output\n const patMatch = result.match(/Pattern: (.+)/);\n const pathMatch = result.match(/Path: (\\S+)/);\n const pattern = patMatch ? patMatch[1].trim() : '';\n const workspace = pathMatch ? parseWorkspacePath(pathMatch[1]) : null;\n const parts: string[] = [];\n if (pattern) parts.push(`\"${truncate(pattern, 50)}\"`);\n if (workspace?.repo) parts.push(workspace.repo);\n return parts.join(', ');\n }\n case 'extract': {\n // Parse file paths from \"Files to extract:\" block\n // Full path looks like: /tmp/visor-workspaces/<session>/<repo>/path/to/file (lines N-M)\n const fileMatch = result.match(/Files to extract:\\n\\s*(\\S+)/);\n if (fileMatch) {\n const fullPath = fileMatch[1];\n const workspace = parseWorkspacePath(fullPath);\n if (workspace) {\n const parts: string[] = [];\n parts.push(workspace.filePath || workspace.repo || fullPath.split('/').pop() || '');\n if (workspace.repo) parts.push(workspace.repo);\n return parts.join(', ');\n }\n // Fallback: show last 2 segments\n const segs = fullPath.split('/');\n return segs.length > 2 ? segs.slice(-2).join('/') : segs[segs.length - 1];\n }\n return '';\n }\n case 'bash': {\n // tool.result starts with \"Command: <cmd>\\nWorking directory: ...\"\n const cmdMatch = result.match(/^Command: (.+)/);\n if (cmdMatch) {\n let cmd = cmdMatch[1].trim();\n // Strip long pipes — show first command + pipe count\n const pipes = cmd.split(/\\s*\\|\\s*/);\n if (pipes.length > 2) {\n cmd = `${pipes[0]} | ... (${pipes.length} stages)`;\n }\n return truncate(cmd, 80);\n }\n // Blocked commands: \"Permission denied: Component \"<cmd>\" not allowed: ...\"\n const deniedMatch = result.match(/^Permission denied: Component \"([^\"]+)\"/);\n if (deniedMatch) {\n return truncate(deniedMatch[1], 60) + ' [denied]';\n }\n return '';\n }\n case 'listFiles': {\n const pathMatch = result.match(/^(\\S+):/);\n if (pathMatch) {\n const parts = pathMatch[1].split('/');\n return parts[parts.length - 1] || '';\n }\n return '';\n }\n default:\n return truncate(explicitInput, 60);\n }\n}\n\n/**\n * Extract a short intent/question from an AI prompt.\n * Looks for ## Current Request, <question>, or the user message.\n */\nfunction extractAIIntent(input: string, maxLen: number = 150): string {\n if (!input || input.length < 20) return '';\n\n // Try <question>...</question>\n const qMatch = input.match(/<question>([\\s\\S]*?)<\\/question>/);\n if (qMatch) return truncate(qMatch[1].trim(), maxLen);\n\n // Try \"## Current Request\" section\n const crMatch = input.match(/## Current Request\\s*\\n(?:User: )?(.+)/);\n if (crMatch) return truncate(crMatch[1].trim(), maxLen);\n\n // Try \"User:\" pattern\n const userMatch = input.match(/(?:^|\\n)User: (.+)/);\n if (userMatch) return truncate(userMatch[1].trim(), maxLen);\n\n // Try \"Primary message\" pattern\n const pmMatch = input.match(/Primary message[^:]*:\\s*\\n(.+)/);\n if (pmMatch) return truncate(pmMatch[1].trim(), maxLen);\n\n return '';\n}\n\n/**\n * Format a JSON output as a compact structural preview.\n * Shows key names + brief values, preserving the JSON nature of the data.\n */\nfunction formatJsonPreview(obj: any, maxLen: number): string {\n if (obj === null || obj === undefined) return '';\n if (typeof obj !== 'object') return truncate(String(obj), maxLen);\n\n if (Array.isArray(obj)) {\n if (obj.length === 0) return '[]';\n // Show array length + first item preview\n const first =\n typeof obj[0] === 'object' && obj[0] !== null\n ? obj[0].project_id || obj[0].id || obj[0].name || Object.keys(obj[0])[0] || '...'\n : String(obj[0]);\n return `[${obj.length}] ${truncate(String(first), 30)}${obj.length > 1 ? ', ...' : ''}`;\n }\n\n // Object — show key:value pairs compactly\n const parts: string[] = [];\n let len = 2; // for { }\n for (const [key, val] of Object.entries(obj)) {\n // Skip internal/verbose keys\n if (key === 'raw' || key === 'skills' || key === 'tags') continue;\n let valStr: string;\n if (val === null || val === undefined) continue;\n if (typeof val === 'boolean') valStr = String(val);\n else if (typeof val === 'number') valStr = String(val);\n else if (typeof val === 'string') {\n // Skip text fields that are stringified JSON (duplicate data)\n if (val.startsWith('{') || val.startsWith('[')) continue;\n // Clean markdown from text values\n const clean = val\n .replace(/\\*\\*/g, '')\n .replace(/^#+\\s*/gm, '')\n .replace(/`/g, '')\n .trim();\n valStr = `\"${truncate(clean.split('\\n')[0], Math.min(80, maxLen / 3))}\"`;\n } else if (Array.isArray(val)) valStr = `[${val.length}]`;\n else if (typeof val === 'object') valStr = `{${Object.keys(val).length} keys}`;\n else valStr = '...';\n\n const part = `${key}: ${valStr}`;\n if (len + part.length + 2 > maxLen) {\n parts.push('...');\n break;\n }\n parts.push(part);\n len += part.length + 2;\n }\n\n return `{${parts.join(', ')}}`;\n}\n\n/**\n * Extract a short preview from a check's output JSON.\n * Shows structural JSON preview preserving the data format.\n */\nfunction extractOutputPreview(output: string, maxLen: number = 120): string {\n try {\n const obj = JSON.parse(output);\n return formatJsonPreview(obj, maxLen);\n } catch {\n // JSON truncated by telemetry — extract top-level keys with regex\n return extractTruncatedJsonPreview(output, maxLen);\n }\n}\n\n/**\n * Extract a structured preview from truncated JSON (telemetry cuts at ~2048 chars).\n * Extracts top-level key:value pairs using regex.\n */\n/**\n * Tolerant JSON parser that extracts as much structure as possible from\n * truncated JSON (e.g., OTEL attribute values cut off at ~2048 chars).\n * Returns a partial JS object/array/string — whatever was parseable.\n */\nfunction parseTruncatedJson(input: string): any {\n let pos = 0;\n const len = input.length;\n\n function skipWhitespace(): void {\n while (pos < len && ' \\t\\n\\r'.includes(input[pos])) pos++;\n }\n\n function parseString(): string {\n if (input[pos] !== '\"') return '';\n pos++; // skip opening quote\n let result = '';\n while (pos < len) {\n const ch = input[pos];\n if (ch === '\\\\' && pos + 1 < len) {\n const next = input[pos + 1];\n if (next === 'n') {\n result += '\\n';\n pos += 2;\n continue;\n }\n if (next === 't') {\n result += '\\t';\n pos += 2;\n continue;\n }\n if (next === '\"') {\n result += '\"';\n pos += 2;\n continue;\n }\n if (next === '\\\\') {\n result += '\\\\';\n pos += 2;\n continue;\n }\n result += next;\n pos += 2;\n continue;\n }\n if (ch === '\"') {\n pos++;\n return result;\n }\n result += ch;\n pos++;\n }\n // Truncated — no closing quote\n return result;\n }\n\n function parseNumber(): number {\n const start = pos;\n if (input[pos] === '-') pos++;\n while (pos < len && input[pos] >= '0' && input[pos] <= '9') pos++;\n if (pos < len && input[pos] === '.') {\n pos++;\n while (pos < len && input[pos] >= '0' && input[pos] <= '9') pos++;\n }\n return Number(input.slice(start, pos));\n }\n\n function parseValue(): any {\n skipWhitespace();\n if (pos >= len) return undefined;\n const ch = input[pos];\n if (ch === '\"') return parseString();\n if (ch === '{') return parseObject();\n if (ch === '[') return parseArray();\n if (ch === 't' && input.slice(pos, pos + 4) === 'true') {\n pos += 4;\n return true;\n }\n if (ch === 'f' && input.slice(pos, pos + 5) === 'false') {\n pos += 5;\n return false;\n }\n if (ch === 'n' && input.slice(pos, pos + 4) === 'null') {\n pos += 4;\n return null;\n }\n if (ch === '-' || (ch >= '0' && ch <= '9')) return parseNumber();\n return undefined; // truncated\n }\n\n function parseObject(): Record<string, any> {\n const obj: Record<string, any> = {};\n pos++; // skip {\n skipWhitespace();\n while (pos < len && input[pos] !== '}') {\n skipWhitespace();\n if (pos >= len || input[pos] !== '\"') break; // truncated\n const key = parseString();\n skipWhitespace();\n if (pos >= len || input[pos] !== ':') {\n obj[key] = undefined;\n break;\n }\n pos++; // skip :\n const val = parseValue();\n if (val !== undefined) obj[key] = val;\n skipWhitespace();\n if (pos < len && input[pos] === ',') pos++;\n }\n if (pos < len && input[pos] === '}') pos++;\n return obj;\n }\n\n function parseArray(): any[] {\n const arr: any[] = [];\n pos++; // skip [\n skipWhitespace();\n while (pos < len && input[pos] !== ']') {\n const val = parseValue();\n if (val !== undefined) arr.push(val);\n else break; // truncated\n skipWhitespace();\n if (pos < len && input[pos] === ',') pos++;\n skipWhitespace();\n }\n if (pos < len && input[pos] === ']') pos++;\n return arr;\n }\n\n return parseValue();\n}\n\nfunction extractTruncatedJsonPreview(output: string, maxLen: number): string {\n if (!output.startsWith('{') && !output.startsWith('[')) return '';\n\n const parsed = parseTruncatedJson(output);\n if (!parsed || typeof parsed !== 'object') return '';\n\n return formatJsonPreview(parsed, maxLen);\n}\n\n/**\n * Format check inputs from visor.check.input.outputs into a compact summary.\n * Shows which checks provided data: ← {route-intent: {intent, topic}, build-config: {5 keys}}\n */\nfunction formatInputPreview(inputOutputsStr: string, maxLen: number): string {\n if (!inputOutputsStr) return '';\n try {\n const inputs = JSON.parse(inputOutputsStr);\n if (typeof inputs !== 'object' || inputs === null) return '';\n const keys = Object.keys(inputs);\n if (keys.length === 0) return '';\n\n const parts: string[] = [];\n let len = 2;\n for (const key of keys) {\n const val = inputs[key];\n let valStr: string;\n if (typeof val === 'object' && val !== null) {\n const vkeys = Object.keys(val);\n if (vkeys.length <= 3) {\n valStr = `{${vkeys.join(', ')}}`;\n } else {\n valStr = `{${vkeys.slice(0, 2).join(', ')}, ...${vkeys.length} keys}`;\n }\n } else {\n valStr = truncate(String(val), 30);\n }\n const part = `${key}: ${valStr}`;\n if (len + part.length + 2 > maxLen) {\n parts.push('...');\n break;\n }\n parts.push(part);\n len += part.length + 2;\n }\n return `← {${parts.join(', ')}}`;\n } catch {\n return '';\n }\n}\n\n/**\n * Extract the route-intent topic from the span list.\n * Looks for the route-intent check output or classify check output.\n */\nfunction extractRouteIntentTopic(spans: NormalizedSpan[]): string | undefined {\n // Try route-intent check output first\n const riSpan = spans.find(s => s.attributes['visor.check.id'] === 'route-intent');\n if (riSpan) {\n const output = String(riSpan.attributes['visor.check.output'] || '');\n if (output) {\n try {\n const obj = JSON.parse(output);\n if (obj.topic) return truncate(String(obj.topic), 150);\n } catch {}\n }\n }\n // Try classify check output\n const classifySpan = spans.find(s => s.attributes['visor.check.id'] === 'classify');\n if (classifySpan) {\n const output = String(classifySpan.attributes['visor.check.output'] || '');\n if (output) {\n try {\n const obj = JSON.parse(output);\n if (obj.topic) return truncate(String(obj.topic), 150);\n } catch {}\n }\n }\n return undefined;\n}\n\nfunction formatSize(chars: number): string {\n if (chars < 1000) return `${chars} chars`;\n return `${(chars / 1000).toFixed(1)}k chars`;\n}\n\nfunction formatDurationMs(ms: number): string {\n if (ms < 0) return '0ms';\n if (ms < 1000) return `${Math.round(ms)}ms`;\n if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;\n const mins = Math.floor(ms / 60000);\n const secs = Math.round((ms % 60000) / 1000);\n return `${mins}m ${secs}s`;\n}\n\nfunction truncate(str: string, max: number): string {\n if (typeof str !== 'string') return '';\n if (str.length <= max) return str;\n // Show first and last portions with truncation marker in the middle\n const tail = Math.min(100, Math.floor(max / 3));\n const head = max - tail - 19; // 19 for \" ...[truncated]... \"\n if (head < 10) return str.slice(0, max - 3) + '...';\n return str.slice(0, head) + ' ...[truncated]... ' + str.slice(-tail);\n}\n","/**\n * Task Response Evaluator with LLM Judge + Trace Analysis.\n *\n * Evaluates completed agent tasks on two axes:\n * 1. Response quality (relevance, completeness, actionability)\n * 2. Execution quality (tool call efficiency, unnecessary delegations)\n *\n * Uses ProbeAgent in single-shot mode (same pattern as llm-judge.ts).\n */\n\nimport crypto from 'crypto';\nimport { logger } from '../logger';\nimport type { SqliteTaskStore } from './task-store';\nimport { serializeTraceForPrompt } from './trace-serializer';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface ResponseQuality {\n rating: number; // 1-5\n category: 'excellent' | 'good' | 'adequate' | 'poor' | 'off-topic' | 'error';\n relevance: boolean;\n completeness: boolean;\n actionable: boolean;\n reasoning: string;\n}\n\nexport interface ExecutionQuality {\n rating: number; // 1-5\n category: 'efficient' | 'adequate' | 'wasteful' | 'error';\n unnecessary_tool_calls?: number;\n reasoning: string;\n}\n\nexport interface TaskEvaluationResult {\n response_quality: ResponseQuality;\n execution_quality?: ExecutionQuality;\n overall_rating: number; // 1-5\n summary: string;\n trace_available?: boolean; // false when evaluation was done without execution trace\n}\n\nexport interface TaskEvaluatorConfig {\n model?: string;\n provider?: string;\n apiKey?: string;\n prompt?: string; // Override default evaluation system prompt\n traceDir?: string; // Where to find trace files (default: output/traces)\n}\n\n// ---------------------------------------------------------------------------\n// Default prompt\n// ---------------------------------------------------------------------------\n\nexport const DEFAULT_EVALUATION_PROMPT = `You are a task response quality evaluator for an AI agent system called Visor.\n\nYou will receive the user's original request and an execution trace inside <execution_trace> tags. The trace is a YAML-formatted view of the entire agent execution, including the final response. When no trace is available, the agent response is provided directly.\n\n## How to Read the Execution Trace\n\nThe trace is a tree of spans representing the agent's execution pipeline:\n\n**Top-level: \\`visor.run\\`** — The root span with metadata:\n- \\`trace_id\\`: Unique execution identifier\n- \\`visor\\` / \\`probe\\`: Software versions\n- \\`source\\`: Where the request came from (e.g., \"slack\", \"cli\")\n- \\`duration\\`: Total wall-clock time\n\n**Checks** — Named processing steps (e.g., \\`route-intent\\`, \\`explore-code\\`, \\`generate-response\\`):\n- \\`type\\`: \"ai\" (LLM-powered), \"script\" (deterministic), or \"workflow\" (sub-pipeline)\n- \\`duration\\`: How long this step took\n- \\`input\\`: What was passed to this check — may include an \\`intent\\` (the user's question as understood by the router) and dependency outputs\n- \\`output\\`: The check's result — may be structured JSON or plain text\n\n**AI blocks** (\\`ai: model-name\\`) — Individual LLM calls within checks:\n- Shows model used, duration, and token counts (input/output)\n- \\`intent\\`: The question or instruction sent to the LLM\n\n**Tool calls** — Listed as \\`- toolName(input) → size\\`:\n- \\`search(\"query\" in repo)\\`: Code search. \"→ no results\" means nothing was found; otherwise shows result size\n- \\`extract(file/path)\\`: File content extraction with result size\n- \\`listFiles(dir)\\`: Directory listing\n- \\`bash()\\`: Shell command execution\n\n**Delegations** (\\`search.delegate(\"query\")\\`) — Sub-agent searches:\n- Contains their own AI blocks and tool calls\n- Used for complex multi-step code exploration\n\n**The \\`response\\` field** at the end of the trace is the final answer sent back to the user. This is the primary output to evaluate.\n\n**Symbols:**\n- \\`✗\\` marks failed/error spans\n- \\`= check-name\\` means output is identical to that check's output (deduplication)\n\n## Evaluation Criteria\n\n**Response Quality** (1-5):\n- **Relevance**: Does the response directly address what the user asked? A response about the wrong topic or that misunderstands the question scores low.\n- **Completeness**: Does it fully answer the question? Partial answers, missing key details, or surface-level responses score lower.\n- **Actionable**: Can the user act on this information? Vague or generic advice scores lower than specific, concrete answers with code references.\n- Rating: 5=excellent (thorough, specific, directly useful), 4=good (answers well but minor gaps), 3=adequate (addresses question but lacks depth), 2=poor (partially relevant or very incomplete), 1=off-topic or error\n\n**Execution Quality** (1-5, only when trace is provided):\n- **Efficiency**: Were tool calls necessary and well-targeted? Good search queries that find results on the first try score high.\n- **Redundancy**: Were there duplicate searches, unnecessary re-searches with slightly different queries, or tools called for information already available?\n- **Extract-then-search anti-pattern**: If a file was already extracted (e.g., \\`extract(docs/config.mdx) → 3.3k chars\\`), then a subsequent \\`search(\"term\" in config.mdx)\\` is redundant — the agent already has the file content and should parse it from context instead of making another tool call. Flag every instance of this pattern.\n- **Search-reformulation waste**: If a search returns \"no results\" and the agent immediately retries with a minor query variation (e.g., \\`\"audit store_type\"\\` → \\`\"audit \"store_type\"\"\\` → \\`\"store_type\"\\`), that's usually wasteful. A single well-crafted query should suffice; reformulating 3+ times for the same concept is a red flag.\n- **Delegation quality**: Were search delegations productive? Did they explore relevant code paths?\n- **Token usage**: Was input context kept reasonable, or did the agent load excessive amounts of code?\n- Rating: 5=efficient (minimal, targeted tool use), 4=adequate (minor redundancy), 3=some waste (noticeable unnecessary calls), 2=wasteful (many redundant searches or delegations), 1=error/broken execution\n\n**Overall Rating** (1-5): Weighted combination — response quality matters most, execution quality is secondary. A perfect response from a wasteful execution still scores 3-4 overall.\n\nYou MUST respond with valid JSON matching the provided schema. Be specific in your reasoning — reference actual check names, tool calls, or response content.`;\n\n// ---------------------------------------------------------------------------\n// JSON Schema\n// ---------------------------------------------------------------------------\n\nfunction buildEvaluationSchema(includeExecution: boolean): Record<string, unknown> {\n const schema: Record<string, unknown> = {\n type: 'object',\n required: ['response_quality', 'overall_rating', 'summary'],\n properties: {\n response_quality: {\n type: 'object',\n required: ['rating', 'category', 'relevance', 'completeness', 'actionable', 'reasoning'],\n properties: {\n rating: { type: 'integer', minimum: 1, maximum: 5 },\n category: {\n type: 'string',\n enum: ['excellent', 'good', 'adequate', 'poor', 'off-topic', 'error'],\n },\n relevance: { type: 'boolean' },\n completeness: { type: 'boolean' },\n actionable: { type: 'boolean' },\n reasoning: { type: 'string' },\n },\n },\n overall_rating: { type: 'integer', minimum: 1, maximum: 5 },\n summary: { type: 'string' },\n },\n };\n\n if (includeExecution) {\n (schema.required as string[]).push('execution_quality');\n (schema.properties as Record<string, unknown>).execution_quality = {\n type: 'object',\n required: ['rating', 'category', 'reasoning'],\n properties: {\n rating: { type: 'integer', minimum: 1, maximum: 5 },\n category: { type: 'string', enum: ['efficient', 'adequate', 'wasteful', 'error'] },\n unnecessary_tool_calls: { type: 'integer' },\n reasoning: { type: 'string' },\n },\n };\n }\n\n return schema;\n}\n\n// ---------------------------------------------------------------------------\n// Core evaluation\n// ---------------------------------------------------------------------------\n\n/**\n * Evaluate a completed task using an LLM judge.\n *\n * @param taskId - Task ID (full or prefix)\n * @param store - Initialized SqliteTaskStore\n * @param config - Optional evaluator configuration\n * @returns Evaluation result with ratings and reasoning\n */\nexport async function evaluateTask(\n taskId: string,\n store: SqliteTaskStore,\n config?: TaskEvaluatorConfig\n): Promise<TaskEvaluationResult> {\n // 1. Load task data\n const { rows } = store.listTasksRaw({ limit: 500 });\n const match = rows.find(r => r.id === taskId || r.id.startsWith(taskId));\n if (!match) {\n throw new Error(`Task not found: ${taskId}`);\n }\n\n const fullTask = store.getTask(match.id);\n if (!fullTask) {\n throw new Error(`Task data not found: ${match.id}`);\n }\n\n // 2. Extract request and response text\n const requestText = match.request_message || 'No request text available';\n\n let responseText = 'No response available';\n if (fullTask.status?.message) {\n const parts = fullTask.status.message.parts ?? [];\n const textPart = parts.find((p: any) => typeof p.text === 'string');\n if (textPart) {\n responseText = (textPart as any).text;\n }\n }\n\n // 3. Shortcut for failed tasks with no real response\n if (fullTask.status.state === 'failed' && responseText === 'No response available') {\n return {\n response_quality: {\n rating: 1,\n category: 'error',\n relevance: false,\n completeness: false,\n actionable: false,\n reasoning: 'Task failed without producing a response.',\n },\n overall_rating: 1,\n summary: 'Task failed without producing a response.',\n };\n }\n\n // 4. Try to find and serialize execution trace (full mode, with task response)\n // Supports Grafana Tempo, Jaeger, and local NDJSON files (auto-detected)\n let traceTree: string | undefined;\n const traceId = match.metadata?.trace_id as string | undefined;\n const traceFile = match.metadata?.trace_file as string | undefined;\n\n if (traceFile || traceId) {\n try {\n const traceRef = traceFile || traceId!;\n // Use full mode (1M chars) so trace is not truncated, include task response\n traceTree = await serializeTraceForPrompt(\n traceRef,\n 1_000_000,\n { traceDir: config?.traceDir },\n responseText !== 'No response available' ? responseText : undefined,\n traceId\n );\n if (traceTree === '(no trace data available)') {\n traceTree = undefined;\n }\n } catch (err) {\n logger.debug(\n `[TaskEvaluator] Failed to load trace: ${err instanceof Error ? err.message : err}`\n );\n }\n }\n\n // 5. Build prompts\n const systemPrompt = config?.prompt || process.env.VISOR_EVAL_PROMPT || DEFAULT_EVALUATION_PROMPT;\n const hasTrace = !!traceTree;\n\n let userPrompt: string;\n if (traceTree) {\n // Trace includes the full execution + response — no need to duplicate\n userPrompt = `<user_request>\\n${requestText}\\n</user_request>\\n\\n<execution_trace>\\n${traceTree}\\n</execution_trace>`;\n } else {\n // No trace available — provide request and response directly\n userPrompt = `<user_request>\\n${requestText}\\n</user_request>\\n\\n<agent_response>\\n${responseText}\\n</agent_response>`;\n }\n\n // 6. Call LLM via ProbeAgent\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { ProbeAgent } = require('@probelabs/probe');\n\n const model =\n config?.model || process.env.VISOR_EVAL_MODEL || process.env.VISOR_JUDGE_MODEL || undefined;\n const provider = config?.provider || process.env.VISOR_EVAL_PROVIDER || undefined;\n\n const agentOptions: Record<string, unknown> = {\n sessionId: `visor-task-eval-${Date.now()}`,\n systemPrompt,\n maxIterations: 1,\n disableTools: true,\n };\n if (model) agentOptions.model = model;\n if (provider) agentOptions.provider = provider;\n if (config?.apiKey) {\n const envKey =\n provider === 'openai'\n ? 'OPENAI_API_KEY'\n : provider === 'anthropic'\n ? 'ANTHROPIC_API_KEY'\n : 'GOOGLE_API_KEY';\n process.env[envKey] = config.apiKey;\n }\n\n const agent = new ProbeAgent(agentOptions);\n if (typeof agent.initialize === 'function') {\n await agent.initialize();\n }\n\n const jsonSchema = buildEvaluationSchema(hasTrace);\n const schemaStr = JSON.stringify(jsonSchema);\n const response = await agent.answer(userPrompt, undefined, { schema: schemaStr });\n\n // 7. Parse JSON response (same pattern as llm-judge.ts:181-196)\n let result: TaskEvaluationResult;\n try {\n const cleaned = response\n .replace(/^```(?:json)?\\s*\\n?/m, '')\n .replace(/\\n?```\\s*$/m, '')\n .trim();\n result = JSON.parse(cleaned);\n } catch {\n const jsonMatch = response.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n result = JSON.parse(jsonMatch[0]);\n } else {\n throw new Error(`Failed to parse evaluation response as JSON: ${response.slice(0, 200)}`);\n }\n }\n\n // 8. When no trace was available, cap overall rating and flag the evaluation\n // as incomplete — response text alone can't verify execution quality.\n if (!hasTrace) {\n const MAX_RATING_WITHOUT_TRACE = 4;\n if (result.overall_rating > MAX_RATING_WITHOUT_TRACE) {\n result.overall_rating = MAX_RATING_WITHOUT_TRACE;\n }\n result.trace_available = false;\n result.summary = `[No trace available — execution quality not assessed, rating capped at ${MAX_RATING_WITHOUT_TRACE}/5] ${result.summary}`;\n } else {\n result.trace_available = true;\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Store evaluation as artifact\n// ---------------------------------------------------------------------------\n\n/**\n * Run evaluation and store result as a task artifact.\n */\nexport async function evaluateAndStore(\n taskId: string,\n store: SqliteTaskStore,\n config?: TaskEvaluatorConfig\n): Promise<TaskEvaluationResult> {\n const result = await evaluateTask(taskId, store, config);\n\n // Resolve full task ID\n const { rows } = store.listTasksRaw({ limit: 500 });\n const match = rows.find(r => r.id === taskId || r.id.startsWith(taskId));\n if (match) {\n store.addArtifact(match.id, {\n artifact_id: crypto.randomUUID(),\n name: 'evaluation',\n parts: [{ text: JSON.stringify(result), media_type: 'application/json' }],\n });\n }\n\n return result;\n}\n"],"mappings":"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","names":["hasChildren","errMark"]}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import {
|
|
2
|
+
init_trace_serializer,
|
|
3
|
+
serializeTraceForPrompt
|
|
4
|
+
} from "./chunk-6C3R6E42.mjs";
|
|
5
|
+
import {
|
|
6
|
+
init_logger,
|
|
7
|
+
logger
|
|
8
|
+
} from "./chunk-ZPYODGYA.mjs";
|
|
9
|
+
import "./chunk-B2OUZAWY.mjs";
|
|
10
|
+
import {
|
|
11
|
+
__esm,
|
|
12
|
+
__require
|
|
13
|
+
} from "./chunk-J7LXIPZS.mjs";
|
|
14
|
+
|
|
15
|
+
// src/agent-protocol/task-evaluator.ts
|
|
16
|
+
import crypto from "crypto";
|
|
17
|
+
function buildEvaluationSchema(includeExecution) {
|
|
18
|
+
const schema = {
|
|
19
|
+
type: "object",
|
|
20
|
+
required: ["response_quality", "overall_rating", "summary"],
|
|
21
|
+
properties: {
|
|
22
|
+
response_quality: {
|
|
23
|
+
type: "object",
|
|
24
|
+
required: ["rating", "category", "relevance", "completeness", "actionable", "reasoning"],
|
|
25
|
+
properties: {
|
|
26
|
+
rating: { type: "integer", minimum: 1, maximum: 5 },
|
|
27
|
+
category: {
|
|
28
|
+
type: "string",
|
|
29
|
+
enum: ["excellent", "good", "adequate", "poor", "off-topic", "error"]
|
|
30
|
+
},
|
|
31
|
+
relevance: { type: "boolean" },
|
|
32
|
+
completeness: { type: "boolean" },
|
|
33
|
+
actionable: { type: "boolean" },
|
|
34
|
+
reasoning: { type: "string" }
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
overall_rating: { type: "integer", minimum: 1, maximum: 5 },
|
|
38
|
+
summary: { type: "string" }
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
if (includeExecution) {
|
|
42
|
+
schema.required.push("execution_quality");
|
|
43
|
+
schema.properties.execution_quality = {
|
|
44
|
+
type: "object",
|
|
45
|
+
required: ["rating", "category", "reasoning"],
|
|
46
|
+
properties: {
|
|
47
|
+
rating: { type: "integer", minimum: 1, maximum: 5 },
|
|
48
|
+
category: { type: "string", enum: ["efficient", "adequate", "wasteful", "error"] },
|
|
49
|
+
unnecessary_tool_calls: { type: "integer" },
|
|
50
|
+
reasoning: { type: "string" }
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return schema;
|
|
55
|
+
}
|
|
56
|
+
async function evaluateTask(taskId, store, config) {
|
|
57
|
+
const { rows } = store.listTasksRaw({ limit: 500 });
|
|
58
|
+
const match = rows.find((r) => r.id === taskId || r.id.startsWith(taskId));
|
|
59
|
+
if (!match) {
|
|
60
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
61
|
+
}
|
|
62
|
+
const fullTask = store.getTask(match.id);
|
|
63
|
+
if (!fullTask) {
|
|
64
|
+
throw new Error(`Task data not found: ${match.id}`);
|
|
65
|
+
}
|
|
66
|
+
const requestText = match.request_message || "No request text available";
|
|
67
|
+
let responseText = "No response available";
|
|
68
|
+
if (fullTask.status?.message) {
|
|
69
|
+
const parts = fullTask.status.message.parts ?? [];
|
|
70
|
+
const textPart = parts.find((p) => typeof p.text === "string");
|
|
71
|
+
if (textPart) {
|
|
72
|
+
responseText = textPart.text;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (fullTask.status.state === "failed" && responseText === "No response available") {
|
|
76
|
+
return {
|
|
77
|
+
response_quality: {
|
|
78
|
+
rating: 1,
|
|
79
|
+
category: "error",
|
|
80
|
+
relevance: false,
|
|
81
|
+
completeness: false,
|
|
82
|
+
actionable: false,
|
|
83
|
+
reasoning: "Task failed without producing a response."
|
|
84
|
+
},
|
|
85
|
+
overall_rating: 1,
|
|
86
|
+
summary: "Task failed without producing a response."
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
let traceTree;
|
|
90
|
+
const traceId = match.metadata?.trace_id;
|
|
91
|
+
const traceFile = match.metadata?.trace_file;
|
|
92
|
+
if (traceFile || traceId) {
|
|
93
|
+
try {
|
|
94
|
+
const traceRef = traceFile || traceId;
|
|
95
|
+
traceTree = await serializeTraceForPrompt(
|
|
96
|
+
traceRef,
|
|
97
|
+
1e6,
|
|
98
|
+
{ traceDir: config?.traceDir },
|
|
99
|
+
responseText !== "No response available" ? responseText : void 0,
|
|
100
|
+
traceId
|
|
101
|
+
);
|
|
102
|
+
if (traceTree === "(no trace data available)") {
|
|
103
|
+
traceTree = void 0;
|
|
104
|
+
}
|
|
105
|
+
} catch (err) {
|
|
106
|
+
logger.debug(
|
|
107
|
+
`[TaskEvaluator] Failed to load trace: ${err instanceof Error ? err.message : err}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const systemPrompt = config?.prompt || process.env.VISOR_EVAL_PROMPT || DEFAULT_EVALUATION_PROMPT;
|
|
112
|
+
const hasTrace = !!traceTree;
|
|
113
|
+
let userPrompt;
|
|
114
|
+
if (traceTree) {
|
|
115
|
+
userPrompt = `<user_request>
|
|
116
|
+
${requestText}
|
|
117
|
+
</user_request>
|
|
118
|
+
|
|
119
|
+
<execution_trace>
|
|
120
|
+
${traceTree}
|
|
121
|
+
</execution_trace>`;
|
|
122
|
+
} else {
|
|
123
|
+
userPrompt = `<user_request>
|
|
124
|
+
${requestText}
|
|
125
|
+
</user_request>
|
|
126
|
+
|
|
127
|
+
<agent_response>
|
|
128
|
+
${responseText}
|
|
129
|
+
</agent_response>`;
|
|
130
|
+
}
|
|
131
|
+
const { ProbeAgent } = __require("@probelabs/probe");
|
|
132
|
+
const model = config?.model || process.env.VISOR_EVAL_MODEL || process.env.VISOR_JUDGE_MODEL || void 0;
|
|
133
|
+
const provider = config?.provider || process.env.VISOR_EVAL_PROVIDER || void 0;
|
|
134
|
+
const agentOptions = {
|
|
135
|
+
sessionId: `visor-task-eval-${Date.now()}`,
|
|
136
|
+
systemPrompt,
|
|
137
|
+
maxIterations: 1,
|
|
138
|
+
disableTools: true
|
|
139
|
+
};
|
|
140
|
+
if (model) agentOptions.model = model;
|
|
141
|
+
if (provider) agentOptions.provider = provider;
|
|
142
|
+
if (config?.apiKey) {
|
|
143
|
+
const envKey = provider === "openai" ? "OPENAI_API_KEY" : provider === "anthropic" ? "ANTHROPIC_API_KEY" : "GOOGLE_API_KEY";
|
|
144
|
+
process.env[envKey] = config.apiKey;
|
|
145
|
+
}
|
|
146
|
+
const agent = new ProbeAgent(agentOptions);
|
|
147
|
+
if (typeof agent.initialize === "function") {
|
|
148
|
+
await agent.initialize();
|
|
149
|
+
}
|
|
150
|
+
const jsonSchema = buildEvaluationSchema(hasTrace);
|
|
151
|
+
const schemaStr = JSON.stringify(jsonSchema);
|
|
152
|
+
const response = await agent.answer(userPrompt, void 0, { schema: schemaStr });
|
|
153
|
+
let result;
|
|
154
|
+
try {
|
|
155
|
+
const cleaned = response.replace(/^```(?:json)?\s*\n?/m, "").replace(/\n?```\s*$/m, "").trim();
|
|
156
|
+
result = JSON.parse(cleaned);
|
|
157
|
+
} catch {
|
|
158
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
159
|
+
if (jsonMatch) {
|
|
160
|
+
result = JSON.parse(jsonMatch[0]);
|
|
161
|
+
} else {
|
|
162
|
+
throw new Error(`Failed to parse evaluation response as JSON: ${response.slice(0, 200)}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (!hasTrace) {
|
|
166
|
+
const MAX_RATING_WITHOUT_TRACE = 4;
|
|
167
|
+
if (result.overall_rating > MAX_RATING_WITHOUT_TRACE) {
|
|
168
|
+
result.overall_rating = MAX_RATING_WITHOUT_TRACE;
|
|
169
|
+
}
|
|
170
|
+
result.trace_available = false;
|
|
171
|
+
result.summary = `[No trace available \u2014 execution quality not assessed, rating capped at ${MAX_RATING_WITHOUT_TRACE}/5] ${result.summary}`;
|
|
172
|
+
} else {
|
|
173
|
+
result.trace_available = true;
|
|
174
|
+
}
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
async function evaluateAndStore(taskId, store, config) {
|
|
178
|
+
const result = await evaluateTask(taskId, store, config);
|
|
179
|
+
const { rows } = store.listTasksRaw({ limit: 500 });
|
|
180
|
+
const match = rows.find((r) => r.id === taskId || r.id.startsWith(taskId));
|
|
181
|
+
if (match) {
|
|
182
|
+
store.addArtifact(match.id, {
|
|
183
|
+
artifact_id: crypto.randomUUID(),
|
|
184
|
+
name: "evaluation",
|
|
185
|
+
parts: [{ text: JSON.stringify(result), media_type: "application/json" }]
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
var DEFAULT_EVALUATION_PROMPT;
|
|
191
|
+
var init_task_evaluator = __esm({
|
|
192
|
+
"src/agent-protocol/task-evaluator.ts"() {
|
|
193
|
+
init_logger();
|
|
194
|
+
init_trace_serializer();
|
|
195
|
+
DEFAULT_EVALUATION_PROMPT = `You are a task response quality evaluator for an AI agent system called Visor.
|
|
196
|
+
|
|
197
|
+
You will receive the user's original request and an execution trace inside <execution_trace> tags. The trace is a YAML-formatted view of the entire agent execution, including the final response. When no trace is available, the agent response is provided directly.
|
|
198
|
+
|
|
199
|
+
## How to Read the Execution Trace
|
|
200
|
+
|
|
201
|
+
The trace is a tree of spans representing the agent's execution pipeline:
|
|
202
|
+
|
|
203
|
+
**Top-level: \`visor.run\`** \u2014 The root span with metadata:
|
|
204
|
+
- \`trace_id\`: Unique execution identifier
|
|
205
|
+
- \`visor\` / \`probe\`: Software versions
|
|
206
|
+
- \`source\`: Where the request came from (e.g., "slack", "cli")
|
|
207
|
+
- \`duration\`: Total wall-clock time
|
|
208
|
+
|
|
209
|
+
**Checks** \u2014 Named processing steps (e.g., \`route-intent\`, \`explore-code\`, \`generate-response\`):
|
|
210
|
+
- \`type\`: "ai" (LLM-powered), "script" (deterministic), or "workflow" (sub-pipeline)
|
|
211
|
+
- \`duration\`: How long this step took
|
|
212
|
+
- \`input\`: What was passed to this check \u2014 may include an \`intent\` (the user's question as understood by the router) and dependency outputs
|
|
213
|
+
- \`output\`: The check's result \u2014 may be structured JSON or plain text
|
|
214
|
+
|
|
215
|
+
**AI blocks** (\`ai: model-name\`) \u2014 Individual LLM calls within checks:
|
|
216
|
+
- Shows model used, duration, and token counts (input/output)
|
|
217
|
+
- \`intent\`: The question or instruction sent to the LLM
|
|
218
|
+
|
|
219
|
+
**Tool calls** \u2014 Listed as \`- toolName(input) \u2192 size\`:
|
|
220
|
+
- \`search("query" in repo)\`: Code search. "\u2192 no results" means nothing was found; otherwise shows result size
|
|
221
|
+
- \`extract(file/path)\`: File content extraction with result size
|
|
222
|
+
- \`listFiles(dir)\`: Directory listing
|
|
223
|
+
- \`bash()\`: Shell command execution
|
|
224
|
+
|
|
225
|
+
**Delegations** (\`search.delegate("query")\`) \u2014 Sub-agent searches:
|
|
226
|
+
- Contains their own AI blocks and tool calls
|
|
227
|
+
- Used for complex multi-step code exploration
|
|
228
|
+
|
|
229
|
+
**The \`response\` field** at the end of the trace is the final answer sent back to the user. This is the primary output to evaluate.
|
|
230
|
+
|
|
231
|
+
**Symbols:**
|
|
232
|
+
- \`\u2717\` marks failed/error spans
|
|
233
|
+
- \`= check-name\` means output is identical to that check's output (deduplication)
|
|
234
|
+
|
|
235
|
+
## Evaluation Criteria
|
|
236
|
+
|
|
237
|
+
**Response Quality** (1-5):
|
|
238
|
+
- **Relevance**: Does the response directly address what the user asked? A response about the wrong topic or that misunderstands the question scores low.
|
|
239
|
+
- **Completeness**: Does it fully answer the question? Partial answers, missing key details, or surface-level responses score lower.
|
|
240
|
+
- **Actionable**: Can the user act on this information? Vague or generic advice scores lower than specific, concrete answers with code references.
|
|
241
|
+
- Rating: 5=excellent (thorough, specific, directly useful), 4=good (answers well but minor gaps), 3=adequate (addresses question but lacks depth), 2=poor (partially relevant or very incomplete), 1=off-topic or error
|
|
242
|
+
|
|
243
|
+
**Execution Quality** (1-5, only when trace is provided):
|
|
244
|
+
- **Efficiency**: Were tool calls necessary and well-targeted? Good search queries that find results on the first try score high.
|
|
245
|
+
- **Redundancy**: Were there duplicate searches, unnecessary re-searches with slightly different queries, or tools called for information already available?
|
|
246
|
+
- **Extract-then-search anti-pattern**: If a file was already extracted (e.g., \`extract(docs/config.mdx) \u2192 3.3k chars\`), then a subsequent \`search("term" in config.mdx)\` is redundant \u2014 the agent already has the file content and should parse it from context instead of making another tool call. Flag every instance of this pattern.
|
|
247
|
+
- **Search-reformulation waste**: If a search returns "no results" and the agent immediately retries with a minor query variation (e.g., \`"audit store_type"\` \u2192 \`"audit "store_type""\` \u2192 \`"store_type"\`), that's usually wasteful. A single well-crafted query should suffice; reformulating 3+ times for the same concept is a red flag.
|
|
248
|
+
- **Delegation quality**: Were search delegations productive? Did they explore relevant code paths?
|
|
249
|
+
- **Token usage**: Was input context kept reasonable, or did the agent load excessive amounts of code?
|
|
250
|
+
- Rating: 5=efficient (minimal, targeted tool use), 4=adequate (minor redundancy), 3=some waste (noticeable unnecessary calls), 2=wasteful (many redundant searches or delegations), 1=error/broken execution
|
|
251
|
+
|
|
252
|
+
**Overall Rating** (1-5): Weighted combination \u2014 response quality matters most, execution quality is secondary. A perfect response from a wasteful execution still scores 3-4 overall.
|
|
253
|
+
|
|
254
|
+
You MUST respond with valid JSON matching the provided schema. Be specific in your reasoning \u2014 reference actual check names, tool calls, or response content.`;
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
init_task_evaluator();
|
|
258
|
+
export {
|
|
259
|
+
DEFAULT_EVALUATION_PROMPT,
|
|
260
|
+
evaluateAndStore,
|
|
261
|
+
evaluateTask
|
|
262
|
+
};
|
|
263
|
+
//# sourceMappingURL=task-evaluator-OVMG7S56.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/agent-protocol/task-evaluator.ts"],"sourcesContent":["/**\n * Task Response Evaluator with LLM Judge + Trace Analysis.\n *\n * Evaluates completed agent tasks on two axes:\n * 1. Response quality (relevance, completeness, actionability)\n * 2. Execution quality (tool call efficiency, unnecessary delegations)\n *\n * Uses ProbeAgent in single-shot mode (same pattern as llm-judge.ts).\n */\n\nimport crypto from 'crypto';\nimport { logger } from '../logger';\nimport type { SqliteTaskStore } from './task-store';\nimport { serializeTraceForPrompt } from './trace-serializer';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface ResponseQuality {\n rating: number; // 1-5\n category: 'excellent' | 'good' | 'adequate' | 'poor' | 'off-topic' | 'error';\n relevance: boolean;\n completeness: boolean;\n actionable: boolean;\n reasoning: string;\n}\n\nexport interface ExecutionQuality {\n rating: number; // 1-5\n category: 'efficient' | 'adequate' | 'wasteful' | 'error';\n unnecessary_tool_calls?: number;\n reasoning: string;\n}\n\nexport interface TaskEvaluationResult {\n response_quality: ResponseQuality;\n execution_quality?: ExecutionQuality;\n overall_rating: number; // 1-5\n summary: string;\n trace_available?: boolean; // false when evaluation was done without execution trace\n}\n\nexport interface TaskEvaluatorConfig {\n model?: string;\n provider?: string;\n apiKey?: string;\n prompt?: string; // Override default evaluation system prompt\n traceDir?: string; // Where to find trace files (default: output/traces)\n}\n\n// ---------------------------------------------------------------------------\n// Default prompt\n// ---------------------------------------------------------------------------\n\nexport const DEFAULT_EVALUATION_PROMPT = `You are a task response quality evaluator for an AI agent system called Visor.\n\nYou will receive the user's original request and an execution trace inside <execution_trace> tags. The trace is a YAML-formatted view of the entire agent execution, including the final response. When no trace is available, the agent response is provided directly.\n\n## How to Read the Execution Trace\n\nThe trace is a tree of spans representing the agent's execution pipeline:\n\n**Top-level: \\`visor.run\\`** — The root span with metadata:\n- \\`trace_id\\`: Unique execution identifier\n- \\`visor\\` / \\`probe\\`: Software versions\n- \\`source\\`: Where the request came from (e.g., \"slack\", \"cli\")\n- \\`duration\\`: Total wall-clock time\n\n**Checks** — Named processing steps (e.g., \\`route-intent\\`, \\`explore-code\\`, \\`generate-response\\`):\n- \\`type\\`: \"ai\" (LLM-powered), \"script\" (deterministic), or \"workflow\" (sub-pipeline)\n- \\`duration\\`: How long this step took\n- \\`input\\`: What was passed to this check — may include an \\`intent\\` (the user's question as understood by the router) and dependency outputs\n- \\`output\\`: The check's result — may be structured JSON or plain text\n\n**AI blocks** (\\`ai: model-name\\`) — Individual LLM calls within checks:\n- Shows model used, duration, and token counts (input/output)\n- \\`intent\\`: The question or instruction sent to the LLM\n\n**Tool calls** — Listed as \\`- toolName(input) → size\\`:\n- \\`search(\"query\" in repo)\\`: Code search. \"→ no results\" means nothing was found; otherwise shows result size\n- \\`extract(file/path)\\`: File content extraction with result size\n- \\`listFiles(dir)\\`: Directory listing\n- \\`bash()\\`: Shell command execution\n\n**Delegations** (\\`search.delegate(\"query\")\\`) — Sub-agent searches:\n- Contains their own AI blocks and tool calls\n- Used for complex multi-step code exploration\n\n**The \\`response\\` field** at the end of the trace is the final answer sent back to the user. This is the primary output to evaluate.\n\n**Symbols:**\n- \\`✗\\` marks failed/error spans\n- \\`= check-name\\` means output is identical to that check's output (deduplication)\n\n## Evaluation Criteria\n\n**Response Quality** (1-5):\n- **Relevance**: Does the response directly address what the user asked? A response about the wrong topic or that misunderstands the question scores low.\n- **Completeness**: Does it fully answer the question? Partial answers, missing key details, or surface-level responses score lower.\n- **Actionable**: Can the user act on this information? Vague or generic advice scores lower than specific, concrete answers with code references.\n- Rating: 5=excellent (thorough, specific, directly useful), 4=good (answers well but minor gaps), 3=adequate (addresses question but lacks depth), 2=poor (partially relevant or very incomplete), 1=off-topic or error\n\n**Execution Quality** (1-5, only when trace is provided):\n- **Efficiency**: Were tool calls necessary and well-targeted? Good search queries that find results on the first try score high.\n- **Redundancy**: Were there duplicate searches, unnecessary re-searches with slightly different queries, or tools called for information already available?\n- **Extract-then-search anti-pattern**: If a file was already extracted (e.g., \\`extract(docs/config.mdx) → 3.3k chars\\`), then a subsequent \\`search(\"term\" in config.mdx)\\` is redundant — the agent already has the file content and should parse it from context instead of making another tool call. Flag every instance of this pattern.\n- **Search-reformulation waste**: If a search returns \"no results\" and the agent immediately retries with a minor query variation (e.g., \\`\"audit store_type\"\\` → \\`\"audit \"store_type\"\"\\` → \\`\"store_type\"\\`), that's usually wasteful. A single well-crafted query should suffice; reformulating 3+ times for the same concept is a red flag.\n- **Delegation quality**: Were search delegations productive? Did they explore relevant code paths?\n- **Token usage**: Was input context kept reasonable, or did the agent load excessive amounts of code?\n- Rating: 5=efficient (minimal, targeted tool use), 4=adequate (minor redundancy), 3=some waste (noticeable unnecessary calls), 2=wasteful (many redundant searches or delegations), 1=error/broken execution\n\n**Overall Rating** (1-5): Weighted combination — response quality matters most, execution quality is secondary. A perfect response from a wasteful execution still scores 3-4 overall.\n\nYou MUST respond with valid JSON matching the provided schema. Be specific in your reasoning — reference actual check names, tool calls, or response content.`;\n\n// ---------------------------------------------------------------------------\n// JSON Schema\n// ---------------------------------------------------------------------------\n\nfunction buildEvaluationSchema(includeExecution: boolean): Record<string, unknown> {\n const schema: Record<string, unknown> = {\n type: 'object',\n required: ['response_quality', 'overall_rating', 'summary'],\n properties: {\n response_quality: {\n type: 'object',\n required: ['rating', 'category', 'relevance', 'completeness', 'actionable', 'reasoning'],\n properties: {\n rating: { type: 'integer', minimum: 1, maximum: 5 },\n category: {\n type: 'string',\n enum: ['excellent', 'good', 'adequate', 'poor', 'off-topic', 'error'],\n },\n relevance: { type: 'boolean' },\n completeness: { type: 'boolean' },\n actionable: { type: 'boolean' },\n reasoning: { type: 'string' },\n },\n },\n overall_rating: { type: 'integer', minimum: 1, maximum: 5 },\n summary: { type: 'string' },\n },\n };\n\n if (includeExecution) {\n (schema.required as string[]).push('execution_quality');\n (schema.properties as Record<string, unknown>).execution_quality = {\n type: 'object',\n required: ['rating', 'category', 'reasoning'],\n properties: {\n rating: { type: 'integer', minimum: 1, maximum: 5 },\n category: { type: 'string', enum: ['efficient', 'adequate', 'wasteful', 'error'] },\n unnecessary_tool_calls: { type: 'integer' },\n reasoning: { type: 'string' },\n },\n };\n }\n\n return schema;\n}\n\n// ---------------------------------------------------------------------------\n// Core evaluation\n// ---------------------------------------------------------------------------\n\n/**\n * Evaluate a completed task using an LLM judge.\n *\n * @param taskId - Task ID (full or prefix)\n * @param store - Initialized SqliteTaskStore\n * @param config - Optional evaluator configuration\n * @returns Evaluation result with ratings and reasoning\n */\nexport async function evaluateTask(\n taskId: string,\n store: SqliteTaskStore,\n config?: TaskEvaluatorConfig\n): Promise<TaskEvaluationResult> {\n // 1. Load task data\n const { rows } = store.listTasksRaw({ limit: 500 });\n const match = rows.find(r => r.id === taskId || r.id.startsWith(taskId));\n if (!match) {\n throw new Error(`Task not found: ${taskId}`);\n }\n\n const fullTask = store.getTask(match.id);\n if (!fullTask) {\n throw new Error(`Task data not found: ${match.id}`);\n }\n\n // 2. Extract request and response text\n const requestText = match.request_message || 'No request text available';\n\n let responseText = 'No response available';\n if (fullTask.status?.message) {\n const parts = fullTask.status.message.parts ?? [];\n const textPart = parts.find((p: any) => typeof p.text === 'string');\n if (textPart) {\n responseText = (textPart as any).text;\n }\n }\n\n // 3. Shortcut for failed tasks with no real response\n if (fullTask.status.state === 'failed' && responseText === 'No response available') {\n return {\n response_quality: {\n rating: 1,\n category: 'error',\n relevance: false,\n completeness: false,\n actionable: false,\n reasoning: 'Task failed without producing a response.',\n },\n overall_rating: 1,\n summary: 'Task failed without producing a response.',\n };\n }\n\n // 4. Try to find and serialize execution trace (full mode, with task response)\n // Supports Grafana Tempo, Jaeger, and local NDJSON files (auto-detected)\n let traceTree: string | undefined;\n const traceId = match.metadata?.trace_id as string | undefined;\n const traceFile = match.metadata?.trace_file as string | undefined;\n\n if (traceFile || traceId) {\n try {\n const traceRef = traceFile || traceId!;\n // Use full mode (1M chars) so trace is not truncated, include task response\n traceTree = await serializeTraceForPrompt(\n traceRef,\n 1_000_000,\n { traceDir: config?.traceDir },\n responseText !== 'No response available' ? responseText : undefined,\n traceId\n );\n if (traceTree === '(no trace data available)') {\n traceTree = undefined;\n }\n } catch (err) {\n logger.debug(\n `[TaskEvaluator] Failed to load trace: ${err instanceof Error ? err.message : err}`\n );\n }\n }\n\n // 5. Build prompts\n const systemPrompt = config?.prompt || process.env.VISOR_EVAL_PROMPT || DEFAULT_EVALUATION_PROMPT;\n const hasTrace = !!traceTree;\n\n let userPrompt: string;\n if (traceTree) {\n // Trace includes the full execution + response — no need to duplicate\n userPrompt = `<user_request>\\n${requestText}\\n</user_request>\\n\\n<execution_trace>\\n${traceTree}\\n</execution_trace>`;\n } else {\n // No trace available — provide request and response directly\n userPrompt = `<user_request>\\n${requestText}\\n</user_request>\\n\\n<agent_response>\\n${responseText}\\n</agent_response>`;\n }\n\n // 6. Call LLM via ProbeAgent\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { ProbeAgent } = require('@probelabs/probe');\n\n const model =\n config?.model || process.env.VISOR_EVAL_MODEL || process.env.VISOR_JUDGE_MODEL || undefined;\n const provider = config?.provider || process.env.VISOR_EVAL_PROVIDER || undefined;\n\n const agentOptions: Record<string, unknown> = {\n sessionId: `visor-task-eval-${Date.now()}`,\n systemPrompt,\n maxIterations: 1,\n disableTools: true,\n };\n if (model) agentOptions.model = model;\n if (provider) agentOptions.provider = provider;\n if (config?.apiKey) {\n const envKey =\n provider === 'openai'\n ? 'OPENAI_API_KEY'\n : provider === 'anthropic'\n ? 'ANTHROPIC_API_KEY'\n : 'GOOGLE_API_KEY';\n process.env[envKey] = config.apiKey;\n }\n\n const agent = new ProbeAgent(agentOptions);\n if (typeof agent.initialize === 'function') {\n await agent.initialize();\n }\n\n const jsonSchema = buildEvaluationSchema(hasTrace);\n const schemaStr = JSON.stringify(jsonSchema);\n const response = await agent.answer(userPrompt, undefined, { schema: schemaStr });\n\n // 7. Parse JSON response (same pattern as llm-judge.ts:181-196)\n let result: TaskEvaluationResult;\n try {\n const cleaned = response\n .replace(/^```(?:json)?\\s*\\n?/m, '')\n .replace(/\\n?```\\s*$/m, '')\n .trim();\n result = JSON.parse(cleaned);\n } catch {\n const jsonMatch = response.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n result = JSON.parse(jsonMatch[0]);\n } else {\n throw new Error(`Failed to parse evaluation response as JSON: ${response.slice(0, 200)}`);\n }\n }\n\n // 8. When no trace was available, cap overall rating and flag the evaluation\n // as incomplete — response text alone can't verify execution quality.\n if (!hasTrace) {\n const MAX_RATING_WITHOUT_TRACE = 4;\n if (result.overall_rating > MAX_RATING_WITHOUT_TRACE) {\n result.overall_rating = MAX_RATING_WITHOUT_TRACE;\n }\n result.trace_available = false;\n result.summary = `[No trace available — execution quality not assessed, rating capped at ${MAX_RATING_WITHOUT_TRACE}/5] ${result.summary}`;\n } else {\n result.trace_available = true;\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Store evaluation as artifact\n// ---------------------------------------------------------------------------\n\n/**\n * Run evaluation and store result as a task artifact.\n */\nexport async function evaluateAndStore(\n taskId: string,\n store: SqliteTaskStore,\n config?: TaskEvaluatorConfig\n): Promise<TaskEvaluationResult> {\n const result = await evaluateTask(taskId, store, config);\n\n // Resolve full task ID\n const { rows } = store.listTasksRaw({ limit: 500 });\n const match = rows.find(r => r.id === taskId || r.id.startsWith(taskId));\n if (match) {\n store.addArtifact(match.id, {\n artifact_id: crypto.randomUUID(),\n name: 'evaluation',\n parts: [{ text: JSON.stringify(result), media_type: 'application/json' }],\n });\n }\n\n return result;\n}\n"],"mappings":"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","names":[]}
|