@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.
Files changed (480) hide show
  1. package/LICENSE +21 -0
  2. package/bin/agenthippo.js +2 -0
  3. package/out/agentpack/github.js +547 -0
  4. package/out/agentpack/index.js +66 -0
  5. package/out/agentpack/install.js +297 -0
  6. package/out/agentpack/manager.js +441 -0
  7. package/out/agentpack/manifest.js +135 -0
  8. package/out/agentpack/paths.js +475 -0
  9. package/out/agentpack/publish.js +248 -0
  10. package/out/agentpack/stores.js +554 -0
  11. package/out/agentpack/types.js +5 -0
  12. package/out/analytics/dashboard/artifactManager.js +439 -0
  13. package/out/analytics/dashboard/fleetDisplayUtils.js +159 -0
  14. package/out/analytics/dashboard/fleetOrchestrator.js +142 -0
  15. package/out/analytics/dashboard/fleetRegistry.js +79 -0
  16. package/out/analytics/dashboard/fleetRowDefaults.js +87 -0
  17. package/out/analytics/dashboard/fleetScriptAdapter.js +146 -0
  18. package/out/analytics/dashboard/fleetStatusUtils.js +30 -0
  19. package/out/analytics/dashboard/fleetTitleUtils.js +68 -0
  20. package/out/analytics/dashboard/fleetTypes.js +29 -0
  21. package/out/analytics/dashboard/index.js +17 -0
  22. package/out/analytics/dashboard/indexingService.js +422 -0
  23. package/out/analytics/dashboard/localFleetAdapter.js +222 -0
  24. package/out/analytics/dashboard/performanceMonitor.js +76 -0
  25. package/out/analytics/dashboard/processManager.js +209 -0
  26. package/out/analytics/dashboard/provider.js +3885 -0
  27. package/out/analytics/dashboard/queryRunner.js +994 -0
  28. package/out/analytics/dashboard/refreshPolicy.js +16 -0
  29. package/out/analytics/dashboard/scheduledFleetFingerprint.js +22 -0
  30. package/out/analytics/dashboard/scheduledFleetStorage.js +241 -0
  31. package/out/analytics/dashboard/scheduledFleetUtils.js +22 -0
  32. package/out/analytics/dashboard/sessionEventFleetStats.js +118 -0
  33. package/out/analytics/dashboard/sessionJsonlFleet.js +204 -0
  34. package/out/analytics/dashboard/teamViewDiscovery.js +106 -0
  35. package/out/analytics/dashboard/viewsConfigMerge.js +221 -0
  36. package/out/analytics/dashboard/webview/analytics-panel.html +155 -0
  37. package/out/analytics/dashboard/webview/codicon.ttf +0 -0
  38. package/out/analytics/dashboard/webview/index.html +78 -0
  39. package/out/analytics/dashboard/webview/panel.js +2295 -0
  40. package/out/analytics/dashboard/webview/runtime-bridge.js +82 -0
  41. package/out/analytics/dashboard/webview/styles.css +1009 -0
  42. package/out/analytics/lifecycle.js +43 -0
  43. package/out/analytics/service.js +1279 -0
  44. package/out/analytics/sessionEvents.js +231 -0
  45. package/out/auth/bootstrapApply.js +72 -0
  46. package/out/auth/brokerClient.js +126 -0
  47. package/out/auth/callbackUri.js +59 -0
  48. package/out/auth/callbackUriParse.js +59 -0
  49. package/out/auth/githubStoreGate.js +70 -0
  50. package/out/auth/loopbackAuthServer.js +165 -0
  51. package/out/auth/pkce.js +9 -0
  52. package/out/auth/provider.js +239 -0
  53. package/out/auth/sessionStore.js +193 -0
  54. package/out/auth/signInFlow.js +182 -0
  55. package/out/auth/types.js +12 -0
  56. package/out/auth-broker/config.js +62 -0
  57. package/out/auth-broker/jwt.js +105 -0
  58. package/out/auth-broker/pkce.js +57 -0
  59. package/out/auth-broker/server.js +539 -0
  60. package/out/auth-broker/store.js +273 -0
  61. package/out/base-prompts/gateway/openclaw-starter.json5 +144 -0
  62. package/out/base-prompts/gateway/slack-socket-mode-minimal.manifest.json +44 -0
  63. package/out/base-prompts/mcp-servers/.mcp.json +13 -0
  64. package/out/base-prompts/mcp-servers/langfuse-analytics/README.md +142 -0
  65. package/out/base-prompts/mcp-servers/langfuse-analytics/mcp.json +13 -0
  66. package/out/base-prompts/mcp-servers/langfuse-analytics/package-lock.json +1192 -0
  67. package/out/base-prompts/mcp-servers/langfuse-analytics/package.json +23 -0
  68. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/run-with-env.sh +12 -0
  69. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/sdk-test.js +102 -0
  70. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/simple-test.js +137 -0
  71. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-client.js +118 -0
  72. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-server.js +209 -0
  73. package/out/base-prompts/mcp-servers/langfuse-analytics/src/client.ts +431 -0
  74. package/out/base-prompts/mcp-servers/langfuse-analytics/src/index.ts +270 -0
  75. package/out/base-prompts/mcp-servers/langfuse-analytics/tsconfig.json +18 -0
  76. package/out/base-prompts/rules/AGENTS.md +204 -0
  77. package/out/base-prompts/skills/agent-browser/SKILL.md +102 -0
  78. package/out/base-prompts/skills/agent-builder/SKILL.md +73 -0
  79. package/out/base-prompts/skills/agent-builder/references/agent-pack.md +63 -0
  80. package/out/base-prompts/skills/agent-builder/references/crewai.md +66 -0
  81. package/out/base-prompts/skills/agent-builder/references/langchain.md +60 -0
  82. package/out/base-prompts/skills/agent-builder/references/raw-sdk.md +73 -0
  83. package/out/base-prompts/skills/agent-engine-creator/SKILL.md +586 -0
  84. package/out/base-prompts/skills/agent-engine-installer/SKILL.md +106 -0
  85. package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.mjs +259 -0
  86. package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.ps1 +189 -0
  87. package/out/base-prompts/skills/agenthippo-cli/SKILL.md +69 -0
  88. package/out/base-prompts/skills/agenthippo-cli/scripts/install.sh +63 -0
  89. package/out/base-prompts/skills/agentpack-creator/SKILL.md +375 -0
  90. package/out/base-prompts/skills/agentpack-creator/references/agent-templates.md +261 -0
  91. package/out/base-prompts/skills/agentpack-creator/references/agents-md-guide.md +195 -0
  92. package/out/base-prompts/skills/agentpack-creator/scripts/init_pack.py +391 -0
  93. package/out/base-prompts/skills/agentpack-creator/scripts/init_pack_test.py +294 -0
  94. package/out/base-prompts/skills/agentpack-testbench/SKILL.md +135 -0
  95. package/out/base-prompts/skills/algorithmic-art/LICENSE.txt +202 -0
  96. package/out/base-prompts/skills/algorithmic-art/SKILL.md +405 -0
  97. package/out/base-prompts/skills/algorithmic-art/templates/generator_template.js +223 -0
  98. package/out/base-prompts/skills/algorithmic-art/templates/viewer.html +599 -0
  99. package/out/base-prompts/skills/debug-agent/SKILL.md +83 -0
  100. package/out/base-prompts/skills/desktop-automation/SKILL.md +166 -0
  101. package/out/base-prompts/skills/desktop-automation/references/agenthippo-ui-recipes.md +441 -0
  102. package/out/base-prompts/skills/desktop-automation/scripts/windows-desktop-uia.psm1 +280 -0
  103. package/out/base-prompts/skills/docx/LICENSE.txt +30 -0
  104. package/out/base-prompts/skills/docx/SKILL.md +197 -0
  105. package/out/base-prompts/skills/docx/docx-js.md +350 -0
  106. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  107. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  108. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  109. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  110. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  111. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  112. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  113. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  114. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  115. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  116. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  117. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  118. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  119. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  120. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  121. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  122. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  123. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  124. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  125. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  126. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  127. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  128. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  129. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  130. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  131. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  132. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  133. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  134. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  135. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  136. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  137. package/out/base-prompts/skills/docx/ooxml/schemas/mce/mc.xsd +75 -0
  138. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  139. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  140. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  141. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  142. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  143. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  144. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  145. package/out/base-prompts/skills/docx/ooxml/scripts/pack.py +159 -0
  146. package/out/base-prompts/skills/docx/ooxml/scripts/unpack.py +29 -0
  147. package/out/base-prompts/skills/docx/ooxml/scripts/validate.py +69 -0
  148. package/out/base-prompts/skills/docx/ooxml/scripts/validation/__init__.py +15 -0
  149. package/out/base-prompts/skills/docx/ooxml/scripts/validation/base.py +951 -0
  150. package/out/base-prompts/skills/docx/ooxml/scripts/validation/docx.py +274 -0
  151. package/out/base-prompts/skills/docx/ooxml/scripts/validation/pptx.py +315 -0
  152. package/out/base-prompts/skills/docx/ooxml/scripts/validation/redlining.py +279 -0
  153. package/out/base-prompts/skills/docx/ooxml.md +610 -0
  154. package/out/base-prompts/skills/docx/scripts/__init__.py +1 -0
  155. package/out/base-prompts/skills/docx/scripts/document.py +1276 -0
  156. package/out/base-prompts/skills/docx/scripts/templates/comments.xml +3 -0
  157. package/out/base-prompts/skills/docx/scripts/templates/commentsExtended.xml +3 -0
  158. package/out/base-prompts/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
  159. package/out/base-prompts/skills/docx/scripts/templates/commentsIds.xml +3 -0
  160. package/out/base-prompts/skills/docx/scripts/templates/people.xml +3 -0
  161. package/out/base-prompts/skills/docx/scripts/utilities.py +374 -0
  162. package/out/base-prompts/skills/gateway-outbound/SKILL.md +136 -0
  163. package/out/base-prompts/skills/mcp-builder/LICENSE.txt +202 -0
  164. package/out/base-prompts/skills/mcp-builder/SKILL.md +328 -0
  165. package/out/base-prompts/skills/mcp-builder/reference/evaluation.md +602 -0
  166. package/out/base-prompts/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
  167. package/out/base-prompts/skills/mcp-builder/reference/node_mcp_server.md +970 -0
  168. package/out/base-prompts/skills/mcp-builder/reference/python_mcp_server.md +719 -0
  169. package/out/base-prompts/skills/mcp-builder/scripts/connections.py +151 -0
  170. package/out/base-prompts/skills/mcp-builder/scripts/evaluation.py +373 -0
  171. package/out/base-prompts/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  172. package/out/base-prompts/skills/mcp-builder/scripts/requirements.txt +2 -0
  173. package/out/base-prompts/skills/openclaw/SKILL.md +406 -0
  174. package/out/base-prompts/skills/openclaw/scripts/run-smoke.sh +61 -0
  175. package/out/base-prompts/skills/pdf/LICENSE.txt +30 -0
  176. package/out/base-prompts/skills/pdf/SKILL.md +294 -0
  177. package/out/base-prompts/skills/pdf/forms.md +205 -0
  178. package/out/base-prompts/skills/pdf/reference.md +612 -0
  179. package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes.py +70 -0
  180. package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes_test.py +226 -0
  181. package/out/base-prompts/skills/pdf/scripts/check_fillable_fields.py +12 -0
  182. package/out/base-prompts/skills/pdf/scripts/convert_pdf_to_images.py +35 -0
  183. package/out/base-prompts/skills/pdf/scripts/create_validation_image.py +41 -0
  184. package/out/base-prompts/skills/pdf/scripts/extract_form_field_info.py +152 -0
  185. package/out/base-prompts/skills/pdf/scripts/fill_fillable_fields.py +114 -0
  186. package/out/base-prompts/skills/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
  187. package/out/base-prompts/skills/pptx/LICENSE.txt +30 -0
  188. package/out/base-prompts/skills/pptx/SKILL.md +484 -0
  189. package/out/base-prompts/skills/pptx/html2pptx.md +625 -0
  190. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  191. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  192. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  193. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  194. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  195. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  196. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  197. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  198. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  199. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  200. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  201. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  202. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  203. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  204. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  205. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  206. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  207. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  208. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  209. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  210. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  211. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  212. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  213. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  214. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  215. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  216. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  217. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  218. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  219. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  220. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  221. package/out/base-prompts/skills/pptx/ooxml/schemas/mce/mc.xsd +75 -0
  222. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  223. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  224. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  225. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  226. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  227. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  228. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  229. package/out/base-prompts/skills/pptx/ooxml/scripts/pack.py +159 -0
  230. package/out/base-prompts/skills/pptx/ooxml/scripts/unpack.py +29 -0
  231. package/out/base-prompts/skills/pptx/ooxml/scripts/validate.py +69 -0
  232. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/__init__.py +15 -0
  233. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/base.py +951 -0
  234. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/docx.py +274 -0
  235. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/pptx.py +315 -0
  236. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/redlining.py +279 -0
  237. package/out/base-prompts/skills/pptx/ooxml.md +427 -0
  238. package/out/base-prompts/skills/pptx/scripts/html2pptx.js +984 -0
  239. package/out/base-prompts/skills/pptx/scripts/inventory.py +1020 -0
  240. package/out/base-prompts/skills/pptx/scripts/rearrange.py +231 -0
  241. package/out/base-prompts/skills/pptx/scripts/replace.py +385 -0
  242. package/out/base-prompts/skills/pptx/scripts/thumbnail.py +450 -0
  243. package/out/base-prompts/skills/prompt-lab/SKILL.md +149 -0
  244. package/out/base-prompts/skills/prompt-lab/scripts/query-analytics.sh +2551 -0
  245. package/out/base-prompts/skills/skill-creator/LICENSE.txt +202 -0
  246. package/out/base-prompts/skills/skill-creator/SKILL.md +371 -0
  247. package/out/base-prompts/skills/skill-creator/references/output-patterns.md +82 -0
  248. package/out/base-prompts/skills/skill-creator/references/workflows.md +28 -0
  249. package/out/base-prompts/skills/skill-creator/scripts/init_skill.py +303 -0
  250. package/out/base-prompts/skills/skill-creator/scripts/package_skill.py +110 -0
  251. package/out/base-prompts/skills/skill-creator/scripts/quick_validate.py +95 -0
  252. package/out/base-prompts/skills/skill-installer/SKILL.md +251 -0
  253. package/out/base-prompts/skills/skill-installer/references/store-api.md +219 -0
  254. package/out/base-prompts/skills/spotlight-dashboard/SKILL.md +83 -0
  255. package/out/base-prompts/skills/spotlight-insights/SKILL.md +101 -0
  256. package/out/base-prompts/skills/spotlight-optimize/SKILL.md +93 -0
  257. package/out/base-prompts/skills/spotlight-optimize/scripts/query-analytics.sh +2551 -0
  258. package/out/base-prompts/skills/spotlight-query/SKILL.md +314 -0
  259. package/out/base-prompts/skills/spotlight-query/references/otel-schema.md +163 -0
  260. package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.sh +2551 -0
  261. package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.test.sh +744 -0
  262. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/sessions/sess-test-1.jsonl +14 -0
  263. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/.duckdb-index.json +7 -0
  264. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/sample.jsonl +5 -0
  265. package/out/base-prompts/skills/spotlight-report/SKILL.md +86 -0
  266. package/out/base-prompts/skills/spotlight-visualize/SKILL.md +92 -0
  267. package/out/base-prompts/skills/spotlight-visualize/scripts/query-analytics.sh +2551 -0
  268. package/out/base-prompts/skills/xlsx/LICENSE.txt +30 -0
  269. package/out/base-prompts/skills/xlsx/SKILL.md +289 -0
  270. package/out/base-prompts/skills/xlsx/recalc.py +178 -0
  271. package/out/branding.js +23 -0
  272. package/out/cli/commands/anywhere.js +66 -0
  273. package/out/cli/commands/ask.js +243 -0
  274. package/out/cli/commands/auth-broker.js +24 -0
  275. package/out/cli/commands/auth.js +219 -0
  276. package/out/cli/commands/chat.js +354 -0
  277. package/out/cli/commands/deploy.js +223 -0
  278. package/out/cli/commands/gateway.js +160 -0
  279. package/out/cli/commands/pack.js +220 -0
  280. package/out/cli/commands/probe-custom-engine.js +82 -0
  281. package/out/cli/commands/serve.js +583 -0
  282. package/out/cli/commands/store.js +919 -0
  283. package/out/cli/credential-context.js +113 -0
  284. package/out/cli/deploy-init.js +2087 -0
  285. package/out/cli/deps.js +238 -0
  286. package/out/cli/index.js +390 -0
  287. package/out/cli/pack-loader.js +376 -0
  288. package/out/cli/run-custom-engine-runner.js +318 -0
  289. package/out/cli/runtime.js +409 -0
  290. package/out/cli/serve-hardening.js +325 -0
  291. package/out/cli/state-sync.js +135 -0
  292. package/out/cli/utils.js +220 -0
  293. package/out/cli/vscode-shim.js +86 -0
  294. package/out/engines/base.js +159 -0
  295. package/out/engines/claude.js +1215 -0
  296. package/out/engines/codex.js +759 -0
  297. package/out/engines/custom.js +632 -0
  298. package/out/engines/customEngineIpc.js +95 -0
  299. package/out/engines/customEngineRunnerEmbed.js +107 -0
  300. package/out/engines/customRegistry.js +274 -0
  301. package/out/engines/embeddedCustomEngineRunner.generated.js +9 -0
  302. package/out/engines/engineApiKey.js +138 -0
  303. package/out/engines/factory.js +45 -0
  304. package/out/engines/install.js +372 -0
  305. package/out/engines/persistentEngineWorker.js +320 -0
  306. package/out/engines/publish.js +341 -0
  307. package/out/engines/types.js +17 -0
  308. package/out/examples/run-headless-repl.js +291 -0
  309. package/out/examples/run-headless.js +187 -0
  310. package/out/extension.js +4472 -0
  311. package/out/gateway/bridge.js +805 -0
  312. package/out/gateway/cli-agent-id.js +36 -0
  313. package/out/gateway/client.js +303 -0
  314. package/out/gateway/client.test.js +395 -0
  315. package/out/gateway/gateway-scope-errors.js +14 -0
  316. package/out/gateway/gatewayBackup.js +76 -0
  317. package/out/gateway/gatewayRouteIndex.js +371 -0
  318. package/out/gateway/gatewaySessionStore.js +243 -0
  319. package/out/gateway/index.js +31 -0
  320. package/out/gateway/lifecycle.js +3056 -0
  321. package/out/gateway/openclaw-channel-routing.js +317 -0
  322. package/out/gateway/outboundHandles.js +373 -0
  323. package/out/gateway/outboundPaths.js +72 -0
  324. package/out/gateway/outboundSend.js +254 -0
  325. package/out/gateway/protocol.js +48 -0
  326. package/out/gateway/resetMappings.js +52 -0
  327. package/out/gateway/service.js +1244 -0
  328. package/out/gateway/sessionRouting.js +77 -0
  329. package/out/headless/index.js +505 -0
  330. package/out/headless/sync.js +473 -0
  331. package/out/mcp/publish.js +143 -0
  332. package/out/mcp.js +597 -0
  333. package/out/permissions.js +108 -0
  334. package/out/permissions.mapping.yaml +68 -0
  335. package/out/registry/index.js +332 -0
  336. package/out/remote/handleRemoteChat.js +122 -0
  337. package/out/remote/remotePackStatusBar.js +103 -0
  338. package/out/remote/serveClient.js +216 -0
  339. package/out/remoteSkills.js +191 -0
  340. package/out/runtime/index.js +32 -0
  341. package/out/runtime/interfaces/config-provider.js +32 -0
  342. package/out/runtime/interfaces/edit-tracker.js +22 -0
  343. package/out/runtime/interfaces/index.js +29 -0
  344. package/out/runtime/interfaces/logger.js +40 -0
  345. package/out/runtime/interfaces/permission-handler.js +66 -0
  346. package/out/runtime/interfaces/session-store.js +342 -0
  347. package/out/runtime/vscode/index.js +20 -0
  348. package/out/runtime/vscode/vscode-config-provider.js +77 -0
  349. package/out/runtime/vscode/vscode-edit-tracker.js +120 -0
  350. package/out/runtime/vscode/vscode-logger.js +30 -0
  351. package/out/runtime/vscode/vscode-permission-handler.js +72 -0
  352. package/out/runtime/vscode/vscode-session-store.js +22 -0
  353. package/out/scripts/agentide-litellm.ps1 +501 -0
  354. package/out/scripts/agentide-litellm.sh +700 -0
  355. package/out/scripts/agentide_litellm_hooks_v1.py +604 -0
  356. package/out/scripts/analytics/agent-fleet-widget.js +954 -0
  357. package/out/scripts/analytics/agentide-litellm-otel.config.yaml +160 -0
  358. package/out/scripts/analytics/default-seed-scripts.json +12 -0
  359. package/out/scripts/analytics/duckdbLocator.js +29 -0
  360. package/out/scripts/analytics/fleet-local-adapter.js +246 -0
  361. package/out/scripts/analytics/fleet-workflow.sh +263 -0
  362. package/out/scripts/analytics/generate-default-charts.py +685 -0
  363. package/out/scripts/analytics/hotspot-analysis-widget.js +909 -0
  364. package/out/scripts/analytics/index-traces.py +1394 -0
  365. package/out/scripts/analytics/install-analytics.sh +286 -0
  366. package/out/scripts/analytics/live-fleet-4-collector.cjs +315 -0
  367. package/out/scripts/analytics/live-fleet-4-daemon.mjs +134 -0
  368. package/out/scripts/analytics/live-fleet-4-widget.cjs +433 -0
  369. package/out/scripts/analytics/otel-collector.yaml +55 -0
  370. package/out/scripts/analytics/query-analytics.ps1 +396 -0
  371. package/out/scripts/analytics/query-analytics.sh +2551 -0
  372. package/out/scripts/analytics/session-comparison-widget.js +1468 -0
  373. package/out/scripts/analytics/session-timeline-widget.js +2137 -0
  374. package/out/scripts/analytics/setup-analytics.ps1 +432 -0
  375. package/out/scripts/analytics/setup-analytics.sh +854 -0
  376. package/out/scripts/analytics/start-analytics.ps1 +169 -0
  377. package/out/scripts/analytics/start-analytics.sh +323 -0
  378. package/out/scripts/analytics/token-cost-flamegraph-widget.js +954 -0
  379. package/out/scripts/analytics/token-cost-trend-widget.js +1062 -0
  380. package/out/scripts/analytics/workflows-collector.cjs +315 -0
  381. package/out/scripts/analytics/workflows-daemon.mjs +186 -0
  382. package/out/scripts/analytics/workflows-widget.cjs +433 -0
  383. package/out/scripts/analytics/wow-model-widget.js +191 -0
  384. package/out/scripts/gateway/Dockerfile.serve +45 -0
  385. package/out/scripts/gateway/docker-serve.sh +248 -0
  386. package/out/scripts/gateway/setup-gateway-server.ps1 +953 -0
  387. package/out/scripts/gateway/setup-gateway-server.sh +935 -0
  388. package/out/scripts/gateway/setup-gateway.ps1 +1813 -0
  389. package/out/scripts/gateway/setup-gateway.sh +1541 -0
  390. package/out/scripts/setup-cli.ps1 +86 -0
  391. package/out/scripts/setup-cli.sh +117 -0
  392. package/out/scripts/tools-manifest.json +83 -0
  393. package/out/scripts/vendor-tools.mjs +234 -0
  394. package/out/session.js +272 -0
  395. package/out/shared/agentEngines.js +170 -0
  396. package/out/shared/index.js +109 -0
  397. package/out/shared/models.js +235 -0
  398. package/out/shared/providers.js +202 -0
  399. package/out/skills/publish.js +178 -0
  400. package/out/spotlight/install.js +339 -0
  401. package/out/spotlight/publish.js +544 -0
  402. package/out/spotlight/yaml.js +31 -0
  403. package/out/store/github/setup.js +235 -0
  404. package/out/store/github/templates/build-index.ts +339 -0
  405. package/out/store/github/templates/build-index.yml +97 -0
  406. package/out/store/github/templates/package.json +11 -0
  407. package/out/store/index.js +755 -0
  408. package/out/store/oramaIndex.js +884 -0
  409. package/out/store/shared.js +241 -0
  410. package/out/sync.js +1827 -0
  411. package/out/terminalLogger.js +358 -0
  412. package/out/test/agentEnvFile.test.js +97 -0
  413. package/out/test/agentYaml.test.js +373 -0
  414. package/out/test/agentpack.test.js +639 -0
  415. package/out/test/analytics-lifecycle.test.js +174 -0
  416. package/out/test/analytics.test.js +524 -0
  417. package/out/test/auth-bootstrapApply.test.js +102 -0
  418. package/out/test/auth-broker.test.js +434 -0
  419. package/out/test/auth-callback.test.js +62 -0
  420. package/out/test/branding.test.js +229 -0
  421. package/out/test/cli-agent-id.test.js +55 -0
  422. package/out/test/credential-context.test.js +105 -0
  423. package/out/test/customEngineInstall.test.js +100 -0
  424. package/out/test/customRegistry.test.js +122 -0
  425. package/out/test/deploy-init.test.js +532 -0
  426. package/out/test/deps-auto-install.test.js +81 -0
  427. package/out/test/engine-integration.test.js +297 -0
  428. package/out/test/enginePaths.test.js +195 -0
  429. package/out/test/enginePublish.test.js +82 -0
  430. package/out/test/fleetDisplayUtils.test.js +96 -0
  431. package/out/test/gateway-client.test.js +381 -0
  432. package/out/test/gateway-lifecycle.test.js +608 -0
  433. package/out/test/gateway-outbound.test.js +531 -0
  434. package/out/test/gateway-reset-mappings.test.js +221 -0
  435. package/out/test/gateway-route-index.test.js +235 -0
  436. package/out/test/gateway-session-routing.test.js +47 -0
  437. package/out/test/gateway-session-store.test.js +216 -0
  438. package/out/test/github.test.js +232 -0
  439. package/out/test/headless-sync.test.js +279 -0
  440. package/out/test/helpers/manifest-fixtures.js +27 -0
  441. package/out/test/mcp-obo.test.js +82 -0
  442. package/out/test/mcp.test.js +307 -0
  443. package/out/test/openclaw-channel-routing.test.js +191 -0
  444. package/out/test/pathSafety.test.js +63 -0
  445. package/out/test/permissions.test.js +175 -0
  446. package/out/test/persistent-engine-worker.test.js +220 -0
  447. package/out/test/refreshPolicy.test.js +60 -0
  448. package/out/test/registry.test.js +351 -0
  449. package/out/test/runtime.test.js +349 -0
  450. package/out/test/scheduledFleetStorage.test.js +72 -0
  451. package/out/test/serve-hardening.test.js +192 -0
  452. package/out/test/serveClient.test.js +173 -0
  453. package/out/test/sessionJsonlFleet.test.js +78 -0
  454. package/out/test/shared.test.js +173 -0
  455. package/out/test/skills-mcp.test.js +299 -0
  456. package/out/test/spotlightPublish.test.js +299 -0
  457. package/out/test/spotlightYaml.test.js +58 -0
  458. package/out/test/state-sync-peer.test.js +74 -0
  459. package/out/test/store.test.js +331 -0
  460. package/out/test/stores.test.js +504 -0
  461. package/out/test/sync-switch.test.js +251 -0
  462. package/out/test/teamViewDiscovery.test.js +90 -0
  463. package/out/test/toolPath.test.js +120 -0
  464. package/out/test/utils.test.js +308 -0
  465. package/out/test/viewsConfigMerge.test.js +180 -0
  466. package/out/test/windows-command-line.test.js +75 -0
  467. package/out/utils/agentEnvFile.js +131 -0
  468. package/out/utils/binary.js +175 -0
  469. package/out/utils/index.js +37 -0
  470. package/out/utils/network.js +74 -0
  471. package/out/utils/pathSafety.js +60 -0
  472. package/out/utils/paths.js +116 -0
  473. package/out/utils/pid.js +180 -0
  474. package/out/utils/pythonRunner.js +258 -0
  475. package/out/utils/resolveNodeBinary.js +168 -0
  476. package/out/utils/toolPath.js +83 -0
  477. package/out/utils/windowsCommandLine.js +29 -0
  478. package/out/utils.js +915 -0
  479. package/out/workspaceFolders.js +59 -0
  480. 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('&', '&amp;')
1447
+ .replaceAll('<', '&lt;')
1448
+ .replaceAll('>', '&gt;')
1449
+ .replaceAll('"', '&quot;')
1450
+ .replaceAll("'", '&#39;');
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();