@agenthippo-ai/agenthippo 0.1.0
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/LICENSE +21 -0
- package/bin/agenthippo.js +2 -0
- package/out/agentpack/github.js +547 -0
- package/out/agentpack/index.js +66 -0
- package/out/agentpack/install.js +297 -0
- package/out/agentpack/manager.js +441 -0
- package/out/agentpack/manifest.js +135 -0
- package/out/agentpack/paths.js +475 -0
- package/out/agentpack/publish.js +248 -0
- package/out/agentpack/stores.js +554 -0
- package/out/agentpack/types.js +5 -0
- package/out/analytics/dashboard/artifactManager.js +439 -0
- package/out/analytics/dashboard/fleetDisplayUtils.js +159 -0
- package/out/analytics/dashboard/fleetOrchestrator.js +142 -0
- package/out/analytics/dashboard/fleetRegistry.js +79 -0
- package/out/analytics/dashboard/fleetRowDefaults.js +87 -0
- package/out/analytics/dashboard/fleetScriptAdapter.js +146 -0
- package/out/analytics/dashboard/fleetStatusUtils.js +30 -0
- package/out/analytics/dashboard/fleetTitleUtils.js +68 -0
- package/out/analytics/dashboard/fleetTypes.js +29 -0
- package/out/analytics/dashboard/index.js +17 -0
- package/out/analytics/dashboard/indexingService.js +422 -0
- package/out/analytics/dashboard/localFleetAdapter.js +222 -0
- package/out/analytics/dashboard/performanceMonitor.js +76 -0
- package/out/analytics/dashboard/processManager.js +209 -0
- package/out/analytics/dashboard/provider.js +3885 -0
- package/out/analytics/dashboard/queryRunner.js +994 -0
- package/out/analytics/dashboard/refreshPolicy.js +16 -0
- package/out/analytics/dashboard/scheduledFleetFingerprint.js +22 -0
- package/out/analytics/dashboard/scheduledFleetStorage.js +241 -0
- package/out/analytics/dashboard/scheduledFleetUtils.js +22 -0
- package/out/analytics/dashboard/sessionEventFleetStats.js +118 -0
- package/out/analytics/dashboard/sessionJsonlFleet.js +204 -0
- package/out/analytics/dashboard/teamViewDiscovery.js +106 -0
- package/out/analytics/dashboard/viewsConfigMerge.js +221 -0
- package/out/analytics/dashboard/webview/analytics-panel.html +155 -0
- package/out/analytics/dashboard/webview/codicon.ttf +0 -0
- package/out/analytics/dashboard/webview/index.html +78 -0
- package/out/analytics/dashboard/webview/panel.js +2295 -0
- package/out/analytics/dashboard/webview/runtime-bridge.js +82 -0
- package/out/analytics/dashboard/webview/styles.css +1009 -0
- package/out/analytics/lifecycle.js +43 -0
- package/out/analytics/service.js +1279 -0
- package/out/analytics/sessionEvents.js +231 -0
- package/out/auth/bootstrapApply.js +72 -0
- package/out/auth/brokerClient.js +126 -0
- package/out/auth/callbackUri.js +59 -0
- package/out/auth/callbackUriParse.js +59 -0
- package/out/auth/githubStoreGate.js +70 -0
- package/out/auth/loopbackAuthServer.js +165 -0
- package/out/auth/pkce.js +9 -0
- package/out/auth/provider.js +239 -0
- package/out/auth/sessionStore.js +193 -0
- package/out/auth/signInFlow.js +182 -0
- package/out/auth/types.js +12 -0
- package/out/auth-broker/config.js +62 -0
- package/out/auth-broker/jwt.js +105 -0
- package/out/auth-broker/pkce.js +57 -0
- package/out/auth-broker/server.js +539 -0
- package/out/auth-broker/store.js +273 -0
- package/out/base-prompts/gateway/openclaw-starter.json5 +144 -0
- package/out/base-prompts/gateway/slack-socket-mode-minimal.manifest.json +44 -0
- package/out/base-prompts/mcp-servers/.mcp.json +13 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/README.md +142 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/mcp.json +13 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/package-lock.json +1192 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/package.json +23 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/run-with-env.sh +12 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/sdk-test.js +102 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/simple-test.js +137 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-client.js +118 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-server.js +209 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/src/client.ts +431 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/src/index.ts +270 -0
- package/out/base-prompts/mcp-servers/langfuse-analytics/tsconfig.json +18 -0
- package/out/base-prompts/rules/AGENTS.md +204 -0
- package/out/base-prompts/skills/agent-browser/SKILL.md +102 -0
- package/out/base-prompts/skills/agent-builder/SKILL.md +73 -0
- package/out/base-prompts/skills/agent-builder/references/agent-pack.md +63 -0
- package/out/base-prompts/skills/agent-builder/references/crewai.md +66 -0
- package/out/base-prompts/skills/agent-builder/references/langchain.md +60 -0
- package/out/base-prompts/skills/agent-builder/references/raw-sdk.md +73 -0
- package/out/base-prompts/skills/agent-engine-creator/SKILL.md +586 -0
- package/out/base-prompts/skills/agent-engine-installer/SKILL.md +106 -0
- package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.mjs +259 -0
- package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.ps1 +189 -0
- package/out/base-prompts/skills/agenthippo-cli/SKILL.md +69 -0
- package/out/base-prompts/skills/agenthippo-cli/scripts/install.sh +63 -0
- package/out/base-prompts/skills/agentpack-creator/SKILL.md +375 -0
- package/out/base-prompts/skills/agentpack-creator/references/agent-templates.md +261 -0
- package/out/base-prompts/skills/agentpack-creator/references/agents-md-guide.md +195 -0
- package/out/base-prompts/skills/agentpack-creator/scripts/init_pack.py +391 -0
- package/out/base-prompts/skills/agentpack-creator/scripts/init_pack_test.py +294 -0
- package/out/base-prompts/skills/agentpack-testbench/SKILL.md +135 -0
- package/out/base-prompts/skills/algorithmic-art/LICENSE.txt +202 -0
- package/out/base-prompts/skills/algorithmic-art/SKILL.md +405 -0
- package/out/base-prompts/skills/algorithmic-art/templates/generator_template.js +223 -0
- package/out/base-prompts/skills/algorithmic-art/templates/viewer.html +599 -0
- package/out/base-prompts/skills/debug-agent/SKILL.md +83 -0
- package/out/base-prompts/skills/desktop-automation/SKILL.md +166 -0
- package/out/base-prompts/skills/desktop-automation/references/agenthippo-ui-recipes.md +441 -0
- package/out/base-prompts/skills/desktop-automation/scripts/windows-desktop-uia.psm1 +280 -0
- package/out/base-prompts/skills/docx/LICENSE.txt +30 -0
- package/out/base-prompts/skills/docx/SKILL.md +197 -0
- package/out/base-prompts/skills/docx/docx-js.md +350 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/mce/mc.xsd +75 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/pack.py +159 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/unpack.py +29 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/validate.py +69 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/validation/__init__.py +15 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/validation/base.py +951 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/validation/docx.py +274 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/validation/pptx.py +315 -0
- package/out/base-prompts/skills/docx/ooxml/scripts/validation/redlining.py +279 -0
- package/out/base-prompts/skills/docx/ooxml.md +610 -0
- package/out/base-prompts/skills/docx/scripts/__init__.py +1 -0
- package/out/base-prompts/skills/docx/scripts/document.py +1276 -0
- package/out/base-prompts/skills/docx/scripts/templates/comments.xml +3 -0
- package/out/base-prompts/skills/docx/scripts/templates/commentsExtended.xml +3 -0
- package/out/base-prompts/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/out/base-prompts/skills/docx/scripts/templates/commentsIds.xml +3 -0
- package/out/base-prompts/skills/docx/scripts/templates/people.xml +3 -0
- package/out/base-prompts/skills/docx/scripts/utilities.py +374 -0
- package/out/base-prompts/skills/gateway-outbound/SKILL.md +136 -0
- package/out/base-prompts/skills/mcp-builder/LICENSE.txt +202 -0
- package/out/base-prompts/skills/mcp-builder/SKILL.md +328 -0
- package/out/base-prompts/skills/mcp-builder/reference/evaluation.md +602 -0
- package/out/base-prompts/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/out/base-prompts/skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/out/base-prompts/skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/out/base-prompts/skills/mcp-builder/scripts/connections.py +151 -0
- package/out/base-prompts/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/out/base-prompts/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/out/base-prompts/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/out/base-prompts/skills/openclaw/SKILL.md +406 -0
- package/out/base-prompts/skills/openclaw/scripts/run-smoke.sh +61 -0
- package/out/base-prompts/skills/pdf/LICENSE.txt +30 -0
- package/out/base-prompts/skills/pdf/SKILL.md +294 -0
- package/out/base-prompts/skills/pdf/forms.md +205 -0
- package/out/base-prompts/skills/pdf/reference.md +612 -0
- package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes.py +70 -0
- package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes_test.py +226 -0
- package/out/base-prompts/skills/pdf/scripts/check_fillable_fields.py +12 -0
- package/out/base-prompts/skills/pdf/scripts/convert_pdf_to_images.py +35 -0
- package/out/base-prompts/skills/pdf/scripts/create_validation_image.py +41 -0
- package/out/base-prompts/skills/pdf/scripts/extract_form_field_info.py +152 -0
- package/out/base-prompts/skills/pdf/scripts/fill_fillable_fields.py +114 -0
- package/out/base-prompts/skills/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
- package/out/base-prompts/skills/pptx/LICENSE.txt +30 -0
- package/out/base-prompts/skills/pptx/SKILL.md +484 -0
- package/out/base-prompts/skills/pptx/html2pptx.md +625 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/mce/mc.xsd +75 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/pack.py +159 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/unpack.py +29 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/validate.py +69 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/validation/__init__.py +15 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/validation/base.py +951 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/validation/docx.py +274 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/validation/pptx.py +315 -0
- package/out/base-prompts/skills/pptx/ooxml/scripts/validation/redlining.py +279 -0
- package/out/base-prompts/skills/pptx/ooxml.md +427 -0
- package/out/base-prompts/skills/pptx/scripts/html2pptx.js +984 -0
- package/out/base-prompts/skills/pptx/scripts/inventory.py +1020 -0
- package/out/base-prompts/skills/pptx/scripts/rearrange.py +231 -0
- package/out/base-prompts/skills/pptx/scripts/replace.py +385 -0
- package/out/base-prompts/skills/pptx/scripts/thumbnail.py +450 -0
- package/out/base-prompts/skills/prompt-lab/SKILL.md +149 -0
- package/out/base-prompts/skills/prompt-lab/scripts/query-analytics.sh +2551 -0
- package/out/base-prompts/skills/skill-creator/LICENSE.txt +202 -0
- package/out/base-prompts/skills/skill-creator/SKILL.md +371 -0
- package/out/base-prompts/skills/skill-creator/references/output-patterns.md +82 -0
- package/out/base-prompts/skills/skill-creator/references/workflows.md +28 -0
- package/out/base-prompts/skills/skill-creator/scripts/init_skill.py +303 -0
- package/out/base-prompts/skills/skill-creator/scripts/package_skill.py +110 -0
- package/out/base-prompts/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/out/base-prompts/skills/skill-installer/SKILL.md +251 -0
- package/out/base-prompts/skills/skill-installer/references/store-api.md +219 -0
- package/out/base-prompts/skills/spotlight-dashboard/SKILL.md +83 -0
- package/out/base-prompts/skills/spotlight-insights/SKILL.md +101 -0
- package/out/base-prompts/skills/spotlight-optimize/SKILL.md +93 -0
- package/out/base-prompts/skills/spotlight-optimize/scripts/query-analytics.sh +2551 -0
- package/out/base-prompts/skills/spotlight-query/SKILL.md +314 -0
- package/out/base-prompts/skills/spotlight-query/references/otel-schema.md +163 -0
- package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.sh +2551 -0
- package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.test.sh +744 -0
- package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/sessions/sess-test-1.jsonl +14 -0
- package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/.duckdb-index.json +7 -0
- package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/sample.jsonl +5 -0
- package/out/base-prompts/skills/spotlight-report/SKILL.md +86 -0
- package/out/base-prompts/skills/spotlight-visualize/SKILL.md +92 -0
- package/out/base-prompts/skills/spotlight-visualize/scripts/query-analytics.sh +2551 -0
- package/out/base-prompts/skills/xlsx/LICENSE.txt +30 -0
- package/out/base-prompts/skills/xlsx/SKILL.md +289 -0
- package/out/base-prompts/skills/xlsx/recalc.py +178 -0
- package/out/branding.js +23 -0
- package/out/cli/commands/anywhere.js +66 -0
- package/out/cli/commands/ask.js +243 -0
- package/out/cli/commands/auth-broker.js +24 -0
- package/out/cli/commands/auth.js +219 -0
- package/out/cli/commands/chat.js +354 -0
- package/out/cli/commands/deploy.js +223 -0
- package/out/cli/commands/gateway.js +160 -0
- package/out/cli/commands/pack.js +220 -0
- package/out/cli/commands/probe-custom-engine.js +82 -0
- package/out/cli/commands/serve.js +583 -0
- package/out/cli/commands/store.js +919 -0
- package/out/cli/credential-context.js +113 -0
- package/out/cli/deploy-init.js +2087 -0
- package/out/cli/deps.js +238 -0
- package/out/cli/index.js +390 -0
- package/out/cli/pack-loader.js +376 -0
- package/out/cli/run-custom-engine-runner.js +318 -0
- package/out/cli/runtime.js +409 -0
- package/out/cli/serve-hardening.js +325 -0
- package/out/cli/state-sync.js +135 -0
- package/out/cli/utils.js +220 -0
- package/out/cli/vscode-shim.js +86 -0
- package/out/engines/base.js +159 -0
- package/out/engines/claude.js +1215 -0
- package/out/engines/codex.js +759 -0
- package/out/engines/custom.js +632 -0
- package/out/engines/customEngineIpc.js +95 -0
- package/out/engines/customEngineRunnerEmbed.js +107 -0
- package/out/engines/customRegistry.js +274 -0
- package/out/engines/embeddedCustomEngineRunner.generated.js +9 -0
- package/out/engines/engineApiKey.js +138 -0
- package/out/engines/factory.js +45 -0
- package/out/engines/install.js +372 -0
- package/out/engines/persistentEngineWorker.js +320 -0
- package/out/engines/publish.js +341 -0
- package/out/engines/types.js +17 -0
- package/out/examples/run-headless-repl.js +291 -0
- package/out/examples/run-headless.js +187 -0
- package/out/extension.js +4472 -0
- package/out/gateway/bridge.js +805 -0
- package/out/gateway/cli-agent-id.js +36 -0
- package/out/gateway/client.js +303 -0
- package/out/gateway/client.test.js +395 -0
- package/out/gateway/gateway-scope-errors.js +14 -0
- package/out/gateway/gatewayBackup.js +76 -0
- package/out/gateway/gatewayRouteIndex.js +371 -0
- package/out/gateway/gatewaySessionStore.js +243 -0
- package/out/gateway/index.js +31 -0
- package/out/gateway/lifecycle.js +3056 -0
- package/out/gateway/openclaw-channel-routing.js +317 -0
- package/out/gateway/outboundHandles.js +373 -0
- package/out/gateway/outboundPaths.js +72 -0
- package/out/gateway/outboundSend.js +254 -0
- package/out/gateway/protocol.js +48 -0
- package/out/gateway/resetMappings.js +52 -0
- package/out/gateway/service.js +1244 -0
- package/out/gateway/sessionRouting.js +77 -0
- package/out/headless/index.js +505 -0
- package/out/headless/sync.js +473 -0
- package/out/mcp/publish.js +143 -0
- package/out/mcp.js +597 -0
- package/out/permissions.js +108 -0
- package/out/permissions.mapping.yaml +68 -0
- package/out/registry/index.js +332 -0
- package/out/remote/handleRemoteChat.js +122 -0
- package/out/remote/remotePackStatusBar.js +103 -0
- package/out/remote/serveClient.js +216 -0
- package/out/remoteSkills.js +191 -0
- package/out/runtime/index.js +32 -0
- package/out/runtime/interfaces/config-provider.js +32 -0
- package/out/runtime/interfaces/edit-tracker.js +22 -0
- package/out/runtime/interfaces/index.js +29 -0
- package/out/runtime/interfaces/logger.js +40 -0
- package/out/runtime/interfaces/permission-handler.js +66 -0
- package/out/runtime/interfaces/session-store.js +342 -0
- package/out/runtime/vscode/index.js +20 -0
- package/out/runtime/vscode/vscode-config-provider.js +77 -0
- package/out/runtime/vscode/vscode-edit-tracker.js +120 -0
- package/out/runtime/vscode/vscode-logger.js +30 -0
- package/out/runtime/vscode/vscode-permission-handler.js +72 -0
- package/out/runtime/vscode/vscode-session-store.js +22 -0
- package/out/scripts/agentide-litellm.ps1 +501 -0
- package/out/scripts/agentide-litellm.sh +700 -0
- package/out/scripts/agentide_litellm_hooks_v1.py +604 -0
- package/out/scripts/analytics/agent-fleet-widget.js +954 -0
- package/out/scripts/analytics/agentide-litellm-otel.config.yaml +160 -0
- package/out/scripts/analytics/default-seed-scripts.json +12 -0
- package/out/scripts/analytics/duckdbLocator.js +29 -0
- package/out/scripts/analytics/fleet-local-adapter.js +246 -0
- package/out/scripts/analytics/fleet-workflow.sh +263 -0
- package/out/scripts/analytics/generate-default-charts.py +685 -0
- package/out/scripts/analytics/hotspot-analysis-widget.js +909 -0
- package/out/scripts/analytics/index-traces.py +1394 -0
- package/out/scripts/analytics/install-analytics.sh +286 -0
- package/out/scripts/analytics/live-fleet-4-collector.cjs +315 -0
- package/out/scripts/analytics/live-fleet-4-daemon.mjs +134 -0
- package/out/scripts/analytics/live-fleet-4-widget.cjs +433 -0
- package/out/scripts/analytics/otel-collector.yaml +55 -0
- package/out/scripts/analytics/query-analytics.ps1 +396 -0
- package/out/scripts/analytics/query-analytics.sh +2551 -0
- package/out/scripts/analytics/session-comparison-widget.js +1468 -0
- package/out/scripts/analytics/session-timeline-widget.js +2137 -0
- package/out/scripts/analytics/setup-analytics.ps1 +432 -0
- package/out/scripts/analytics/setup-analytics.sh +854 -0
- package/out/scripts/analytics/start-analytics.ps1 +169 -0
- package/out/scripts/analytics/start-analytics.sh +323 -0
- package/out/scripts/analytics/token-cost-flamegraph-widget.js +954 -0
- package/out/scripts/analytics/token-cost-trend-widget.js +1062 -0
- package/out/scripts/analytics/workflows-collector.cjs +315 -0
- package/out/scripts/analytics/workflows-daemon.mjs +186 -0
- package/out/scripts/analytics/workflows-widget.cjs +433 -0
- package/out/scripts/analytics/wow-model-widget.js +191 -0
- package/out/scripts/gateway/Dockerfile.serve +45 -0
- package/out/scripts/gateway/docker-serve.sh +248 -0
- package/out/scripts/gateway/setup-gateway-server.ps1 +953 -0
- package/out/scripts/gateway/setup-gateway-server.sh +935 -0
- package/out/scripts/gateway/setup-gateway.ps1 +1813 -0
- package/out/scripts/gateway/setup-gateway.sh +1541 -0
- package/out/scripts/setup-cli.ps1 +86 -0
- package/out/scripts/setup-cli.sh +117 -0
- package/out/scripts/tools-manifest.json +83 -0
- package/out/scripts/vendor-tools.mjs +234 -0
- package/out/session.js +272 -0
- package/out/shared/agentEngines.js +170 -0
- package/out/shared/index.js +109 -0
- package/out/shared/models.js +235 -0
- package/out/shared/providers.js +202 -0
- package/out/skills/publish.js +178 -0
- package/out/spotlight/install.js +339 -0
- package/out/spotlight/publish.js +544 -0
- package/out/spotlight/yaml.js +31 -0
- package/out/store/github/setup.js +235 -0
- package/out/store/github/templates/build-index.ts +339 -0
- package/out/store/github/templates/build-index.yml +97 -0
- package/out/store/github/templates/package.json +11 -0
- package/out/store/index.js +755 -0
- package/out/store/oramaIndex.js +884 -0
- package/out/store/shared.js +241 -0
- package/out/sync.js +1827 -0
- package/out/terminalLogger.js +358 -0
- package/out/test/agentEnvFile.test.js +97 -0
- package/out/test/agentYaml.test.js +373 -0
- package/out/test/agentpack.test.js +639 -0
- package/out/test/analytics-lifecycle.test.js +174 -0
- package/out/test/analytics.test.js +524 -0
- package/out/test/auth-bootstrapApply.test.js +102 -0
- package/out/test/auth-broker.test.js +434 -0
- package/out/test/auth-callback.test.js +62 -0
- package/out/test/branding.test.js +229 -0
- package/out/test/cli-agent-id.test.js +55 -0
- package/out/test/credential-context.test.js +105 -0
- package/out/test/customEngineInstall.test.js +100 -0
- package/out/test/customRegistry.test.js +122 -0
- package/out/test/deploy-init.test.js +532 -0
- package/out/test/deps-auto-install.test.js +81 -0
- package/out/test/engine-integration.test.js +297 -0
- package/out/test/enginePaths.test.js +195 -0
- package/out/test/enginePublish.test.js +82 -0
- package/out/test/fleetDisplayUtils.test.js +96 -0
- package/out/test/gateway-client.test.js +381 -0
- package/out/test/gateway-lifecycle.test.js +608 -0
- package/out/test/gateway-outbound.test.js +531 -0
- package/out/test/gateway-reset-mappings.test.js +221 -0
- package/out/test/gateway-route-index.test.js +235 -0
- package/out/test/gateway-session-routing.test.js +47 -0
- package/out/test/gateway-session-store.test.js +216 -0
- package/out/test/github.test.js +232 -0
- package/out/test/headless-sync.test.js +279 -0
- package/out/test/helpers/manifest-fixtures.js +27 -0
- package/out/test/mcp-obo.test.js +82 -0
- package/out/test/mcp.test.js +307 -0
- package/out/test/openclaw-channel-routing.test.js +191 -0
- package/out/test/pathSafety.test.js +63 -0
- package/out/test/permissions.test.js +175 -0
- package/out/test/persistent-engine-worker.test.js +220 -0
- package/out/test/refreshPolicy.test.js +60 -0
- package/out/test/registry.test.js +351 -0
- package/out/test/runtime.test.js +349 -0
- package/out/test/scheduledFleetStorage.test.js +72 -0
- package/out/test/serve-hardening.test.js +192 -0
- package/out/test/serveClient.test.js +173 -0
- package/out/test/sessionJsonlFleet.test.js +78 -0
- package/out/test/shared.test.js +173 -0
- package/out/test/skills-mcp.test.js +299 -0
- package/out/test/spotlightPublish.test.js +299 -0
- package/out/test/spotlightYaml.test.js +58 -0
- package/out/test/state-sync-peer.test.js +74 -0
- package/out/test/store.test.js +331 -0
- package/out/test/stores.test.js +504 -0
- package/out/test/sync-switch.test.js +251 -0
- package/out/test/teamViewDiscovery.test.js +90 -0
- package/out/test/toolPath.test.js +120 -0
- package/out/test/utils.test.js +308 -0
- package/out/test/viewsConfigMerge.test.js +180 -0
- package/out/test/windows-command-line.test.js +75 -0
- package/out/utils/agentEnvFile.js +131 -0
- package/out/utils/binary.js +175 -0
- package/out/utils/index.js +37 -0
- package/out/utils/network.js +74 -0
- package/out/utils/pathSafety.js +60 -0
- package/out/utils/paths.js +116 -0
- package/out/utils/pid.js +180 -0
- package/out/utils/pythonRunner.js +258 -0
- package/out/utils/resolveNodeBinary.js +168 -0
- package/out/utils/toolPath.js +83 -0
- package/out/utils/windowsCommandLine.js +29 -0
- package/out/utils.js +915 -0
- package/out/workspaceFolders.js +59 -0
- package/package.json +41 -0
|
@@ -0,0 +1,2137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* Copyright (c) AgentHippo.ai. All rights reserved. */
|
|
3
|
+
/* eslint-disable no-console */
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { execFileSync } = require('child_process');
|
|
7
|
+
const { findDuckdb } = require('./duckdbLocator');
|
|
8
|
+
|
|
9
|
+
const SESSION_LIMIT = 8;
|
|
10
|
+
const SESSION_OVERSCAN = 24;
|
|
11
|
+
const MAX_LLM_ROWS_PER_SESSION = 90;
|
|
12
|
+
const MAX_TOOL_CALLS_PER_SESSION = 200;
|
|
13
|
+
const MAX_TURN_EVENTS_PER_SESSION = 260;
|
|
14
|
+
const MAX_ERROR_EVENTS_PER_SESSION = 80;
|
|
15
|
+
const TEXT_PREVIEW_CHARS = 320;
|
|
16
|
+
const MAX_FULL_TEXT_CHARS_PER_FIELD = 32_000;
|
|
17
|
+
const GLOBAL_FULL_TEXT_BUDGET = 360_000;
|
|
18
|
+
const SQL_MESSAGE_FIELD_CHARS = 12_000; // cap SQL payload size to avoid duckdb JSON output buffer overflows.
|
|
19
|
+
|
|
20
|
+
function parseJsonArraysFromOutput(stdout) {
|
|
21
|
+
const results = [];
|
|
22
|
+
let depth = 0;
|
|
23
|
+
let start = -1;
|
|
24
|
+
let inString = false;
|
|
25
|
+
let escaped = false;
|
|
26
|
+
|
|
27
|
+
for (let i = 0; i < stdout.length; i++) {
|
|
28
|
+
const c = stdout[i];
|
|
29
|
+
|
|
30
|
+
if (inString) {
|
|
31
|
+
if (escaped) {
|
|
32
|
+
escaped = false;
|
|
33
|
+
} else if (c === '\\') {
|
|
34
|
+
escaped = true;
|
|
35
|
+
} else if (c === '"') {
|
|
36
|
+
inString = false;
|
|
37
|
+
}
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (c === '"') {
|
|
42
|
+
inString = true;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (c === '[') {
|
|
47
|
+
if (depth === 0) {
|
|
48
|
+
start = i;
|
|
49
|
+
}
|
|
50
|
+
depth++;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (c === ']') {
|
|
55
|
+
if (depth > 0) {
|
|
56
|
+
depth--;
|
|
57
|
+
}
|
|
58
|
+
if (depth === 0 && start >= 0) {
|
|
59
|
+
try {
|
|
60
|
+
results.push(JSON.parse(stdout.slice(start, i + 1)));
|
|
61
|
+
} catch {
|
|
62
|
+
results.push([]);
|
|
63
|
+
}
|
|
64
|
+
start = -1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return results;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function runSingleQuery(duckdbPath, sql, dbPath = '') {
|
|
73
|
+
const settings = "SET memory_limit='4GB';\nSET threads TO 1;\nSET preserve_insertion_order=false";
|
|
74
|
+
const args = [];
|
|
75
|
+
if (dbPath) {
|
|
76
|
+
args.push(dbPath);
|
|
77
|
+
}
|
|
78
|
+
args.push('-json', '-c', `${settings};\n${sql}`);
|
|
79
|
+
const stdout = execFileSync(duckdbPath, args, {
|
|
80
|
+
encoding: 'utf8',
|
|
81
|
+
maxBuffer: 40 * 1024 * 1024,
|
|
82
|
+
});
|
|
83
|
+
const arrays = parseJsonArraysFromOutput(stdout);
|
|
84
|
+
return arrays[0] || [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function runBatchQueries(duckdbPath, queries, dbPath = '') {
|
|
88
|
+
const settings = "SET memory_limit='4GB';\nSET threads TO 1;\nSET preserve_insertion_order=false";
|
|
89
|
+
const allSql = settings + ';\n' + queries.map((q) => q.sql).join('\n');
|
|
90
|
+
const args = [];
|
|
91
|
+
if (dbPath) {
|
|
92
|
+
args.push(dbPath);
|
|
93
|
+
}
|
|
94
|
+
args.push('-json', '-c', allSql);
|
|
95
|
+
try {
|
|
96
|
+
const stdout = execFileSync(duckdbPath, args, {
|
|
97
|
+
encoding: 'utf8',
|
|
98
|
+
maxBuffer: 40 * 1024 * 1024,
|
|
99
|
+
});
|
|
100
|
+
const arrays = parseJsonArraysFromOutput(stdout);
|
|
101
|
+
const result = {};
|
|
102
|
+
queries.forEach((q, i) => { result[q.name] = arrays[i] || []; });
|
|
103
|
+
return { result, failed: false };
|
|
104
|
+
} catch (err) {
|
|
105
|
+
console.error(`session-timeline-widget batch query failed: ${err instanceof Error ? err.message : err}`);
|
|
106
|
+
const fallbackResult = {};
|
|
107
|
+
let anySuccess = false;
|
|
108
|
+
for (const query of queries) {
|
|
109
|
+
try {
|
|
110
|
+
fallbackResult[query.name] = runSingleQuery(duckdbPath, query.sql, dbPath);
|
|
111
|
+
anySuccess = true;
|
|
112
|
+
} catch (singleErr) {
|
|
113
|
+
console.error(`session-timeline-widget query fallback failed (${query.name}): ${singleErr instanceof Error ? singleErr.message : singleErr}`);
|
|
114
|
+
fallbackResult[query.name] = [];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { result: fallbackResult, failed: !anySuccess };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getIndexedDbPath(tracesDir) {
|
|
122
|
+
const configuredDbPath = String(process.env.AGENTIDE_INDEX_DB_PATH || '').trim();
|
|
123
|
+
const fallbackDbPath = path.join(tracesDir, 'analytics.duckdb');
|
|
124
|
+
const candidates = [configuredDbPath, fallbackDbPath].filter(Boolean);
|
|
125
|
+
for (const candidate of candidates) {
|
|
126
|
+
try {
|
|
127
|
+
const stat = fs.statSync(candidate);
|
|
128
|
+
if (stat.isFile()) {
|
|
129
|
+
return candidate;
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
// continue searching
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return '';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function buildBaseQuery(tracesDir, useIndexed = false) {
|
|
139
|
+
if (useIndexed) {
|
|
140
|
+
return `
|
|
141
|
+
WITH extracted AS (
|
|
142
|
+
SELECT
|
|
143
|
+
trace_id,
|
|
144
|
+
COALESCE(span_name, 'Received Proxy Server Request') AS span_name,
|
|
145
|
+
COALESCE(duration_ms, 0) AS duration_ms,
|
|
146
|
+
COALESCE(start_ms, 0) AS start_ms,
|
|
147
|
+
CASE WHEN COALESCE(status, '') = 'error' THEN 2 ELSE 0 END AS status_code,
|
|
148
|
+
session_id,
|
|
149
|
+
COALESCE(request_id, '') AS request_id,
|
|
150
|
+
COALESCE(agent_id, '') AS agent_id,
|
|
151
|
+
COALESCE(model, 'unknown') AS model,
|
|
152
|
+
COALESCE(total_cost_usd, 0) AS cost,
|
|
153
|
+
COALESCE(prompt_tokens, 0) AS input_tokens,
|
|
154
|
+
COALESCE(completion_tokens, 0) AS output_tokens,
|
|
155
|
+
LEFT(COALESCE(input_messages_raw, ''), ${SQL_MESSAGE_FIELD_CHARS}) AS input_messages,
|
|
156
|
+
LEFT(COALESCE(output_messages_raw, ''), ${SQL_MESSAGE_FIELD_CHARS}) AS output_messages
|
|
157
|
+
FROM model_calls
|
|
158
|
+
WHERE session_id IS NOT NULL AND session_id != ''
|
|
159
|
+
)
|
|
160
|
+
`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const tracesGlob = `${tracesDir.replace(/'/g, "''")}/*.jsonl`;
|
|
164
|
+
return `
|
|
165
|
+
WITH raw_spans AS (
|
|
166
|
+
SELECT unnest(resourceSpans) AS rs
|
|
167
|
+
FROM read_json('${tracesGlob}', format='newline_delimited', ignore_errors=true)
|
|
168
|
+
),
|
|
169
|
+
spans AS (
|
|
170
|
+
SELECT unnest(rs.scopeSpans) AS ss
|
|
171
|
+
FROM raw_spans
|
|
172
|
+
),
|
|
173
|
+
flat_spans AS (
|
|
174
|
+
SELECT unnest(ss.spans) AS span
|
|
175
|
+
FROM spans
|
|
176
|
+
),
|
|
177
|
+
parsed AS (
|
|
178
|
+
SELECT
|
|
179
|
+
span.traceId AS trace_id,
|
|
180
|
+
span.spanId AS span_id,
|
|
181
|
+
span.name AS span_name,
|
|
182
|
+
span.startTimeUnixNano::BIGINT / 1000000 AS start_ms,
|
|
183
|
+
span.endTimeUnixNano::BIGINT / 1000000 AS end_ms,
|
|
184
|
+
(span.endTimeUnixNano::BIGINT - span.startTimeUnixNano::BIGINT) / 1000000 AS duration_ms,
|
|
185
|
+
CAST(json_extract(to_json(span), '$.status.code') AS BIGINT) AS status_code,
|
|
186
|
+
json_extract(to_json(span), '$.attributes') AS attrs_json
|
|
187
|
+
FROM flat_spans
|
|
188
|
+
),
|
|
189
|
+
span_filter AS (
|
|
190
|
+
SELECT trace_id, span_name, duration_ms, start_ms, status_code, attrs_json
|
|
191
|
+
FROM parsed
|
|
192
|
+
WHERE span_name = 'Received Proxy Server Request'
|
|
193
|
+
OR span_name = 'Failed Proxy Server Request'
|
|
194
|
+
OR EXISTS (
|
|
195
|
+
SELECT 1
|
|
196
|
+
FROM json_each(coalesce(attrs_json, '[]'::JSON)) AS attr
|
|
197
|
+
WHERE json_extract_string(attr.value, '$.key') = 'gen_ai.cost.total_cost'
|
|
198
|
+
)
|
|
199
|
+
),
|
|
200
|
+
attrs AS (
|
|
201
|
+
SELECT
|
|
202
|
+
trace_id,
|
|
203
|
+
span_name,
|
|
204
|
+
duration_ms,
|
|
205
|
+
start_ms,
|
|
206
|
+
status_code,
|
|
207
|
+
json_extract_string(value, '$.key') AS attr_key,
|
|
208
|
+
json_extract_string(value, '$.value.stringValue') AS attr_string,
|
|
209
|
+
json_extract_string(value, '$.value.intValue') AS attr_int,
|
|
210
|
+
json_extract(value, '$.value.doubleValue') AS attr_double
|
|
211
|
+
FROM span_filter, json_each(coalesce(attrs_json, '[]'::JSON))
|
|
212
|
+
),
|
|
213
|
+
extracted AS (
|
|
214
|
+
SELECT
|
|
215
|
+
trace_id,
|
|
216
|
+
span_name,
|
|
217
|
+
duration_ms,
|
|
218
|
+
start_ms,
|
|
219
|
+
max(status_code) AS status_code,
|
|
220
|
+
COALESCE(
|
|
221
|
+
NULLIF(regexp_extract(
|
|
222
|
+
max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END),
|
|
223
|
+
'x-litellm-metadata-session-id'': ''([^'']+)''', 1
|
|
224
|
+
), ''),
|
|
225
|
+
NULLIF(regexp_extract(
|
|
226
|
+
max(CASE WHEN attr_key = 'metadata.requester_metadata' THEN attr_string END),
|
|
227
|
+
'session_id'': ''([^'']+)''', 1
|
|
228
|
+
), '')
|
|
229
|
+
) AS session_id,
|
|
230
|
+
COALESCE(
|
|
231
|
+
NULLIF(regexp_extract(
|
|
232
|
+
max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END),
|
|
233
|
+
'x-litellm-metadata-request-id'': ''([^'']+)''', 1
|
|
234
|
+
), ''),
|
|
235
|
+
NULLIF(regexp_extract(
|
|
236
|
+
max(CASE WHEN attr_key = 'metadata.requester_metadata' THEN attr_string END),
|
|
237
|
+
'request_id'': ''([^'']+)''', 1
|
|
238
|
+
), '')
|
|
239
|
+
) AS request_id,
|
|
240
|
+
COALESCE(
|
|
241
|
+
NULLIF(regexp_extract(
|
|
242
|
+
max(CASE WHEN attr_key = 'metadata.requester_custom_headers' THEN attr_string END),
|
|
243
|
+
'x-litellm-metadata-agent-id'': ''([^'']+)''', 1
|
|
244
|
+
), ''),
|
|
245
|
+
NULLIF(regexp_extract(
|
|
246
|
+
max(CASE WHEN attr_key = 'metadata.requester_metadata' THEN attr_string END),
|
|
247
|
+
'agent_id'': ''([^'']+)''', 1
|
|
248
|
+
), '')
|
|
249
|
+
) AS agent_id,
|
|
250
|
+
max(CASE WHEN attr_key IN ('gen_ai.request.model', 'gen_ai.response.model', 'model') THEN attr_string END) AS model,
|
|
251
|
+
max(CASE WHEN attr_key = 'gen_ai.cost.total_cost' THEN CAST(attr_double AS DOUBLE) END) AS cost,
|
|
252
|
+
max(CASE WHEN attr_key = 'gen_ai.usage.input_tokens' THEN CAST(attr_int AS BIGINT) END) AS input_tokens,
|
|
253
|
+
max(CASE WHEN attr_key = 'gen_ai.usage.output_tokens' THEN CAST(attr_int AS BIGINT) END) AS output_tokens,
|
|
254
|
+
max(CASE WHEN attr_key = 'gen_ai.input.messages' THEN attr_string END) AS input_messages,
|
|
255
|
+
max(CASE WHEN attr_key = 'gen_ai.output.messages' THEN attr_string END) AS output_messages
|
|
256
|
+
FROM attrs
|
|
257
|
+
GROUP BY trace_id, span_name, duration_ms, start_ms
|
|
258
|
+
)
|
|
259
|
+
`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function buildFallbackHtml(message) {
|
|
263
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>Session Timeline Explorer</title></head><body style="font-family: ui-sans-serif, system-ui; background:#0f111a; color:#e5e7eb; margin:0; padding:20px;"><h2 style="margin:0 0 8px;">Session Timeline Explorer</h2><p style="opacity:.8;">${message}</p></body></html>`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function isOutputDisabled(filename) {
|
|
267
|
+
const raw = String(process.env.AGENTIDE_DISABLED_CHARTS || '').trim();
|
|
268
|
+
if (!raw) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
const disabled = new Set(
|
|
272
|
+
raw
|
|
273
|
+
.split(',')
|
|
274
|
+
.map((entry) => path.basename(entry.trim()).toLowerCase())
|
|
275
|
+
.filter(Boolean),
|
|
276
|
+
);
|
|
277
|
+
return disabled.has(path.basename(filename).toLowerCase());
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function hasJsonlFiles(dirPath) {
|
|
281
|
+
try {
|
|
282
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
283
|
+
return entries.some((entry) => entry.isFile() && entry.name.endsWith('.jsonl'));
|
|
284
|
+
} catch {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function toNumber(value, fallback = 0) {
|
|
290
|
+
const numeric = Number(value);
|
|
291
|
+
return Number.isFinite(numeric) ? numeric : fallback;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function sessionHandle(sessionId) {
|
|
295
|
+
const raw = String(sessionId || '').trim();
|
|
296
|
+
if (!raw) {
|
|
297
|
+
return 'unknown';
|
|
298
|
+
}
|
|
299
|
+
if (raw.length <= 16) {
|
|
300
|
+
return raw;
|
|
301
|
+
}
|
|
302
|
+
return `${raw.slice(0, 8)}...${raw.slice(-6)}`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function normalizeText(raw) {
|
|
306
|
+
if (raw === null || raw === undefined) {
|
|
307
|
+
return '';
|
|
308
|
+
}
|
|
309
|
+
if (typeof raw === 'string') {
|
|
310
|
+
return raw;
|
|
311
|
+
}
|
|
312
|
+
if (typeof raw === 'number' || typeof raw === 'boolean') {
|
|
313
|
+
return String(raw);
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
return JSON.stringify(raw, null, 2);
|
|
317
|
+
} catch {
|
|
318
|
+
return String(raw);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function safeJsonParse(raw) {
|
|
323
|
+
if (raw === null || raw === undefined) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
if (typeof raw !== 'string') {
|
|
327
|
+
return raw;
|
|
328
|
+
}
|
|
329
|
+
const trimmed = raw.trim();
|
|
330
|
+
if (!trimmed) {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
return JSON.parse(trimmed);
|
|
335
|
+
} catch {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function extractTextContent(value) {
|
|
341
|
+
if (value === null || value === undefined) {
|
|
342
|
+
return '';
|
|
343
|
+
}
|
|
344
|
+
if (typeof value === 'string') {
|
|
345
|
+
return value;
|
|
346
|
+
}
|
|
347
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
348
|
+
return String(value);
|
|
349
|
+
}
|
|
350
|
+
if (Array.isArray(value)) {
|
|
351
|
+
return value
|
|
352
|
+
.map((item) => extractTextContent(item))
|
|
353
|
+
.filter(Boolean)
|
|
354
|
+
.join(' ')
|
|
355
|
+
.trim();
|
|
356
|
+
}
|
|
357
|
+
if (typeof value === 'object') {
|
|
358
|
+
const itemType = String(value.type || '').toLowerCase();
|
|
359
|
+
if (itemType === 'thinking' || itemType === 'signature') {
|
|
360
|
+
return '';
|
|
361
|
+
}
|
|
362
|
+
const parts = [];
|
|
363
|
+
if (value.text !== undefined) {
|
|
364
|
+
parts.push(extractTextContent(value.text));
|
|
365
|
+
}
|
|
366
|
+
if (value.content !== undefined) {
|
|
367
|
+
parts.push(extractTextContent(value.content));
|
|
368
|
+
}
|
|
369
|
+
if (value.parts !== undefined) {
|
|
370
|
+
parts.push(extractTextContent(value.parts));
|
|
371
|
+
}
|
|
372
|
+
if (value.input !== undefined) {
|
|
373
|
+
parts.push(extractTextContent(value.input));
|
|
374
|
+
}
|
|
375
|
+
if (value.arguments !== undefined) {
|
|
376
|
+
parts.push(extractTextContent(value.arguments));
|
|
377
|
+
}
|
|
378
|
+
return parts.filter(Boolean).join(' ').trim();
|
|
379
|
+
}
|
|
380
|
+
return String(value);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function truncateWords(text, maxWords) {
|
|
384
|
+
const words = String(text || '').trim().split(/\s+/).filter(Boolean);
|
|
385
|
+
if (words.length <= maxWords) {
|
|
386
|
+
return words.join(' ');
|
|
387
|
+
}
|
|
388
|
+
return words.slice(0, maxWords).join(' ') + ' …';
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function extractSnippetCandidatesFromRawString(raw) {
|
|
392
|
+
const text = normalizeText(raw);
|
|
393
|
+
if (!text) {
|
|
394
|
+
return [];
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const candidates = [];
|
|
398
|
+
const patterns = [
|
|
399
|
+
/"text"\s*:\s*"((?:\\.|[^"\\])*)"/g,
|
|
400
|
+
/"content"\s*:\s*"((?:\\.|[^"\\])*)"/g,
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
for (const pattern of patterns) {
|
|
404
|
+
let match;
|
|
405
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
406
|
+
const rawValue = match[1];
|
|
407
|
+
if (!rawValue) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
try {
|
|
411
|
+
const decoded = JSON.parse(`"${rawValue}"`);
|
|
412
|
+
const normalized = String(decoded || '').replace(/\u0000/g, '').trim();
|
|
413
|
+
if (normalized) {
|
|
414
|
+
candidates.push(normalized);
|
|
415
|
+
}
|
|
416
|
+
} catch {
|
|
417
|
+
// ignore malformed/truncated snippets
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return candidates;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function buildPromptPreview(raw) {
|
|
426
|
+
const parsed = safeJsonParse(raw);
|
|
427
|
+
let entries = [];
|
|
428
|
+
if (Array.isArray(parsed)) {
|
|
429
|
+
entries = parsed;
|
|
430
|
+
} else if (parsed && typeof parsed === 'object' && Array.isArray(parsed.messages)) {
|
|
431
|
+
entries = parsed.messages;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const snippets = [];
|
|
435
|
+
for (const entry of entries) {
|
|
436
|
+
if (!entry || typeof entry !== 'object') {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
const content = entry.content ?? entry.parts ?? entry.text ?? entry.input;
|
|
440
|
+
const text = extractTextContent(content).replace(/\u0000/g, '').trim();
|
|
441
|
+
if (text) {
|
|
442
|
+
snippets.push(text);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const candidates = snippets.length > 0 ? snippets : extractSnippetCandidatesFromRawString(raw);
|
|
447
|
+
const lastTwo = candidates.slice(-2).map((text) => truncateWords(text, 10));
|
|
448
|
+
return lastTwo.join('\n').trim();
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function makePromptField(raw, budget, fullTexts) {
|
|
452
|
+
const base = makeTextField(raw, budget, fullTexts);
|
|
453
|
+
if (!base) {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const customPreview = buildPromptPreview(raw);
|
|
458
|
+
if (!customPreview) {
|
|
459
|
+
return base;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
...base,
|
|
464
|
+
preview: customPreview,
|
|
465
|
+
previewTruncated: true,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function makeTextField(raw, budget, fullTexts) {
|
|
470
|
+
const text = normalizeText(raw).replace(/\u0000/g, '').trim();
|
|
471
|
+
if (!text) {
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const preview = text.slice(0, TEXT_PREVIEW_CHARS);
|
|
476
|
+
const previewTruncated = text.length > TEXT_PREVIEW_CHARS;
|
|
477
|
+
if (!previewTruncated) {
|
|
478
|
+
return {
|
|
479
|
+
preview,
|
|
480
|
+
previewTruncated: false,
|
|
481
|
+
originalLength: text.length,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (budget.remaining <= 0) {
|
|
486
|
+
return {
|
|
487
|
+
preview,
|
|
488
|
+
previewTruncated: true,
|
|
489
|
+
originalLength: text.length,
|
|
490
|
+
fullUnavailable: true,
|
|
491
|
+
fullUnavailableReason: 'budget',
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const alloc = Math.min(MAX_FULL_TEXT_CHARS_PER_FIELD, text.length, budget.remaining);
|
|
496
|
+
if (alloc <= TEXT_PREVIEW_CHARS) {
|
|
497
|
+
return {
|
|
498
|
+
preview,
|
|
499
|
+
previewTruncated: true,
|
|
500
|
+
originalLength: text.length,
|
|
501
|
+
fullUnavailable: true,
|
|
502
|
+
fullUnavailableReason: 'budget',
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const full = text.slice(0, alloc);
|
|
507
|
+
const fullKey = `t${budget.nextId++}`;
|
|
508
|
+
fullTexts[fullKey] = full;
|
|
509
|
+
budget.remaining -= full.length;
|
|
510
|
+
return {
|
|
511
|
+
preview,
|
|
512
|
+
previewTruncated: true,
|
|
513
|
+
originalLength: text.length,
|
|
514
|
+
fullKey,
|
|
515
|
+
fullLength: full.length,
|
|
516
|
+
fullTruncated: full.length < text.length,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function buildSessionSummariesSql(baseQuery) {
|
|
521
|
+
return `${baseQuery}
|
|
522
|
+
SELECT
|
|
523
|
+
session_id,
|
|
524
|
+
COALESCE(MAX(agent_id), '') AS agent_id,
|
|
525
|
+
ROUND(COALESCE(SUM(cost), 0), 6) AS total_cost,
|
|
526
|
+
COUNT(*) AS llm_calls,
|
|
527
|
+
COUNT(DISTINCT COALESCE(request_id, trace_id)) AS requests,
|
|
528
|
+
COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
|
529
|
+
COALESCE(SUM(output_tokens), 0) AS output_tokens,
|
|
530
|
+
ROUND(COALESCE(AVG(duration_ms), 0), 0) AS avg_latency_ms,
|
|
531
|
+
MIN(start_ms) AS started_ms,
|
|
532
|
+
MAX(start_ms) AS latest_ms
|
|
533
|
+
FROM extracted
|
|
534
|
+
WHERE session_id IS NOT NULL AND session_id != ''
|
|
535
|
+
GROUP BY session_id
|
|
536
|
+
ORDER BY latest_ms DESC
|
|
537
|
+
LIMIT ${SESSION_OVERSCAN};`;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function buildAllSessionsLlmSql(baseQuery) {
|
|
541
|
+
return `${baseQuery}
|
|
542
|
+
, ranked AS (
|
|
543
|
+
SELECT
|
|
544
|
+
session_id,
|
|
545
|
+
trace_id,
|
|
546
|
+
COALESCE(request_id, '') AS request_id,
|
|
547
|
+
start_ms,
|
|
548
|
+
duration_ms,
|
|
549
|
+
COALESCE(model, 'unknown') AS model,
|
|
550
|
+
COALESCE(cost, 0) AS cost,
|
|
551
|
+
COALESCE(input_tokens, 0) AS input_tokens,
|
|
552
|
+
COALESCE(output_tokens, 0) AS output_tokens,
|
|
553
|
+
COALESCE(input_messages, '') AS input_messages,
|
|
554
|
+
COALESCE(output_messages, '') AS output_messages,
|
|
555
|
+
COALESCE(status_code, 0) AS status_code,
|
|
556
|
+
COALESCE(span_name, '') AS span_name,
|
|
557
|
+
ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY start_ms DESC) AS rn
|
|
558
|
+
FROM extracted
|
|
559
|
+
WHERE session_id IN (
|
|
560
|
+
SELECT session_id FROM extracted
|
|
561
|
+
WHERE session_id IS NOT NULL AND session_id != ''
|
|
562
|
+
GROUP BY session_id ORDER BY MAX(start_ms) DESC LIMIT ${SESSION_OVERSCAN}
|
|
563
|
+
)
|
|
564
|
+
)
|
|
565
|
+
SELECT
|
|
566
|
+
session_id, trace_id, request_id, start_ms, duration_ms,
|
|
567
|
+
model, cost, input_tokens, output_tokens,
|
|
568
|
+
input_messages, output_messages, status_code, span_name
|
|
569
|
+
FROM ranked
|
|
570
|
+
WHERE rn <= ${MAX_LLM_ROWS_PER_SESSION}
|
|
571
|
+
ORDER BY session_id, start_ms ASC;`;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function loadSessionEventRows(sessionsDir, sessionId) {
|
|
575
|
+
const sessionPath = path.join(sessionsDir, `${sessionId}.jsonl`);
|
|
576
|
+
if (!fs.existsSync(sessionPath)) {
|
|
577
|
+
return [];
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
try {
|
|
581
|
+
const raw = fs.readFileSync(sessionPath, 'utf8');
|
|
582
|
+
if (!raw.trim()) {
|
|
583
|
+
return [];
|
|
584
|
+
}
|
|
585
|
+
const rows = [];
|
|
586
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
587
|
+
const trimmed = line.trim();
|
|
588
|
+
if (!trimmed) {
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
try {
|
|
592
|
+
const parsed = JSON.parse(trimmed);
|
|
593
|
+
rows.push(parsed);
|
|
594
|
+
} catch {
|
|
595
|
+
// ignore malformed line
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
rows.sort((a, b) => toNumber(a.timestamp) - toNumber(b.timestamp));
|
|
599
|
+
return rows;
|
|
600
|
+
} catch {
|
|
601
|
+
return [];
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function buildToolAndEventData(sessionEventRows, budget, fullTexts) {
|
|
606
|
+
const pendingTools = new Map();
|
|
607
|
+
const toolCalls = [];
|
|
608
|
+
const turnEvents = [];
|
|
609
|
+
const errorEvents = [];
|
|
610
|
+
|
|
611
|
+
let syntheticToolCounter = 0;
|
|
612
|
+
for (const row of sessionEventRows) {
|
|
613
|
+
const eventType = String(row.event_type || '').trim();
|
|
614
|
+
const payload = row && typeof row.payload === 'object' ? row.payload : {};
|
|
615
|
+
const requestId = String(row.request_id || '').trim() || '(unknown)';
|
|
616
|
+
const timestamp = toNumber(row.timestamp);
|
|
617
|
+
if (!Number.isFinite(timestamp) || timestamp <= 0) {
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (eventType === 'turn_start' || eventType === 'turn_end') {
|
|
622
|
+
turnEvents.push({
|
|
623
|
+
type: eventType,
|
|
624
|
+
requestId,
|
|
625
|
+
timestamp,
|
|
626
|
+
});
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (eventType === 'error') {
|
|
631
|
+
const message = makeTextField(payload.message || payload.error || '', budget, fullTexts);
|
|
632
|
+
errorEvents.push({
|
|
633
|
+
type: 'error',
|
|
634
|
+
requestId,
|
|
635
|
+
timestamp,
|
|
636
|
+
message,
|
|
637
|
+
});
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (eventType !== 'tool_start' && eventType !== 'tool_end') {
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const toolName = String(payload.toolName || '(unknown tool)').trim() || '(unknown tool)';
|
|
646
|
+
const callIdRaw = String(payload.callId || '').trim();
|
|
647
|
+
const callId = callIdRaw || `__synthetic_${requestId}_${toolName}_${syntheticToolCounter++}`;
|
|
648
|
+
const key = `${requestId}::${callId}`;
|
|
649
|
+
|
|
650
|
+
if (eventType === 'tool_start') {
|
|
651
|
+
pendingTools.set(key, {
|
|
652
|
+
requestId,
|
|
653
|
+
callId,
|
|
654
|
+
toolName,
|
|
655
|
+
startTimestamp: timestamp,
|
|
656
|
+
input: makeTextField(payload.inputPreview || payload.input || '', budget, fullTexts),
|
|
657
|
+
});
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const started = pendingTools.get(key);
|
|
662
|
+
const resultField = makeTextField(payload.resultPreview || payload.result || '', budget, fullTexts);
|
|
663
|
+
const isError = payload.isError === true || payload.isError === 'true';
|
|
664
|
+
if (started) {
|
|
665
|
+
pendingTools.delete(key);
|
|
666
|
+
toolCalls.push({
|
|
667
|
+
type: 'tool',
|
|
668
|
+
requestId: started.requestId,
|
|
669
|
+
callId: started.callId,
|
|
670
|
+
toolName: started.toolName,
|
|
671
|
+
startTimestamp: started.startTimestamp,
|
|
672
|
+
endTimestamp: timestamp,
|
|
673
|
+
durationMs: Math.max(0, timestamp - started.startTimestamp),
|
|
674
|
+
isError,
|
|
675
|
+
input: started.input,
|
|
676
|
+
result: resultField,
|
|
677
|
+
});
|
|
678
|
+
} else {
|
|
679
|
+
toolCalls.push({
|
|
680
|
+
type: 'tool',
|
|
681
|
+
requestId,
|
|
682
|
+
callId,
|
|
683
|
+
toolName,
|
|
684
|
+
startTimestamp: timestamp,
|
|
685
|
+
endTimestamp: timestamp,
|
|
686
|
+
durationMs: null,
|
|
687
|
+
isError,
|
|
688
|
+
input: null,
|
|
689
|
+
result: resultField,
|
|
690
|
+
orphanedEnd: true,
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
for (const pending of pendingTools.values()) {
|
|
696
|
+
toolCalls.push({
|
|
697
|
+
type: 'tool',
|
|
698
|
+
requestId: pending.requestId,
|
|
699
|
+
callId: pending.callId,
|
|
700
|
+
toolName: pending.toolName,
|
|
701
|
+
startTimestamp: pending.startTimestamp,
|
|
702
|
+
endTimestamp: null,
|
|
703
|
+
durationMs: null,
|
|
704
|
+
isError: false,
|
|
705
|
+
input: pending.input,
|
|
706
|
+
result: null,
|
|
707
|
+
incomplete: true,
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
toolCalls.sort((a, b) => toNumber(a.startTimestamp) - toNumber(b.startTimestamp));
|
|
712
|
+
turnEvents.sort((a, b) => toNumber(a.timestamp) - toNumber(b.timestamp));
|
|
713
|
+
errorEvents.sort((a, b) => toNumber(a.timestamp) - toNumber(b.timestamp));
|
|
714
|
+
|
|
715
|
+
const trimmedToolCalls = toolCalls.length > MAX_TOOL_CALLS_PER_SESSION
|
|
716
|
+
? toolCalls.slice(-MAX_TOOL_CALLS_PER_SESSION)
|
|
717
|
+
: toolCalls;
|
|
718
|
+
const trimmedTurnEvents = turnEvents.length > MAX_TURN_EVENTS_PER_SESSION
|
|
719
|
+
? turnEvents.slice(-MAX_TURN_EVENTS_PER_SESSION)
|
|
720
|
+
: turnEvents;
|
|
721
|
+
const trimmedErrorEvents = errorEvents.length > MAX_ERROR_EVENTS_PER_SESSION
|
|
722
|
+
? errorEvents.slice(-MAX_ERROR_EVENTS_PER_SESSION)
|
|
723
|
+
: errorEvents;
|
|
724
|
+
|
|
725
|
+
return {
|
|
726
|
+
toolCalls: trimmedToolCalls,
|
|
727
|
+
turnEvents: trimmedTurnEvents,
|
|
728
|
+
errorEvents: trimmedErrorEvents,
|
|
729
|
+
truncated: {
|
|
730
|
+
toolCallsDropped: Math.max(0, toolCalls.length - trimmedToolCalls.length),
|
|
731
|
+
turnEventsDropped: Math.max(0, turnEvents.length - trimmedTurnEvents.length),
|
|
732
|
+
errorEventsDropped: Math.max(0, errorEvents.length - trimmedErrorEvents.length),
|
|
733
|
+
},
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function buildRequestGroups(llmRows, toolCalls, turnEvents, errorEvents, budget, fullTexts) {
|
|
738
|
+
const groupMap = new Map();
|
|
739
|
+
const ensureGroup = (requestId) => {
|
|
740
|
+
const normalized = String(requestId || '').trim() || '(unknown)';
|
|
741
|
+
if (!groupMap.has(normalized)) {
|
|
742
|
+
groupMap.set(normalized, {
|
|
743
|
+
requestId: normalized,
|
|
744
|
+
llmCalls: [],
|
|
745
|
+
orphanTools: [],
|
|
746
|
+
turnEvents: [],
|
|
747
|
+
errorEvents: [],
|
|
748
|
+
startTimestamp: null,
|
|
749
|
+
endTimestamp: null,
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
return groupMap.get(normalized);
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
for (const row of llmRows) {
|
|
756
|
+
const requestId = String(row.request_id || '').trim() || '(unknown)';
|
|
757
|
+
const group = ensureGroup(requestId);
|
|
758
|
+
const timestamp = toNumber(row.start_ms);
|
|
759
|
+
const durationMs = toNumber(row.duration_ms);
|
|
760
|
+
const isError = toNumber(row.status_code) === 2 || String(row.span_name || '').includes('Failed');
|
|
761
|
+
const llmCall = {
|
|
762
|
+
type: 'llm',
|
|
763
|
+
traceId: String(row.trace_id || ''),
|
|
764
|
+
requestId,
|
|
765
|
+
timestamp,
|
|
766
|
+
durationMs,
|
|
767
|
+
model: String(row.model || 'unknown'),
|
|
768
|
+
cost: toNumber(row.cost),
|
|
769
|
+
inputTokens: Math.max(0, Math.round(toNumber(row.input_tokens))),
|
|
770
|
+
outputTokens: Math.max(0, Math.round(toNumber(row.output_tokens))),
|
|
771
|
+
isError,
|
|
772
|
+
prompt: makePromptField(row.input_messages, budget, fullTexts),
|
|
773
|
+
completion: makeTextField(row.output_messages, budget, fullTexts),
|
|
774
|
+
tools: [],
|
|
775
|
+
};
|
|
776
|
+
group.llmCalls.push(llmCall);
|
|
777
|
+
group.startTimestamp = group.startTimestamp === null ? timestamp : Math.min(group.startTimestamp, timestamp);
|
|
778
|
+
group.endTimestamp = group.endTimestamp === null
|
|
779
|
+
? timestamp + durationMs
|
|
780
|
+
: Math.max(group.endTimestamp, timestamp + durationMs);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
for (const call of toolCalls) {
|
|
784
|
+
const group = ensureGroup(call.requestId);
|
|
785
|
+
const targetCall = [...group.llmCalls]
|
|
786
|
+
.reverse()
|
|
787
|
+
.find((llm) => llm.timestamp <= toNumber(call.startTimestamp));
|
|
788
|
+
if (targetCall) {
|
|
789
|
+
targetCall.tools.push(call);
|
|
790
|
+
} else {
|
|
791
|
+
group.orphanTools.push(call);
|
|
792
|
+
}
|
|
793
|
+
const start = toNumber(call.startTimestamp);
|
|
794
|
+
const end = call.endTimestamp === null ? start : toNumber(call.endTimestamp);
|
|
795
|
+
group.startTimestamp = group.startTimestamp === null ? start : Math.min(group.startTimestamp, start);
|
|
796
|
+
group.endTimestamp = group.endTimestamp === null ? end : Math.max(group.endTimestamp, end);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
for (const marker of turnEvents) {
|
|
800
|
+
const group = ensureGroup(marker.requestId);
|
|
801
|
+
group.turnEvents.push(marker);
|
|
802
|
+
const ts = toNumber(marker.timestamp);
|
|
803
|
+
group.startTimestamp = group.startTimestamp === null ? ts : Math.min(group.startTimestamp, ts);
|
|
804
|
+
group.endTimestamp = group.endTimestamp === null ? ts : Math.max(group.endTimestamp, ts);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
for (const event of errorEvents) {
|
|
808
|
+
const group = ensureGroup(event.requestId);
|
|
809
|
+
group.errorEvents.push(event);
|
|
810
|
+
const ts = toNumber(event.timestamp);
|
|
811
|
+
group.startTimestamp = group.startTimestamp === null ? ts : Math.min(group.startTimestamp, ts);
|
|
812
|
+
group.endTimestamp = group.endTimestamp === null ? ts : Math.max(group.endTimestamp, ts);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const groups = [...groupMap.values()]
|
|
816
|
+
.map((group) => {
|
|
817
|
+
group.llmCalls.sort((a, b) => a.timestamp - b.timestamp);
|
|
818
|
+
for (const llm of group.llmCalls) {
|
|
819
|
+
llm.tools.sort((a, b) => toNumber(a.startTimestamp) - toNumber(b.startTimestamp));
|
|
820
|
+
}
|
|
821
|
+
group.orphanTools.sort((a, b) => toNumber(a.startTimestamp) - toNumber(b.startTimestamp));
|
|
822
|
+
group.turnEvents.sort((a, b) => toNumber(a.timestamp) - toNumber(b.timestamp));
|
|
823
|
+
group.errorEvents.sort((a, b) => toNumber(a.timestamp) - toNumber(b.timestamp));
|
|
824
|
+
return group;
|
|
825
|
+
})
|
|
826
|
+
.sort((a, b) => toNumber(a.startTimestamp) - toNumber(b.startTimestamp));
|
|
827
|
+
|
|
828
|
+
return groups;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function buildSessionDetails(llmRowsBySession, sessionsDir, activeSessionId, summaryRows) {
|
|
832
|
+
const budget = { remaining: GLOBAL_FULL_TEXT_BUDGET, nextId: 1 };
|
|
833
|
+
const fullTexts = {};
|
|
834
|
+
|
|
835
|
+
const normalizedActive = String(activeSessionId || '').trim();
|
|
836
|
+
const orderedSummaries = [...summaryRows];
|
|
837
|
+
if (normalizedActive) {
|
|
838
|
+
const idx = orderedSummaries.findIndex((s) => String(s.session_id || '').trim() === normalizedActive);
|
|
839
|
+
if (idx > 0) {
|
|
840
|
+
const [active] = orderedSummaries.splice(idx, 1);
|
|
841
|
+
orderedSummaries.unshift(active);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const selected = orderedSummaries.slice(0, SESSION_LIMIT);
|
|
846
|
+
const sessions = [];
|
|
847
|
+
for (const row of selected) {
|
|
848
|
+
const sessionId = String(row.session_id || '').trim();
|
|
849
|
+
if (!sessionId) {
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
const llmRows = llmRowsBySession.get(sessionId) || [];
|
|
853
|
+
const sessionEventRows = loadSessionEventRows(sessionsDir, sessionId);
|
|
854
|
+
const toolBundle = buildToolAndEventData(sessionEventRows, budget, fullTexts);
|
|
855
|
+
const requestGroups = buildRequestGroups(
|
|
856
|
+
llmRows,
|
|
857
|
+
toolBundle.toolCalls,
|
|
858
|
+
toolBundle.turnEvents,
|
|
859
|
+
toolBundle.errorEvents,
|
|
860
|
+
budget,
|
|
861
|
+
fullTexts,
|
|
862
|
+
);
|
|
863
|
+
const isActive = normalizedActive ? sessionId === normalizedActive : sessions.length === 0;
|
|
864
|
+
|
|
865
|
+
sessions.push({
|
|
866
|
+
sessionId,
|
|
867
|
+
handle: sessionHandle(sessionId),
|
|
868
|
+
isActive,
|
|
869
|
+
agentId: String(row.agent_id || ''),
|
|
870
|
+
totalCost: toNumber(row.total_cost),
|
|
871
|
+
llmCalls: Math.max(0, Math.round(toNumber(row.llm_calls))),
|
|
872
|
+
requestCount: Math.max(0, Math.round(toNumber(row.requests))),
|
|
873
|
+
inputTokens: Math.max(0, Math.round(toNumber(row.input_tokens))),
|
|
874
|
+
outputTokens: Math.max(0, Math.round(toNumber(row.output_tokens))),
|
|
875
|
+
avgLatencyMs: Math.max(0, Math.round(toNumber(row.avg_latency_ms))),
|
|
876
|
+
startedMs: Math.round(toNumber(row.started_ms)),
|
|
877
|
+
latestMs: Math.round(toNumber(row.latest_ms)),
|
|
878
|
+
requests: requestGroups,
|
|
879
|
+
timelineStats: {
|
|
880
|
+
toolCalls: toolBundle.toolCalls.length,
|
|
881
|
+
turnEvents: toolBundle.turnEvents.length,
|
|
882
|
+
errorEvents: toolBundle.errorEvents.length,
|
|
883
|
+
llmRows: llmRows.length,
|
|
884
|
+
truncated: {
|
|
885
|
+
llmRowsDropped: llmRows.length >= MAX_LLM_ROWS_PER_SESSION,
|
|
886
|
+
...toolBundle.truncated,
|
|
887
|
+
},
|
|
888
|
+
},
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
return { sessions, fullTexts };
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function buildHtml(payload) {
|
|
896
|
+
const safeJson = JSON.stringify(payload)
|
|
897
|
+
.replace(/</g, '\\u003c')
|
|
898
|
+
.replace(/>/g, '\\u003e')
|
|
899
|
+
.replace(/&/g, '\\u0026')
|
|
900
|
+
.replace(/\u2028/g, '\\u2028')
|
|
901
|
+
.replace(/\u2029/g, '\\u2029');
|
|
902
|
+
|
|
903
|
+
return `<!doctype html>
|
|
904
|
+
<html>
|
|
905
|
+
<head>
|
|
906
|
+
<meta charset="utf-8" />
|
|
907
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
908
|
+
<title>Session Timeline Explorer</title>
|
|
909
|
+
<style>
|
|
910
|
+
:root {
|
|
911
|
+
color-scheme: dark;
|
|
912
|
+
--bg: #0b1220;
|
|
913
|
+
--panel: #10192d;
|
|
914
|
+
--panel-soft: #16233c;
|
|
915
|
+
--text: #e2e8f0;
|
|
916
|
+
--muted: #94a3b8;
|
|
917
|
+
--border: #1f2d44;
|
|
918
|
+
--accent: #3b82f6;
|
|
919
|
+
--good: #10b981;
|
|
920
|
+
--warn: #f59e0b;
|
|
921
|
+
--bad: #ef4444;
|
|
922
|
+
}
|
|
923
|
+
* { box-sizing: border-box; }
|
|
924
|
+
body {
|
|
925
|
+
margin: 0;
|
|
926
|
+
font: 13px/1.45 ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
|
927
|
+
background: var(--bg);
|
|
928
|
+
color: var(--text);
|
|
929
|
+
}
|
|
930
|
+
.root {
|
|
931
|
+
display: grid;
|
|
932
|
+
grid-template-columns: minmax(240px, 300px) 1fr;
|
|
933
|
+
min-height: 96vh;
|
|
934
|
+
}
|
|
935
|
+
.sidebar {
|
|
936
|
+
border-right: 1px solid var(--border);
|
|
937
|
+
background: #0e172a;
|
|
938
|
+
padding: 12px;
|
|
939
|
+
display: flex;
|
|
940
|
+
flex-direction: column;
|
|
941
|
+
gap: 10px;
|
|
942
|
+
}
|
|
943
|
+
.title {
|
|
944
|
+
font-weight: 700;
|
|
945
|
+
font-size: 14px;
|
|
946
|
+
letter-spacing: 0.1px;
|
|
947
|
+
}
|
|
948
|
+
.title-row {
|
|
949
|
+
display: flex;
|
|
950
|
+
align-items: center;
|
|
951
|
+
justify-content: space-between;
|
|
952
|
+
gap: 8px;
|
|
953
|
+
}
|
|
954
|
+
.pill {
|
|
955
|
+
display: inline-flex;
|
|
956
|
+
align-items: center;
|
|
957
|
+
border: 1px solid #24467b;
|
|
958
|
+
background: #132341;
|
|
959
|
+
color: #93c5fd;
|
|
960
|
+
border-radius: 999px;
|
|
961
|
+
padding: 2px 8px;
|
|
962
|
+
font-size: 11px;
|
|
963
|
+
white-space: nowrap;
|
|
964
|
+
}
|
|
965
|
+
.search {
|
|
966
|
+
width: 100%;
|
|
967
|
+
background: #0a1324;
|
|
968
|
+
border: 1px solid var(--border);
|
|
969
|
+
border-radius: 8px;
|
|
970
|
+
color: var(--text);
|
|
971
|
+
padding: 8px 10px;
|
|
972
|
+
outline: none;
|
|
973
|
+
}
|
|
974
|
+
.search:focus { border-color: var(--accent); }
|
|
975
|
+
.session-list {
|
|
976
|
+
display: grid;
|
|
977
|
+
gap: 8px;
|
|
978
|
+
overflow: auto;
|
|
979
|
+
padding-right: 2px;
|
|
980
|
+
min-height: 120px;
|
|
981
|
+
}
|
|
982
|
+
.session-item {
|
|
983
|
+
border: 1px solid var(--border);
|
|
984
|
+
background: var(--panel);
|
|
985
|
+
border-radius: 10px;
|
|
986
|
+
padding: 9px;
|
|
987
|
+
cursor: pointer;
|
|
988
|
+
transition: border-color .12s ease, background .12s ease;
|
|
989
|
+
}
|
|
990
|
+
.session-item:hover {
|
|
991
|
+
border-color: #34588c;
|
|
992
|
+
background: #152440;
|
|
993
|
+
}
|
|
994
|
+
.session-item.active {
|
|
995
|
+
border-color: var(--accent);
|
|
996
|
+
background: #152a4a;
|
|
997
|
+
}
|
|
998
|
+
.session-head {
|
|
999
|
+
display: flex;
|
|
1000
|
+
align-items: center;
|
|
1001
|
+
justify-content: space-between;
|
|
1002
|
+
gap: 8px;
|
|
1003
|
+
margin-bottom: 4px;
|
|
1004
|
+
}
|
|
1005
|
+
.session-id {
|
|
1006
|
+
font-weight: 600;
|
|
1007
|
+
max-width: 75%;
|
|
1008
|
+
overflow: hidden;
|
|
1009
|
+
text-overflow: ellipsis;
|
|
1010
|
+
white-space: nowrap;
|
|
1011
|
+
}
|
|
1012
|
+
.live-badge {
|
|
1013
|
+
border: 1px solid #14532d;
|
|
1014
|
+
color: #86efac;
|
|
1015
|
+
background: #052e16;
|
|
1016
|
+
font-size: 10px;
|
|
1017
|
+
border-radius: 999px;
|
|
1018
|
+
padding: 1px 7px;
|
|
1019
|
+
font-weight: 700;
|
|
1020
|
+
}
|
|
1021
|
+
.session-meta {
|
|
1022
|
+
color: var(--muted);
|
|
1023
|
+
font-size: 11px;
|
|
1024
|
+
display: flex;
|
|
1025
|
+
gap: 8px;
|
|
1026
|
+
flex-wrap: wrap;
|
|
1027
|
+
}
|
|
1028
|
+
.main {
|
|
1029
|
+
padding: 14px;
|
|
1030
|
+
display: grid;
|
|
1031
|
+
gap: 10px;
|
|
1032
|
+
min-width: 0;
|
|
1033
|
+
}
|
|
1034
|
+
.session-header {
|
|
1035
|
+
border: 1px solid var(--border);
|
|
1036
|
+
background: var(--panel);
|
|
1037
|
+
border-radius: 12px;
|
|
1038
|
+
padding: 12px;
|
|
1039
|
+
display: grid;
|
|
1040
|
+
gap: 8px;
|
|
1041
|
+
}
|
|
1042
|
+
.session-topline {
|
|
1043
|
+
display: flex;
|
|
1044
|
+
justify-content: space-between;
|
|
1045
|
+
align-items: center;
|
|
1046
|
+
gap: 10px;
|
|
1047
|
+
flex-wrap: wrap;
|
|
1048
|
+
}
|
|
1049
|
+
.session-title {
|
|
1050
|
+
display: flex;
|
|
1051
|
+
align-items: center;
|
|
1052
|
+
gap: 8px;
|
|
1053
|
+
font-weight: 700;
|
|
1054
|
+
min-width: 0;
|
|
1055
|
+
}
|
|
1056
|
+
.session-title code {
|
|
1057
|
+
background: #0b1730;
|
|
1058
|
+
border: 1px solid #22375a;
|
|
1059
|
+
padding: 2px 6px;
|
|
1060
|
+
border-radius: 6px;
|
|
1061
|
+
max-width: 100%;
|
|
1062
|
+
overflow: hidden;
|
|
1063
|
+
text-overflow: ellipsis;
|
|
1064
|
+
white-space: nowrap;
|
|
1065
|
+
}
|
|
1066
|
+
.copy-btn {
|
|
1067
|
+
background: #10203a;
|
|
1068
|
+
border: 1px solid #28446f;
|
|
1069
|
+
color: #bfdbfe;
|
|
1070
|
+
border-radius: 8px;
|
|
1071
|
+
padding: 5px 10px;
|
|
1072
|
+
font-size: 11px;
|
|
1073
|
+
cursor: pointer;
|
|
1074
|
+
}
|
|
1075
|
+
.copy-btn:hover { border-color: #3b82f6; }
|
|
1076
|
+
.stats {
|
|
1077
|
+
display: grid;
|
|
1078
|
+
grid-template-columns: repeat(5, minmax(100px, 1fr));
|
|
1079
|
+
gap: 8px;
|
|
1080
|
+
}
|
|
1081
|
+
.stat {
|
|
1082
|
+
border: 1px solid var(--border);
|
|
1083
|
+
border-radius: 8px;
|
|
1084
|
+
background: #0f1b30;
|
|
1085
|
+
padding: 8px;
|
|
1086
|
+
min-width: 0;
|
|
1087
|
+
}
|
|
1088
|
+
.stat-label {
|
|
1089
|
+
color: var(--muted);
|
|
1090
|
+
font-size: 11px;
|
|
1091
|
+
margin-bottom: 3px;
|
|
1092
|
+
}
|
|
1093
|
+
.stat-value {
|
|
1094
|
+
font-weight: 700;
|
|
1095
|
+
font-size: 14px;
|
|
1096
|
+
overflow: hidden;
|
|
1097
|
+
text-overflow: ellipsis;
|
|
1098
|
+
white-space: nowrap;
|
|
1099
|
+
}
|
|
1100
|
+
.warn-line {
|
|
1101
|
+
color: #fcd34d;
|
|
1102
|
+
font-size: 11px;
|
|
1103
|
+
border: 1px solid rgba(245, 158, 11, 0.45);
|
|
1104
|
+
background: rgba(146, 64, 14, 0.35);
|
|
1105
|
+
border-radius: 8px;
|
|
1106
|
+
padding: 6px 8px;
|
|
1107
|
+
display: none;
|
|
1108
|
+
}
|
|
1109
|
+
.session-growth {
|
|
1110
|
+
border: 1px solid var(--border);
|
|
1111
|
+
border-radius: 10px;
|
|
1112
|
+
background: #0d172b;
|
|
1113
|
+
padding: 8px;
|
|
1114
|
+
display: grid;
|
|
1115
|
+
gap: 7px;
|
|
1116
|
+
}
|
|
1117
|
+
.session-growth-head {
|
|
1118
|
+
display: flex;
|
|
1119
|
+
align-items: center;
|
|
1120
|
+
justify-content: space-between;
|
|
1121
|
+
gap: 8px;
|
|
1122
|
+
flex-wrap: wrap;
|
|
1123
|
+
}
|
|
1124
|
+
.session-growth-title {
|
|
1125
|
+
font-size: 12px;
|
|
1126
|
+
font-weight: 700;
|
|
1127
|
+
color: #bfdbfe;
|
|
1128
|
+
}
|
|
1129
|
+
.session-growth-hint {
|
|
1130
|
+
color: var(--muted);
|
|
1131
|
+
font-size: 10px;
|
|
1132
|
+
}
|
|
1133
|
+
.session-growth svg {
|
|
1134
|
+
width: 100%;
|
|
1135
|
+
height: 210px;
|
|
1136
|
+
display: block;
|
|
1137
|
+
border-radius: 8px;
|
|
1138
|
+
border: 1px solid rgba(148, 163, 184, 0.26);
|
|
1139
|
+
background: #081325;
|
|
1140
|
+
}
|
|
1141
|
+
.growth-meta {
|
|
1142
|
+
color: var(--muted);
|
|
1143
|
+
font-size: 11px;
|
|
1144
|
+
min-height: 16px;
|
|
1145
|
+
word-break: break-word;
|
|
1146
|
+
}
|
|
1147
|
+
.timeline {
|
|
1148
|
+
display: grid;
|
|
1149
|
+
gap: 10px;
|
|
1150
|
+
min-width: 0;
|
|
1151
|
+
}
|
|
1152
|
+
.request {
|
|
1153
|
+
border: 1px solid var(--border);
|
|
1154
|
+
border-radius: 12px;
|
|
1155
|
+
background: #0f182b;
|
|
1156
|
+
padding: 10px;
|
|
1157
|
+
display: grid;
|
|
1158
|
+
gap: 8px;
|
|
1159
|
+
}
|
|
1160
|
+
.request.focused {
|
|
1161
|
+
border-color: #60a5fa;
|
|
1162
|
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.25);
|
|
1163
|
+
}
|
|
1164
|
+
.request-head {
|
|
1165
|
+
display: flex;
|
|
1166
|
+
justify-content: space-between;
|
|
1167
|
+
align-items: center;
|
|
1168
|
+
gap: 8px;
|
|
1169
|
+
flex-wrap: wrap;
|
|
1170
|
+
border-bottom: 1px solid rgba(148, 163, 184, 0.18);
|
|
1171
|
+
padding-bottom: 6px;
|
|
1172
|
+
margin-bottom: 2px;
|
|
1173
|
+
}
|
|
1174
|
+
.request-id {
|
|
1175
|
+
font-weight: 600;
|
|
1176
|
+
color: #bfdbfe;
|
|
1177
|
+
}
|
|
1178
|
+
.request-meta {
|
|
1179
|
+
color: var(--muted);
|
|
1180
|
+
font-size: 11px;
|
|
1181
|
+
display: flex;
|
|
1182
|
+
gap: 7px;
|
|
1183
|
+
flex-wrap: wrap;
|
|
1184
|
+
}
|
|
1185
|
+
.llm-card {
|
|
1186
|
+
border: 1px solid #25406a;
|
|
1187
|
+
border-radius: 10px;
|
|
1188
|
+
background: #10203a;
|
|
1189
|
+
padding: 9px;
|
|
1190
|
+
display: grid;
|
|
1191
|
+
gap: 7px;
|
|
1192
|
+
}
|
|
1193
|
+
.llm-card.focused {
|
|
1194
|
+
border-color: #60a5fa;
|
|
1195
|
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.25);
|
|
1196
|
+
}
|
|
1197
|
+
.llm-card.error {
|
|
1198
|
+
border-color: rgba(239, 68, 68, 0.7);
|
|
1199
|
+
background: rgba(69, 19, 19, 0.45);
|
|
1200
|
+
}
|
|
1201
|
+
.llm-head {
|
|
1202
|
+
display: flex;
|
|
1203
|
+
justify-content: space-between;
|
|
1204
|
+
align-items: center;
|
|
1205
|
+
gap: 8px;
|
|
1206
|
+
flex-wrap: wrap;
|
|
1207
|
+
}
|
|
1208
|
+
.model-pill {
|
|
1209
|
+
border: 1px solid #31598e;
|
|
1210
|
+
border-radius: 999px;
|
|
1211
|
+
background: #142949;
|
|
1212
|
+
color: #dbeafe;
|
|
1213
|
+
padding: 2px 8px;
|
|
1214
|
+
font-size: 11px;
|
|
1215
|
+
max-width: 100%;
|
|
1216
|
+
overflow: hidden;
|
|
1217
|
+
text-overflow: ellipsis;
|
|
1218
|
+
white-space: nowrap;
|
|
1219
|
+
}
|
|
1220
|
+
.llm-metrics {
|
|
1221
|
+
color: var(--muted);
|
|
1222
|
+
font-size: 11px;
|
|
1223
|
+
display: flex;
|
|
1224
|
+
gap: 7px;
|
|
1225
|
+
flex-wrap: wrap;
|
|
1226
|
+
}
|
|
1227
|
+
.error-pill {
|
|
1228
|
+
border: 1px solid #7f1d1d;
|
|
1229
|
+
background: #450a0a;
|
|
1230
|
+
color: #fca5a5;
|
|
1231
|
+
border-radius: 999px;
|
|
1232
|
+
font-size: 10px;
|
|
1233
|
+
padding: 2px 7px;
|
|
1234
|
+
font-weight: 700;
|
|
1235
|
+
}
|
|
1236
|
+
.tool-list {
|
|
1237
|
+
display: grid;
|
|
1238
|
+
gap: 6px;
|
|
1239
|
+
margin-left: 14px;
|
|
1240
|
+
border-left: 2px solid rgba(148, 163, 184, 0.2);
|
|
1241
|
+
padding-left: 10px;
|
|
1242
|
+
}
|
|
1243
|
+
.tool-item {
|
|
1244
|
+
border: 1px solid var(--border);
|
|
1245
|
+
background: var(--panel-soft);
|
|
1246
|
+
border-radius: 8px;
|
|
1247
|
+
padding: 7px;
|
|
1248
|
+
display: grid;
|
|
1249
|
+
gap: 5px;
|
|
1250
|
+
}
|
|
1251
|
+
.tool-head {
|
|
1252
|
+
display: flex;
|
|
1253
|
+
justify-content: space-between;
|
|
1254
|
+
gap: 8px;
|
|
1255
|
+
align-items: center;
|
|
1256
|
+
flex-wrap: wrap;
|
|
1257
|
+
}
|
|
1258
|
+
.tool-name { font-weight: 600; color: #cbd5e1; }
|
|
1259
|
+
.tool-meta {
|
|
1260
|
+
color: var(--muted);
|
|
1261
|
+
font-size: 11px;
|
|
1262
|
+
display: flex;
|
|
1263
|
+
gap: 7px;
|
|
1264
|
+
flex-wrap: wrap;
|
|
1265
|
+
}
|
|
1266
|
+
.tool-status {
|
|
1267
|
+
border-radius: 999px;
|
|
1268
|
+
font-size: 10px;
|
|
1269
|
+
padding: 1px 7px;
|
|
1270
|
+
font-weight: 700;
|
|
1271
|
+
}
|
|
1272
|
+
.tool-status.ok {
|
|
1273
|
+
color: #86efac;
|
|
1274
|
+
border: 1px solid #14532d;
|
|
1275
|
+
background: #052e16;
|
|
1276
|
+
}
|
|
1277
|
+
.tool-status.err {
|
|
1278
|
+
color: #fecaca;
|
|
1279
|
+
border: 1px solid #7f1d1d;
|
|
1280
|
+
background: #450a0a;
|
|
1281
|
+
}
|
|
1282
|
+
.tool-status.running {
|
|
1283
|
+
color: #fde68a;
|
|
1284
|
+
border: 1px solid #78350f;
|
|
1285
|
+
background: #422006;
|
|
1286
|
+
}
|
|
1287
|
+
.text-block {
|
|
1288
|
+
border: 1px solid rgba(148, 163, 184, 0.25);
|
|
1289
|
+
border-radius: 8px;
|
|
1290
|
+
background: #0b1628;
|
|
1291
|
+
padding: 7px;
|
|
1292
|
+
display: grid;
|
|
1293
|
+
gap: 6px;
|
|
1294
|
+
}
|
|
1295
|
+
.text-head {
|
|
1296
|
+
display: flex;
|
|
1297
|
+
justify-content: space-between;
|
|
1298
|
+
align-items: center;
|
|
1299
|
+
gap: 8px;
|
|
1300
|
+
color: #cbd5e1;
|
|
1301
|
+
font-size: 11px;
|
|
1302
|
+
}
|
|
1303
|
+
.text-lens {
|
|
1304
|
+
color: var(--muted);
|
|
1305
|
+
font-size: 10px;
|
|
1306
|
+
}
|
|
1307
|
+
pre {
|
|
1308
|
+
margin: 0;
|
|
1309
|
+
white-space: pre-wrap;
|
|
1310
|
+
word-break: break-word;
|
|
1311
|
+
overflow-wrap: anywhere;
|
|
1312
|
+
max-height: 240px;
|
|
1313
|
+
overflow: auto;
|
|
1314
|
+
padding: 8px;
|
|
1315
|
+
border-radius: 6px;
|
|
1316
|
+
border: 1px solid rgba(148, 163, 184, 0.2);
|
|
1317
|
+
background: #070f1d;
|
|
1318
|
+
color: #dbeafe;
|
|
1319
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
1320
|
+
font-size: 11px;
|
|
1321
|
+
line-height: 1.42;
|
|
1322
|
+
}
|
|
1323
|
+
pre.full {
|
|
1324
|
+
max-height: 460px;
|
|
1325
|
+
border-color: rgba(59, 130, 246, 0.45);
|
|
1326
|
+
background: #050d1a;
|
|
1327
|
+
margin-top: 2px;
|
|
1328
|
+
}
|
|
1329
|
+
.toggle-btn {
|
|
1330
|
+
justify-self: start;
|
|
1331
|
+
font-size: 11px;
|
|
1332
|
+
border-radius: 7px;
|
|
1333
|
+
border: 1px solid #2c4f84;
|
|
1334
|
+
background: #112645;
|
|
1335
|
+
color: #bfdbfe;
|
|
1336
|
+
padding: 4px 9px;
|
|
1337
|
+
cursor: pointer;
|
|
1338
|
+
}
|
|
1339
|
+
.toggle-btn:hover { border-color: #3b82f6; }
|
|
1340
|
+
.note {
|
|
1341
|
+
color: #fcd34d;
|
|
1342
|
+
font-size: 10px;
|
|
1343
|
+
}
|
|
1344
|
+
.marker-row,
|
|
1345
|
+
.error-row {
|
|
1346
|
+
border: 1px dashed rgba(148, 163, 184, 0.4);
|
|
1347
|
+
border-radius: 8px;
|
|
1348
|
+
padding: 6px 8px;
|
|
1349
|
+
font-size: 11px;
|
|
1350
|
+
color: #cbd5e1;
|
|
1351
|
+
background: rgba(15, 23, 42, 0.55);
|
|
1352
|
+
}
|
|
1353
|
+
.error-row {
|
|
1354
|
+
border-color: rgba(239, 68, 68, 0.55);
|
|
1355
|
+
background: rgba(69, 10, 10, 0.35);
|
|
1356
|
+
color: #fecaca;
|
|
1357
|
+
}
|
|
1358
|
+
.empty {
|
|
1359
|
+
border: 1px dashed var(--border);
|
|
1360
|
+
border-radius: 10px;
|
|
1361
|
+
color: var(--muted);
|
|
1362
|
+
padding: 16px;
|
|
1363
|
+
text-align: center;
|
|
1364
|
+
background: #0d172b;
|
|
1365
|
+
}
|
|
1366
|
+
.meta-line {
|
|
1367
|
+
color: var(--muted);
|
|
1368
|
+
font-size: 11px;
|
|
1369
|
+
}
|
|
1370
|
+
@media (max-width: 960px) {
|
|
1371
|
+
.root { grid-template-columns: 1fr; }
|
|
1372
|
+
.sidebar { border-right: none; border-bottom: 1px solid var(--border); max-height: 38vh; }
|
|
1373
|
+
.stats { grid-template-columns: repeat(2, minmax(110px, 1fr)); }
|
|
1374
|
+
.session-growth svg { height: 190px; }
|
|
1375
|
+
}
|
|
1376
|
+
</style>
|
|
1377
|
+
</head>
|
|
1378
|
+
<body>
|
|
1379
|
+
<div class="root">
|
|
1380
|
+
<aside class="sidebar">
|
|
1381
|
+
<div class="title-row">
|
|
1382
|
+
<div class="title">Session Timeline Explorer</div>
|
|
1383
|
+
<div class="pill">Interactive HTML</div>
|
|
1384
|
+
</div>
|
|
1385
|
+
<input id="session-search" class="search" placeholder="Search session id / agent" />
|
|
1386
|
+
<div id="session-list" class="session-list"></div>
|
|
1387
|
+
<div class="meta-line" id="footer-meta"></div>
|
|
1388
|
+
</aside>
|
|
1389
|
+
<main class="main">
|
|
1390
|
+
<section class="session-header">
|
|
1391
|
+
<div class="session-topline">
|
|
1392
|
+
<div id="session-title" class="session-title"></div>
|
|
1393
|
+
<button id="copy-session-btn" class="copy-btn" type="button">Copy session id</button>
|
|
1394
|
+
</div>
|
|
1395
|
+
<div class="stats">
|
|
1396
|
+
<div class="stat"><div class="stat-label">Cost</div><div class="stat-value" id="stat-cost">-</div></div>
|
|
1397
|
+
<div class="stat"><div class="stat-label">LLM calls</div><div class="stat-value" id="stat-llm">-</div></div>
|
|
1398
|
+
<div class="stat"><div class="stat-label">Requests</div><div class="stat-value" id="stat-requests">-</div></div>
|
|
1399
|
+
<div class="stat"><div class="stat-label">Tokens</div><div class="stat-value" id="stat-tokens">-</div></div>
|
|
1400
|
+
<div class="stat"><div class="stat-label">Avg latency</div><div class="stat-value" id="stat-latency">-</div></div>
|
|
1401
|
+
</div>
|
|
1402
|
+
<div class="session-growth">
|
|
1403
|
+
<div class="session-growth-head">
|
|
1404
|
+
<div class="session-growth-title">Cumulative Token/Cost by Turn</div>
|
|
1405
|
+
<div class="session-growth-hint">Click a point to jump to its request in timeline</div>
|
|
1406
|
+
</div>
|
|
1407
|
+
<svg id="session-growth-chart" role="img" aria-label="Cumulative token and cost chart"></svg>
|
|
1408
|
+
<div id="growth-meta" class="growth-meta"></div>
|
|
1409
|
+
</div>
|
|
1410
|
+
<div id="warn-line" class="warn-line"></div>
|
|
1411
|
+
</section>
|
|
1412
|
+
<section id="timeline" class="timeline"></section>
|
|
1413
|
+
</main>
|
|
1414
|
+
</div>
|
|
1415
|
+
<script>
|
|
1416
|
+
const data = ${safeJson};
|
|
1417
|
+
const listEl = document.getElementById('session-list');
|
|
1418
|
+
const timelineEl = document.getElementById('timeline');
|
|
1419
|
+
const titleEl = document.getElementById('session-title');
|
|
1420
|
+
const footerMetaEl = document.getElementById('footer-meta');
|
|
1421
|
+
const warnLineEl = document.getElementById('warn-line');
|
|
1422
|
+
const searchEl = document.getElementById('session-search');
|
|
1423
|
+
const copyBtn = document.getElementById('copy-session-btn');
|
|
1424
|
+
const statCostEl = document.getElementById('stat-cost');
|
|
1425
|
+
const statLlmEl = document.getElementById('stat-llm');
|
|
1426
|
+
const statRequestsEl = document.getElementById('stat-requests');
|
|
1427
|
+
const statTokensEl = document.getElementById('stat-tokens');
|
|
1428
|
+
const statLatencyEl = document.getElementById('stat-latency');
|
|
1429
|
+
const growthChartEl = document.getElementById('session-growth-chart');
|
|
1430
|
+
const growthMetaEl = document.getElementById('growth-meta');
|
|
1431
|
+
|
|
1432
|
+
const sessions = Array.isArray(data.sessions) ? data.sessions : [];
|
|
1433
|
+
const fullTexts = data.fullTexts || {};
|
|
1434
|
+
const clipboardPending = new Map();
|
|
1435
|
+
const state = {
|
|
1436
|
+
search: '',
|
|
1437
|
+
selectedId: (() => {
|
|
1438
|
+
const active = sessions.find((item) => item && item.isActive);
|
|
1439
|
+
return active ? active.sessionId : (sessions[0] ? sessions[0].sessionId : '');
|
|
1440
|
+
})(),
|
|
1441
|
+
selectedGrowthSessionId: '',
|
|
1442
|
+
selectedGrowthIndex: -1,
|
|
1443
|
+
};
|
|
1444
|
+
|
|
1445
|
+
const esc = (v) => String(v ?? '')
|
|
1446
|
+
.replaceAll('&', '&')
|
|
1447
|
+
.replaceAll('<', '<')
|
|
1448
|
+
.replaceAll('>', '>')
|
|
1449
|
+
.replaceAll('"', '"')
|
|
1450
|
+
.replaceAll("'", ''');
|
|
1451
|
+
|
|
1452
|
+
const fmtMoney = (n) => '$' + Number(n || 0).toFixed(4);
|
|
1453
|
+
const fmtNum = (n) => Number(n || 0).toLocaleString();
|
|
1454
|
+
const toNum = (n) => {
|
|
1455
|
+
const value = Number(n || 0);
|
|
1456
|
+
return Number.isFinite(value) ? value : 0;
|
|
1457
|
+
};
|
|
1458
|
+
const fmtDuration = (ms) => {
|
|
1459
|
+
const value = Number(ms || 0);
|
|
1460
|
+
if (!Number.isFinite(value) || value <= 0) return '-';
|
|
1461
|
+
if (value < 1000) return Math.round(value) + 'ms';
|
|
1462
|
+
return (value / 1000).toFixed(2) + 's';
|
|
1463
|
+
};
|
|
1464
|
+
const fmtWhen = (ms) => {
|
|
1465
|
+
const value = Number(ms || 0);
|
|
1466
|
+
if (!Number.isFinite(value) || value <= 0) return '-';
|
|
1467
|
+
const date = new Date(value);
|
|
1468
|
+
return date.toLocaleString();
|
|
1469
|
+
};
|
|
1470
|
+
const fmtAgo = (ms) => {
|
|
1471
|
+
const value = Number(ms || 0);
|
|
1472
|
+
if (!Number.isFinite(value) || value <= 0) return '-';
|
|
1473
|
+
const deltaSec = Math.floor((Date.now() - value) / 1000);
|
|
1474
|
+
if (deltaSec < 60) return 'just now';
|
|
1475
|
+
if (deltaSec < 3600) return Math.floor(deltaSec / 60) + 'm ago';
|
|
1476
|
+
if (deltaSec < 86400) return Math.floor(deltaSec / 3600) + 'h ago';
|
|
1477
|
+
return Math.floor(deltaSec / 86400) + 'd ago';
|
|
1478
|
+
};
|
|
1479
|
+
|
|
1480
|
+
const postCopySessionId = (sessionId) => {
|
|
1481
|
+
if (!sessionId) return;
|
|
1482
|
+
window.parent.postMessage({
|
|
1483
|
+
source: 'agentide-wow-widget',
|
|
1484
|
+
type: 'copySessionId',
|
|
1485
|
+
sessionId,
|
|
1486
|
+
}, '*');
|
|
1487
|
+
copyBtn.textContent = 'Copied';
|
|
1488
|
+
setTimeout(() => {
|
|
1489
|
+
copyBtn.textContent = 'Copy session id';
|
|
1490
|
+
}, 1200);
|
|
1491
|
+
};
|
|
1492
|
+
|
|
1493
|
+
const requestClipboardText = () => new Promise((resolve) => {
|
|
1494
|
+
const requestId = 'clip-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8);
|
|
1495
|
+
const timer = setTimeout(() => {
|
|
1496
|
+
clipboardPending.delete(requestId);
|
|
1497
|
+
resolve('');
|
|
1498
|
+
}, 1500);
|
|
1499
|
+
clipboardPending.set(requestId, { resolve, timer });
|
|
1500
|
+
window.parent.postMessage({
|
|
1501
|
+
source: 'agentide-wow-widget',
|
|
1502
|
+
type: 'requestClipboardText',
|
|
1503
|
+
requestId,
|
|
1504
|
+
}, '*');
|
|
1505
|
+
});
|
|
1506
|
+
|
|
1507
|
+
window.addEventListener('message', (event) => {
|
|
1508
|
+
const payload = event.data;
|
|
1509
|
+
if (!payload || payload.source !== 'agentide-panel' || payload.type !== 'clipboardText') {
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
const entry = clipboardPending.get(payload.requestId);
|
|
1513
|
+
if (!entry) {
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
clearTimeout(entry.timer);
|
|
1517
|
+
clipboardPending.delete(payload.requestId);
|
|
1518
|
+
entry.resolve(typeof payload.text === 'string' ? payload.text : '');
|
|
1519
|
+
});
|
|
1520
|
+
|
|
1521
|
+
const insertAtCursor = (input, text) => {
|
|
1522
|
+
const start = Number.isFinite(input.selectionStart) ? input.selectionStart : input.value.length;
|
|
1523
|
+
const end = Number.isFinite(input.selectionEnd) ? input.selectionEnd : input.value.length;
|
|
1524
|
+
const before = input.value.slice(0, start);
|
|
1525
|
+
const after = input.value.slice(end);
|
|
1526
|
+
input.value = before + text + after;
|
|
1527
|
+
const caret = start + text.length;
|
|
1528
|
+
input.selectionStart = caret;
|
|
1529
|
+
input.selectionEnd = caret;
|
|
1530
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1531
|
+
};
|
|
1532
|
+
|
|
1533
|
+
searchEl?.addEventListener('keydown', async (event) => {
|
|
1534
|
+
if (event.defaultPrevented) {
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
const isPasteShortcut = (event.metaKey || event.ctrlKey) && String(event.key).toLowerCase() === 'v';
|
|
1538
|
+
if (!isPasteShortcut) {
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
event.preventDefault();
|
|
1542
|
+
const text = await requestClipboardText();
|
|
1543
|
+
if (!text) {
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
insertAtCursor(searchEl, text);
|
|
1547
|
+
});
|
|
1548
|
+
|
|
1549
|
+
searchEl?.addEventListener('paste', async (event) => {
|
|
1550
|
+
if (event.defaultPrevented) {
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
if (event.clipboardData?.getData('text')) {
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
event.preventDefault();
|
|
1557
|
+
const text = await requestClipboardText();
|
|
1558
|
+
if (!text) {
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
insertAtCursor(searchEl, text);
|
|
1562
|
+
});
|
|
1563
|
+
|
|
1564
|
+
function getFilteredSessions() {
|
|
1565
|
+
const query = state.search.trim().toLowerCase();
|
|
1566
|
+
if (!query) return sessions;
|
|
1567
|
+
return sessions.filter((session) => {
|
|
1568
|
+
const sid = String(session.sessionId || '').toLowerCase();
|
|
1569
|
+
const agent = String(session.agentId || '').toLowerCase();
|
|
1570
|
+
return sid.includes(query) || agent.includes(query);
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
function renderSessionList() {
|
|
1575
|
+
const filtered = getFilteredSessions();
|
|
1576
|
+
if (filtered.length === 0) {
|
|
1577
|
+
listEl.innerHTML = '<div class="empty">No matching sessions.</div>';
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
listEl.innerHTML = filtered.map((session) => {
|
|
1581
|
+
const activeClass = session.sessionId === state.selectedId ? ' active' : '';
|
|
1582
|
+
const liveBadge = session.isActive ? '<span class="live-badge">LIVE</span>' : '';
|
|
1583
|
+
return '<button type="button" class="session-item' + activeClass + '" data-session-id="' + esc(session.sessionId) + '">' +
|
|
1584
|
+
'<div class="session-head">' +
|
|
1585
|
+
'<div class="session-id" title="' + esc(session.sessionId) + '">' + esc(session.handle || session.sessionId) + '</div>' +
|
|
1586
|
+
liveBadge +
|
|
1587
|
+
'</div>' +
|
|
1588
|
+
'<div class="session-meta">' +
|
|
1589
|
+
'<span>' + fmtMoney(session.totalCost) + '</span>' +
|
|
1590
|
+
'<span>' + fmtNum(session.llmCalls) + ' LLM</span>' +
|
|
1591
|
+
'<span>' + fmtNum(session.requestCount) + ' req</span>' +
|
|
1592
|
+
'<span>' + esc(fmtAgo(session.latestMs)) + '</span>' +
|
|
1593
|
+
'</div>' +
|
|
1594
|
+
'</button>';
|
|
1595
|
+
}).join('');
|
|
1596
|
+
|
|
1597
|
+
listEl.querySelectorAll('.session-item').forEach((item) => {
|
|
1598
|
+
item.addEventListener('click', () => {
|
|
1599
|
+
const sid = item.getAttribute('data-session-id') || '';
|
|
1600
|
+
if (!sid) return;
|
|
1601
|
+
state.selectedId = sid;
|
|
1602
|
+
render();
|
|
1603
|
+
});
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
function renderTextBlock(label, field, prefix) {
|
|
1608
|
+
if (!field || !field.preview) return '';
|
|
1609
|
+
const preview = esc(field.preview + (field.previewTruncated ? '\\n... (preview)' : ''));
|
|
1610
|
+
const lens = field.originalLength ? fmtNum(field.originalLength) + ' chars' : '';
|
|
1611
|
+
const toggleButton = field.fullKey
|
|
1612
|
+
? '<button type="button" class="toggle-btn" data-full-key="' + esc(field.fullKey) + '" data-full-target="' + esc(prefix) + '">Show full</button>'
|
|
1613
|
+
: '';
|
|
1614
|
+
const fullPre = field.fullKey
|
|
1615
|
+
? '<pre id="' + esc(prefix) + '" class="full" hidden></pre>'
|
|
1616
|
+
: '';
|
|
1617
|
+
const note = field.fullUnavailable
|
|
1618
|
+
? '<div class="note">Full text omitted to keep dashboard responsive.</div>'
|
|
1619
|
+
: (field.fullTruncated ? '<div class="note">Full text clipped for dashboard size.</div>' : '');
|
|
1620
|
+
return '<div class="text-block">' +
|
|
1621
|
+
'<div class="text-head"><span>' + esc(label) + '</span><span class="text-lens">' + esc(lens) + '</span></div>' +
|
|
1622
|
+
'<pre>' + preview + '</pre>' +
|
|
1623
|
+
toggleButton +
|
|
1624
|
+
fullPre +
|
|
1625
|
+
note +
|
|
1626
|
+
'</div>';
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
function requestDomId(requestId, index) {
|
|
1630
|
+
const normalized = String(requestId || 'request')
|
|
1631
|
+
.toLowerCase()
|
|
1632
|
+
.replace(/[^a-z0-9_-]+/g, '-')
|
|
1633
|
+
.replace(/^-+|-+$/g, '');
|
|
1634
|
+
return 'request-' + (normalized || 'item') + '-' + String(index);
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
function llmDomId(requestId, requestIndex, llmIndex) {
|
|
1638
|
+
const normalized = String(requestId || 'request')
|
|
1639
|
+
.toLowerCase()
|
|
1640
|
+
.replace(/[^a-z0-9_-]+/g, '-')
|
|
1641
|
+
.replace(/^-+|-+$/g, '');
|
|
1642
|
+
return 'llm-' + (normalized || 'item') + '-' + String(requestIndex) + '-' + String(llmIndex);
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
function renderTool(tool, requestId, idx) {
|
|
1646
|
+
const statusClass = tool.incomplete ? 'running' : (tool.isError ? 'err' : 'ok');
|
|
1647
|
+
const statusText = tool.incomplete ? 'RUNNING' : (tool.isError ? 'ERROR' : 'OK');
|
|
1648
|
+
const duration = tool.durationMs === null ? '-' : fmtDuration(tool.durationMs);
|
|
1649
|
+
const inputBlock = renderTextBlock('Input', tool.input, requestId + '-tool-' + idx + '-input');
|
|
1650
|
+
const resultBlock = renderTextBlock('Result', tool.result, requestId + '-tool-' + idx + '-result');
|
|
1651
|
+
return '<div class="tool-item">' +
|
|
1652
|
+
'<div class="tool-head">' +
|
|
1653
|
+
'<div class="tool-name">' + esc(tool.toolName || '(tool)') + '</div>' +
|
|
1654
|
+
'<span class="tool-status ' + statusClass + '">' + statusText + '</span>' +
|
|
1655
|
+
'</div>' +
|
|
1656
|
+
'<div class="tool-meta">' +
|
|
1657
|
+
'<span>' + esc(duration) + '</span>' +
|
|
1658
|
+
'<span>' + esc(fmtWhen(tool.startTimestamp)) + '</span>' +
|
|
1659
|
+
'<span>' + esc(tool.callId || '') + '</span>' +
|
|
1660
|
+
'</div>' +
|
|
1661
|
+
inputBlock +
|
|
1662
|
+
resultBlock +
|
|
1663
|
+
'</div>';
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
function renderLlm(llm, requestId, requestRowIndex, llmIdx) {
|
|
1667
|
+
const llmClass = llm.isError ? 'llm-card error' : 'llm-card';
|
|
1668
|
+
const cardId = llmDomId(requestId, requestRowIndex, llmIdx);
|
|
1669
|
+
const errorPill = llm.isError ? '<span class="error-pill">ERROR</span>' : '';
|
|
1670
|
+
const promptBlock = renderTextBlock('Prompt', llm.prompt, requestId + '-llm-' + llmIdx + '-prompt');
|
|
1671
|
+
const completionBlock = renderTextBlock('Completion', llm.completion, requestId + '-llm-' + llmIdx + '-completion');
|
|
1672
|
+
const tools = Array.isArray(llm.tools) ? llm.tools : [];
|
|
1673
|
+
const toolsHtml = tools.length > 0
|
|
1674
|
+
? '<div class="tool-list">' + tools.map((tool, idx) => renderTool(tool, requestId + '-llm-' + llmIdx, idx)).join('') + '</div>'
|
|
1675
|
+
: '';
|
|
1676
|
+
return '<div id="' + esc(cardId) + '" class="' + llmClass + '" data-llm-index="' + String(llmIdx) + '">' +
|
|
1677
|
+
'<div class="llm-head">' +
|
|
1678
|
+
'<div class="model-pill" title="' + esc(llm.model || 'unknown') + '">' + esc(llm.model || 'unknown') + '</div>' +
|
|
1679
|
+
errorPill +
|
|
1680
|
+
'</div>' +
|
|
1681
|
+
'<div class="llm-metrics">' +
|
|
1682
|
+
'<span>' + fmtMoney(llm.cost) + '</span>' +
|
|
1683
|
+
'<span>' + esc(fmtDuration(llm.durationMs)) + '</span>' +
|
|
1684
|
+
'<span>in ' + fmtNum(llm.inputTokens) + '</span>' +
|
|
1685
|
+
'<span>out ' + fmtNum(llm.outputTokens) + '</span>' +
|
|
1686
|
+
'<span>' + esc(fmtWhen(llm.timestamp)) + '</span>' +
|
|
1687
|
+
'</div>' +
|
|
1688
|
+
promptBlock +
|
|
1689
|
+
completionBlock +
|
|
1690
|
+
toolsHtml +
|
|
1691
|
+
'</div>';
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
function renderRequest(request, index) {
|
|
1695
|
+
const llmCalls = Array.isArray(request.llmCalls) ? request.llmCalls : [];
|
|
1696
|
+
const orphanTools = Array.isArray(request.orphanTools) ? request.orphanTools : [];
|
|
1697
|
+
const turnEvents = Array.isArray(request.turnEvents) ? request.turnEvents : [];
|
|
1698
|
+
const errorEvents = Array.isArray(request.errorEvents) ? request.errorEvents : [];
|
|
1699
|
+
const requestId = String(request.requestId || '(unknown request)');
|
|
1700
|
+
const domId = requestDomId(requestId, index);
|
|
1701
|
+
|
|
1702
|
+
const llmHtml = llmCalls
|
|
1703
|
+
.map((llm, idx) => renderLlm(llm, request.requestId || 'request-' + index, index, idx))
|
|
1704
|
+
.join('');
|
|
1705
|
+
const orphanHtml = orphanTools.length > 0
|
|
1706
|
+
? '<div class="tool-list">' + orphanTools.map((tool, idx) => renderTool(tool, request.requestId || 'request-' + index, idx)).join('') + '</div>'
|
|
1707
|
+
: '';
|
|
1708
|
+
const turnsHtml = turnEvents.map((event) =>
|
|
1709
|
+
'<div class="marker-row">' + esc(event.type === 'turn_start' ? 'Turn start' : 'Turn end') + ' · ' + esc(fmtWhen(event.timestamp)) + '</div>'
|
|
1710
|
+
).join('');
|
|
1711
|
+
const errorsHtml = errorEvents.map((event, idx) => {
|
|
1712
|
+
const msg = event.message ? renderTextBlock('Error', event.message, (request.requestId || 'request-' + index) + '-err-' + idx) : '';
|
|
1713
|
+
return '<div class="error-row">Error event · ' + esc(fmtWhen(event.timestamp)) + '</div>' + msg;
|
|
1714
|
+
}).join('');
|
|
1715
|
+
|
|
1716
|
+
return '<article id="' + esc(domId) + '" class="request" data-request-id="' + esc(requestId) + '" data-request-index="' + String(index) + '">' +
|
|
1717
|
+
'<div class="request-head">' +
|
|
1718
|
+
'<div class="request-id" title="' + esc(request.requestId || '(unknown)') + '">' + esc(requestId) + '</div>' +
|
|
1719
|
+
'<div class="request-meta">' +
|
|
1720
|
+
'<span>' + fmtNum(llmCalls.length) + ' LLM</span>' +
|
|
1721
|
+
'<span>' + fmtNum(orphanTools.length + llmCalls.reduce((acc, llm) => acc + (Array.isArray(llm.tools) ? llm.tools.length : 0), 0)) + ' tools</span>' +
|
|
1722
|
+
'<span>' + esc(fmtWhen(request.startTimestamp)) + '</span>' +
|
|
1723
|
+
'</div>' +
|
|
1724
|
+
'</div>' +
|
|
1725
|
+
turnsHtml +
|
|
1726
|
+
errorsHtml +
|
|
1727
|
+
llmHtml +
|
|
1728
|
+
orphanHtml +
|
|
1729
|
+
'</article>';
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
function median(values) {
|
|
1733
|
+
if (!values.length) {
|
|
1734
|
+
return 0;
|
|
1735
|
+
}
|
|
1736
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
1737
|
+
const half = Math.floor(sorted.length / 2);
|
|
1738
|
+
if (sorted.length % 2 === 0) {
|
|
1739
|
+
return (sorted[half - 1] + sorted[half]) / 2;
|
|
1740
|
+
}
|
|
1741
|
+
return sorted[half];
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
function buildGrowthPoints(requestRows) {
|
|
1745
|
+
const points = [];
|
|
1746
|
+
let cumulativeCost = 0;
|
|
1747
|
+
let cumulativeTokens = 0;
|
|
1748
|
+
let turn = 0;
|
|
1749
|
+
|
|
1750
|
+
requestRows.forEach((request, requestRowIndex) => {
|
|
1751
|
+
const requestId = String(request.requestId || '(unknown request)');
|
|
1752
|
+
const llmCalls = Array.isArray(request.llmCalls) ? request.llmCalls : [];
|
|
1753
|
+
llmCalls.forEach((llm, llmIndex) => {
|
|
1754
|
+
const deltaCost = toNum(llm.cost);
|
|
1755
|
+
const deltaTokens = Math.max(0, Math.round(toNum(llm.inputTokens) + toNum(llm.outputTokens)));
|
|
1756
|
+
cumulativeCost += deltaCost;
|
|
1757
|
+
cumulativeTokens += deltaTokens;
|
|
1758
|
+
turn += 1;
|
|
1759
|
+
points.push({
|
|
1760
|
+
turn,
|
|
1761
|
+
requestId,
|
|
1762
|
+
requestRowIndex,
|
|
1763
|
+
llmIndex,
|
|
1764
|
+
llmCardId: llmDomId(requestId, requestRowIndex, llmIndex),
|
|
1765
|
+
model: String(llm.model || 'unknown'),
|
|
1766
|
+
timestamp: toNum(llm.timestamp),
|
|
1767
|
+
deltaCost,
|
|
1768
|
+
deltaTokens,
|
|
1769
|
+
cumulativeCost: Number(cumulativeCost.toFixed(6)),
|
|
1770
|
+
cumulativeTokens,
|
|
1771
|
+
});
|
|
1772
|
+
});
|
|
1773
|
+
});
|
|
1774
|
+
|
|
1775
|
+
const tokenM = median(points.map((point) => point.deltaTokens).filter((v) => v > 0));
|
|
1776
|
+
const costM = median(points.map((point) => point.deltaCost).filter((v) => v > 0));
|
|
1777
|
+
const tokenSpikeThreshold = tokenM > 0 ? tokenM * 2.2 : 0;
|
|
1778
|
+
const costSpikeThreshold = costM > 0 ? costM * 2.2 : 0;
|
|
1779
|
+
points.forEach((point) => {
|
|
1780
|
+
point.isSpike = (tokenSpikeThreshold > 0 && point.deltaTokens >= tokenSpikeThreshold)
|
|
1781
|
+
|| (costSpikeThreshold > 0 && point.deltaCost >= costSpikeThreshold);
|
|
1782
|
+
});
|
|
1783
|
+
return points;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
function jumpToTurn(point) {
|
|
1787
|
+
if (!point) return;
|
|
1788
|
+
const llmTarget = point.llmCardId ? document.getElementById(point.llmCardId) : null;
|
|
1789
|
+
if (llmTarget) {
|
|
1790
|
+
llmTarget.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1791
|
+
llmTarget.classList.add('focused');
|
|
1792
|
+
const requestHost = llmTarget.closest('.request');
|
|
1793
|
+
requestHost?.classList.add('focused');
|
|
1794
|
+
setTimeout(() => {
|
|
1795
|
+
llmTarget.classList.remove('focused');
|
|
1796
|
+
requestHost?.classList.remove('focused');
|
|
1797
|
+
}, 1600);
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
const requestTarget = document.getElementById(requestDomId(point.requestId, point.requestRowIndex));
|
|
1802
|
+
if (!requestTarget) return;
|
|
1803
|
+
requestTarget.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
1804
|
+
requestTarget.classList.add('focused');
|
|
1805
|
+
setTimeout(() => {
|
|
1806
|
+
requestTarget.classList.remove('focused');
|
|
1807
|
+
}, 1600);
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
function renderGrowthChart(session, requestRows) {
|
|
1811
|
+
if (!growthChartEl || !growthMetaEl) {
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
const points = buildGrowthPoints(requestRows);
|
|
1815
|
+
if (!points.length) {
|
|
1816
|
+
growthChartEl.innerHTML = '';
|
|
1817
|
+
growthMetaEl.textContent = 'No LLM turns available for cumulative chart.';
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
if (state.selectedGrowthSessionId !== session.sessionId) {
|
|
1822
|
+
state.selectedGrowthSessionId = session.sessionId;
|
|
1823
|
+
state.selectedGrowthIndex = points.length - 1;
|
|
1824
|
+
}
|
|
1825
|
+
if (state.selectedGrowthIndex < 0 || state.selectedGrowthIndex >= points.length) {
|
|
1826
|
+
state.selectedGrowthIndex = points.length - 1;
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
const width = Math.max(640, growthChartEl.clientWidth || 780);
|
|
1830
|
+
const height = Math.max(190, growthChartEl.clientHeight || 210);
|
|
1831
|
+
const margin = { top: 14, right: 58, bottom: 30, left: 58 };
|
|
1832
|
+
const innerWidth = Math.max(220, width - margin.left - margin.right);
|
|
1833
|
+
const innerHeight = Math.max(120, height - margin.top - margin.bottom);
|
|
1834
|
+
|
|
1835
|
+
growthChartEl.setAttribute('viewBox', '0 0 ' + width + ' ' + height);
|
|
1836
|
+
growthChartEl.innerHTML = '';
|
|
1837
|
+
|
|
1838
|
+
const maxTokens = Math.max(1, ...points.map((point) => point.cumulativeTokens));
|
|
1839
|
+
const maxCost = Math.max(0.000001, ...points.map((point) => point.cumulativeCost));
|
|
1840
|
+
const sx = (index) => margin.left + (index / Math.max(1, points.length - 1)) * innerWidth;
|
|
1841
|
+
const syTokens = (value) => margin.top + innerHeight - (toNum(value) / maxTokens) * innerHeight;
|
|
1842
|
+
const syCost = (value) => margin.top + innerHeight - (toNum(value) / maxCost) * innerHeight;
|
|
1843
|
+
const make = (name) => document.createElementNS('http://www.w3.org/2000/svg', name);
|
|
1844
|
+
const pathFor = (items) => {
|
|
1845
|
+
if (!items.length) return '';
|
|
1846
|
+
let d = 'M ' + items[0].x + ' ' + items[0].y;
|
|
1847
|
+
for (let i = 1; i < items.length; i++) {
|
|
1848
|
+
d += ' L ' + items[i].x + ' ' + items[i].y;
|
|
1849
|
+
}
|
|
1850
|
+
return d;
|
|
1851
|
+
};
|
|
1852
|
+
|
|
1853
|
+
const bg = make('rect');
|
|
1854
|
+
bg.setAttribute('x', String(margin.left));
|
|
1855
|
+
bg.setAttribute('y', String(margin.top));
|
|
1856
|
+
bg.setAttribute('width', String(innerWidth));
|
|
1857
|
+
bg.setAttribute('height', String(innerHeight));
|
|
1858
|
+
bg.setAttribute('fill', '#081325');
|
|
1859
|
+
bg.setAttribute('stroke', '#243a59');
|
|
1860
|
+
growthChartEl.appendChild(bg);
|
|
1861
|
+
|
|
1862
|
+
const tokenPoints = points.map((point, index) => ({ x: sx(index), y: syTokens(point.cumulativeTokens) }));
|
|
1863
|
+
const costPoints = points.map((point, index) => ({ x: sx(index), y: syCost(point.cumulativeCost) }));
|
|
1864
|
+
|
|
1865
|
+
const tokenPath = make('path');
|
|
1866
|
+
tokenPath.setAttribute('d', pathFor(tokenPoints));
|
|
1867
|
+
tokenPath.setAttribute('fill', 'none');
|
|
1868
|
+
tokenPath.setAttribute('stroke', '#38bdf8');
|
|
1869
|
+
tokenPath.setAttribute('stroke-width', '2');
|
|
1870
|
+
growthChartEl.appendChild(tokenPath);
|
|
1871
|
+
|
|
1872
|
+
const costPath = make('path');
|
|
1873
|
+
costPath.setAttribute('d', pathFor(costPoints));
|
|
1874
|
+
costPath.setAttribute('fill', 'none');
|
|
1875
|
+
costPath.setAttribute('stroke', '#f59e0b');
|
|
1876
|
+
costPath.setAttribute('stroke-width', '2');
|
|
1877
|
+
growthChartEl.appendChild(costPath);
|
|
1878
|
+
|
|
1879
|
+
points.forEach((point, index) => {
|
|
1880
|
+
const x = sx(index);
|
|
1881
|
+
const selected = index === state.selectedGrowthIndex;
|
|
1882
|
+
const hit = make('rect');
|
|
1883
|
+
hit.setAttribute('x', String(x - 5));
|
|
1884
|
+
hit.setAttribute('y', String(margin.top));
|
|
1885
|
+
hit.setAttribute('width', '10');
|
|
1886
|
+
hit.setAttribute('height', String(innerHeight));
|
|
1887
|
+
hit.setAttribute('fill', 'transparent');
|
|
1888
|
+
hit.style.cursor = 'pointer';
|
|
1889
|
+
hit.addEventListener('click', () => {
|
|
1890
|
+
state.selectedGrowthIndex = index;
|
|
1891
|
+
renderGrowthChart(session, requestRows);
|
|
1892
|
+
jumpToTurn(point);
|
|
1893
|
+
});
|
|
1894
|
+
growthChartEl.appendChild(hit);
|
|
1895
|
+
|
|
1896
|
+
const tokenDot = make('circle');
|
|
1897
|
+
tokenDot.setAttribute('cx', String(x));
|
|
1898
|
+
tokenDot.setAttribute('cy', String(syTokens(point.cumulativeTokens)));
|
|
1899
|
+
tokenDot.setAttribute('r', selected ? '3.8' : '2.6');
|
|
1900
|
+
tokenDot.setAttribute('fill', '#38bdf8');
|
|
1901
|
+
if (selected) {
|
|
1902
|
+
tokenDot.setAttribute('stroke', '#ffffff');
|
|
1903
|
+
tokenDot.setAttribute('stroke-width', '1.2');
|
|
1904
|
+
}
|
|
1905
|
+
growthChartEl.appendChild(tokenDot);
|
|
1906
|
+
|
|
1907
|
+
const costDot = make('circle');
|
|
1908
|
+
costDot.setAttribute('cx', String(x));
|
|
1909
|
+
costDot.setAttribute('cy', String(syCost(point.cumulativeCost)));
|
|
1910
|
+
costDot.setAttribute('r', selected ? '3.6' : '2.4');
|
|
1911
|
+
costDot.setAttribute('fill', '#f59e0b');
|
|
1912
|
+
growthChartEl.appendChild(costDot);
|
|
1913
|
+
|
|
1914
|
+
if (point.isSpike) {
|
|
1915
|
+
const spike = make('line');
|
|
1916
|
+
spike.setAttribute('x1', String(x));
|
|
1917
|
+
spike.setAttribute('y1', String(margin.top));
|
|
1918
|
+
spike.setAttribute('x2', String(x));
|
|
1919
|
+
spike.setAttribute('y2', String(margin.top + innerHeight));
|
|
1920
|
+
spike.setAttribute('stroke', '#ef4444');
|
|
1921
|
+
spike.setAttribute('stroke-dasharray', '2 4');
|
|
1922
|
+
spike.setAttribute('stroke-opacity', selected ? '0.8' : '0.35');
|
|
1923
|
+
growthChartEl.appendChild(spike);
|
|
1924
|
+
}
|
|
1925
|
+
});
|
|
1926
|
+
|
|
1927
|
+
const selectedPoint = points[state.selectedGrowthIndex];
|
|
1928
|
+
const spikeHint = selectedPoint.isSpike ? ' · sudden change detected' : '';
|
|
1929
|
+
growthMetaEl.textContent =
|
|
1930
|
+
'Turn #' + selectedPoint.turn +
|
|
1931
|
+
' · ' + selectedPoint.requestId +
|
|
1932
|
+
' · model ' + selectedPoint.model +
|
|
1933
|
+
' · +$' + toNum(selectedPoint.deltaCost).toFixed(4) +
|
|
1934
|
+
' · +' + fmtNum(selectedPoint.deltaTokens) + ' tokens' +
|
|
1935
|
+
' · cumulative ' + fmtMoney(selectedPoint.cumulativeCost) +
|
|
1936
|
+
' / ' + fmtNum(selectedPoint.cumulativeTokens) + ' tokens' +
|
|
1937
|
+
spikeHint;
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
function renderSelectedSession() {
|
|
1941
|
+
const session = sessions.find((item) => item.sessionId === state.selectedId);
|
|
1942
|
+
if (!session) {
|
|
1943
|
+
titleEl.innerHTML = '<span>No session selected</span>';
|
|
1944
|
+
statCostEl.textContent = '-';
|
|
1945
|
+
statLlmEl.textContent = '-';
|
|
1946
|
+
statRequestsEl.textContent = '-';
|
|
1947
|
+
statTokensEl.textContent = '-';
|
|
1948
|
+
statLatencyEl.textContent = '-';
|
|
1949
|
+
warnLineEl.style.display = 'none';
|
|
1950
|
+
timelineEl.innerHTML = '<div class="empty">Select a session to inspect timeline details.</div>';
|
|
1951
|
+
if (growthChartEl) {
|
|
1952
|
+
growthChartEl.innerHTML = '';
|
|
1953
|
+
}
|
|
1954
|
+
if (growthMetaEl) {
|
|
1955
|
+
growthMetaEl.textContent = 'Select a session to inspect cumulative growth.';
|
|
1956
|
+
}
|
|
1957
|
+
copyBtn.disabled = true;
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
copyBtn.disabled = false;
|
|
1962
|
+
copyBtn.onclick = () => postCopySessionId(session.sessionId);
|
|
1963
|
+
titleEl.innerHTML = '<span>Session</span><code title="' + esc(session.sessionId) + '">' + esc(session.sessionId) + '</code>' +
|
|
1964
|
+
(session.isActive ? '<span class="live-badge">LIVE</span>' : '');
|
|
1965
|
+
|
|
1966
|
+
statCostEl.textContent = fmtMoney(session.totalCost);
|
|
1967
|
+
statLlmEl.textContent = fmtNum(session.llmCalls);
|
|
1968
|
+
statRequestsEl.textContent = fmtNum(session.requestCount);
|
|
1969
|
+
statTokensEl.textContent = fmtNum((session.inputTokens || 0) + (session.outputTokens || 0));
|
|
1970
|
+
statLatencyEl.textContent = fmtDuration(session.avgLatencyMs);
|
|
1971
|
+
|
|
1972
|
+
const trunc = session.timelineStats && session.timelineStats.truncated ? session.timelineStats.truncated : {};
|
|
1973
|
+
const warnings = [];
|
|
1974
|
+
if (trunc.llmRowsDropped) {
|
|
1975
|
+
warnings.push('LLM timeline capped at recent ' + ${String(MAX_LLM_ROWS_PER_SESSION)} + ' calls.');
|
|
1976
|
+
}
|
|
1977
|
+
if (trunc.toolCallsDropped > 0) {
|
|
1978
|
+
warnings.push(String(trunc.toolCallsDropped) + ' older tool calls hidden.');
|
|
1979
|
+
}
|
|
1980
|
+
if (trunc.turnEventsDropped > 0) {
|
|
1981
|
+
warnings.push(String(trunc.turnEventsDropped) + ' older turn markers hidden.');
|
|
1982
|
+
}
|
|
1983
|
+
if (trunc.errorEventsDropped > 0) {
|
|
1984
|
+
warnings.push(String(trunc.errorEventsDropped) + ' older error events hidden.');
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
if (warnings.length > 0) {
|
|
1988
|
+
warnLineEl.textContent = warnings.join(' ');
|
|
1989
|
+
warnLineEl.style.display = 'block';
|
|
1990
|
+
} else {
|
|
1991
|
+
warnLineEl.style.display = 'none';
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
const requestRows = Array.isArray(session.requests) ? session.requests : [];
|
|
1995
|
+
if (requestRows.length === 0) {
|
|
1996
|
+
timelineEl.innerHTML = '<div class="empty">No timeline events found for this session.</div>';
|
|
1997
|
+
} else {
|
|
1998
|
+
timelineEl.innerHTML = requestRows.map((request, idx) => renderRequest(request, idx)).join('');
|
|
1999
|
+
}
|
|
2000
|
+
renderGrowthChart(session, requestRows);
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
function wireFullTextToggles() {
|
|
2004
|
+
timelineEl.querySelectorAll('.toggle-btn').forEach((btn) => {
|
|
2005
|
+
btn.addEventListener('click', () => {
|
|
2006
|
+
const key = btn.getAttribute('data-full-key') || '';
|
|
2007
|
+
const target = btn.getAttribute('data-full-target') || '';
|
|
2008
|
+
const fullEl = document.getElementById(target);
|
|
2009
|
+
if (!fullEl) return;
|
|
2010
|
+
if (fullEl.hidden) {
|
|
2011
|
+
if (!fullEl.dataset.loaded) {
|
|
2012
|
+
const content = typeof fullTexts[key] === 'string'
|
|
2013
|
+
? fullTexts[key]
|
|
2014
|
+
: '(Full text not available in this view.)';
|
|
2015
|
+
fullEl.textContent = content;
|
|
2016
|
+
fullEl.dataset.loaded = '1';
|
|
2017
|
+
}
|
|
2018
|
+
fullEl.hidden = false;
|
|
2019
|
+
btn.textContent = 'Hide full';
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
fullEl.hidden = true;
|
|
2023
|
+
btn.textContent = 'Show full';
|
|
2024
|
+
});
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
function renderFooterMeta() {
|
|
2029
|
+
footerMetaEl.textContent = sessions.length + ' session' + (sessions.length === 1 ? '' : 's') + ' loaded';
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
function render() {
|
|
2033
|
+
renderSessionList();
|
|
2034
|
+
renderSelectedSession();
|
|
2035
|
+
wireFullTextToggles();
|
|
2036
|
+
renderFooterMeta();
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
searchEl.addEventListener('input', () => {
|
|
2040
|
+
state.search = searchEl.value || '';
|
|
2041
|
+
renderSessionList();
|
|
2042
|
+
});
|
|
2043
|
+
|
|
2044
|
+
render();
|
|
2045
|
+
</script>
|
|
2046
|
+
</body>
|
|
2047
|
+
</html>`;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
function main() {
|
|
2051
|
+
const home = process.env.HOME || '';
|
|
2052
|
+
const tracesDir = process.env.AGENTIDE_TRACES_DIR || path.join(home, '.agent-hippo', 'analytics', 'traces');
|
|
2053
|
+
const sessionsDir = process.env.AGENTIDE_SESSIONS_DIR || path.join(home, '.agent-hippo', 'analytics', 'sessions');
|
|
2054
|
+
const chartDir = process.env.AGENTIDE_CHART_DIR || path.join(home, '.agent-hippo', 'analytics', 'views', 'global', '__all', '__all', 'charts');
|
|
2055
|
+
const outputPath = path.join(chartDir, 'session-timeline-widget.html');
|
|
2056
|
+
const activeSessionId = String(process.env.AGENTIDE_ACTIVE_SESSION_ID || '').trim();
|
|
2057
|
+
|
|
2058
|
+
fs.mkdirSync(chartDir, { recursive: true });
|
|
2059
|
+
if (isOutputDisabled('session-timeline-widget.html')) {
|
|
2060
|
+
try {
|
|
2061
|
+
fs.unlinkSync(outputPath);
|
|
2062
|
+
} catch {
|
|
2063
|
+
// ignore missing file
|
|
2064
|
+
}
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
const duckdb = findDuckdb();
|
|
2069
|
+
if (!duckdb) {
|
|
2070
|
+
fs.writeFileSync(outputPath, buildFallbackHtml('DuckDB not found. Run Spotlight setup first.'), 'utf8');
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
const indexedDbPath = getIndexedDbPath(tracesDir);
|
|
2075
|
+
if (!indexedDbPath && !hasJsonlFiles(tracesDir)) {
|
|
2076
|
+
fs.writeFileSync(outputPath, buildFallbackHtml('No session trace data available yet.'), 'utf8');
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
const runQueries = (baseQuery, dbPath = '') => runBatchQueries(duckdb, [
|
|
2081
|
+
{ name: 'summaries', sql: buildSessionSummariesSql(baseQuery) },
|
|
2082
|
+
{ name: 'llmRows', sql: buildAllSessionsLlmSql(baseQuery) },
|
|
2083
|
+
], dbPath);
|
|
2084
|
+
|
|
2085
|
+
// Run against indexed DB first when available; fall back to JSONL if indexed query fails.
|
|
2086
|
+
let batch = runQueries(buildBaseQuery(tracesDir, Boolean(indexedDbPath)), indexedDbPath);
|
|
2087
|
+
if (indexedDbPath && batch.failed) {
|
|
2088
|
+
console.error('session-timeline-widget indexed query failed; falling back to JSONL parsing');
|
|
2089
|
+
batch = runQueries(buildBaseQuery(tracesDir, false));
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
const batchResults = batch.result;
|
|
2093
|
+
const summaryRows = batchResults.summaries;
|
|
2094
|
+
if (!summaryRows.length) {
|
|
2095
|
+
fs.writeFileSync(outputPath, buildFallbackHtml('No session trace data available yet.'), 'utf8');
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
// Build per-session LLM rows map (already ordered chronologically by the SQL query).
|
|
2100
|
+
const llmRowsBySession = new Map();
|
|
2101
|
+
for (const row of batchResults.llmRows) {
|
|
2102
|
+
const sid = String(row.session_id || '').trim();
|
|
2103
|
+
if (!sid) { continue; }
|
|
2104
|
+
if (!llmRowsBySession.has(sid)) { llmRowsBySession.set(sid, []); }
|
|
2105
|
+
llmRowsBySession.get(sid).push(row);
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
const { sessions, fullTexts } = buildSessionDetails(
|
|
2109
|
+
llmRowsBySession,
|
|
2110
|
+
sessionsDir,
|
|
2111
|
+
activeSessionId,
|
|
2112
|
+
summaryRows,
|
|
2113
|
+
);
|
|
2114
|
+
|
|
2115
|
+
if (!sessions.length) {
|
|
2116
|
+
fs.writeFileSync(outputPath, buildFallbackHtml('No sessions available for timeline rendering.'), 'utf8');
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
const payload = {
|
|
2121
|
+
version: 1,
|
|
2122
|
+
generatedAt: new Date().toISOString(),
|
|
2123
|
+
activeSessionId: activeSessionId || null,
|
|
2124
|
+
limits: {
|
|
2125
|
+
sessionLimit: SESSION_LIMIT,
|
|
2126
|
+
maxLlmRowsPerSession: MAX_LLM_ROWS_PER_SESSION,
|
|
2127
|
+
maxToolCallsPerSession: MAX_TOOL_CALLS_PER_SESSION,
|
|
2128
|
+
textPreviewChars: TEXT_PREVIEW_CHARS,
|
|
2129
|
+
},
|
|
2130
|
+
sessions,
|
|
2131
|
+
fullTexts,
|
|
2132
|
+
};
|
|
2133
|
+
|
|
2134
|
+
fs.writeFileSync(outputPath, buildHtml(payload), 'utf8');
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
main();
|