@agenthippo-ai/agenthippo 1.110.11 → 1.110.19

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 (534) hide show
  1. package/out/agentpack/types.js +2 -5
  2. package/out/analytics/dashboard/artifactManager.js +2 -439
  3. package/out/analytics/dashboard/fleetDisplayUtils.js +2 -162
  4. package/out/analytics/dashboard/fleetOrchestrator.js +3 -142
  5. package/out/analytics/dashboard/fleetRegistry.js +2 -79
  6. package/out/analytics/dashboard/fleetRowDefaults.js +2 -87
  7. package/out/analytics/dashboard/fleetScriptAdapter.js +2 -146
  8. package/out/analytics/dashboard/fleetTypes.js +2 -29
  9. package/out/analytics/dashboard/index.js +2 -17
  10. package/out/analytics/dashboard/indexingService.js +2 -422
  11. package/out/analytics/dashboard/localFleetAdapter.js +2 -302
  12. package/out/analytics/dashboard/performanceMonitor.js +5 -76
  13. package/out/analytics/dashboard/processManager.js +2 -209
  14. package/out/analytics/dashboard/provider.js +38 -3817
  15. package/out/analytics/dashboard/queryRunner.js +62 -750
  16. package/out/analytics/dashboard/scheduledFleetStorage.js +2 -241
  17. package/out/analytics/dashboard/sessionJsonlFleet.js +3 -204
  18. package/out/analytics/dashboard/teamViewDiscovery.js +2 -106
  19. package/out/analytics/dashboard/webview/panel.js +34 -2318
  20. package/out/analytics/dashboard/webview/runtime-bridge.js +2 -82
  21. package/out/analytics/lifecycle.js +2 -43
  22. package/out/analytics/service.js +8 -1283
  23. package/out/auth/authBaseUrl.js +2 -59
  24. package/out/auth/bootstrapApply.js +2 -71
  25. package/out/auth/brokerClient.js +2 -160
  26. package/out/auth/callbackUri.js +2 -59
  27. package/out/auth/githubStoreGate.js +2 -70
  28. package/out/auth/loopbackAuthServer.js +4 -159
  29. package/out/auth/onboardingPrompt.js +2 -112
  30. package/out/auth/pkce.js +2 -9
  31. package/out/auth/provider.js +2 -226
  32. package/out/auth/sessionStore.js +2 -156
  33. package/out/auth/signInFlow.js +2 -183
  34. package/out/cli/index.js +617 -564
  35. package/out/cli/run-custom-engine-runner.js +3 -318
  36. package/out/cli/vscode-shim.js +2 -86
  37. package/out/engines/factory.js +2 -45
  38. package/out/engines/types.js +2 -17
  39. package/out/examples/run-headless-repl.js +6 -286
  40. package/out/examples/run-headless.js +2 -187
  41. package/out/extension.js +32 -4473
  42. package/out/gateway/gatewayRouteIndex.js +6 -371
  43. package/out/gateway/gatewaySessionStore.js +3 -243
  44. package/out/gateway/index.js +2 -31
  45. package/out/gateway/resetMappings.js +2 -52
  46. package/out/gateway/service.js +5 -1249
  47. package/out/mcp/publish.js +2 -143
  48. package/out/remote/handleRemoteChat.js +2 -169
  49. package/out/remote/remotePackStatusBar.js +3 -103
  50. package/out/remote/remoteTrust.js +6 -277
  51. package/out/remote/serveClient.js +4 -277
  52. package/out/remoteSkills.js +3 -191
  53. package/out/runtime/index.js +2 -32
  54. package/out/runtime/vscode/index.js +2 -20
  55. package/out/runtime/vscode/vscode-config-provider.js +2 -77
  56. package/out/runtime/vscode/vscode-edit-tracker.js +2 -122
  57. package/out/runtime/vscode/vscode-logger.js +2 -30
  58. package/out/runtime/vscode/vscode-permission-handler.js +2 -72
  59. package/out/scripts/agentide-litellm.sh +0 -1
  60. package/out/scripts/agentide_litellm_hooks_v1.py +127 -459
  61. package/out/scripts/analytics/agent-fleet-widget.js +18 -257
  62. package/out/scripts/analytics/agentide-litellm-otel.config.yaml +0 -4
  63. package/out/scripts/analytics/duckdbLocator.js +2 -28
  64. package/out/scripts/analytics/generate-default-charts.py +43 -323
  65. package/out/scripts/analytics/hotspot-analysis-widget.js +4 -430
  66. package/out/scripts/analytics/index-traces.py +78 -1036
  67. package/out/scripts/analytics/session-comparison-widget.js +26 -472
  68. package/out/scripts/analytics/session-timeline-widget.js +25 -846
  69. package/out/scripts/analytics/token-cost-flamegraph-widget.js +4 -461
  70. package/out/scripts/analytics/token-cost-trend-widget.js +4 -534
  71. package/out/scripts/analytics/workflows-collector.cjs +4 -314
  72. package/out/scripts/analytics/workflows-daemon.mjs +9 -185
  73. package/out/scripts/analytics/workflows-widget.cjs +15 -159
  74. package/out/scripts/analytics/wow-model-widget.js +10 -120
  75. package/out/scripts/vendor-tools.mjs +2 -233
  76. package/out/skills/publish.js +3 -178
  77. package/out/store/github/setup.js +7 -212
  78. package/out/store/oramaIndex.js +2 -884
  79. package/out/sync.js +26 -1829
  80. package/out/terminalLogger.js +5 -358
  81. package/out/utils/pathSafety.js +2 -60
  82. package/out/utils/pythonRunner.js +4 -258
  83. package/out/utils/toolPath.js +2 -83
  84. package/out/workspaceFolders.js +2 -59
  85. package/package.json +1 -1
  86. package/out/agentpack/github.js +0 -547
  87. package/out/agentpack/index.js +0 -69
  88. package/out/agentpack/install.js +0 -297
  89. package/out/agentpack/manager.js +0 -441
  90. package/out/agentpack/manifest.js +0 -135
  91. package/out/agentpack/paths.js +0 -521
  92. package/out/agentpack/publish.js +0 -248
  93. package/out/agentpack/stores.js +0 -554
  94. package/out/analytics/dashboard/fleetStatusUtils.js +0 -30
  95. package/out/analytics/dashboard/fleetTitleUtils.js +0 -68
  96. package/out/analytics/dashboard/refreshPolicy.js +0 -16
  97. package/out/analytics/dashboard/scheduledFleetFingerprint.js +0 -22
  98. package/out/analytics/dashboard/scheduledFleetUtils.js +0 -22
  99. package/out/analytics/dashboard/sessionEventFleetStats.js +0 -118
  100. package/out/analytics/dashboard/viewsConfigMerge.js +0 -221
  101. package/out/analytics/dashboard/webview/codicon.ttf +0 -0
  102. package/out/analytics/identity.js +0 -0
  103. package/out/analytics/sessionEvents.js +0 -324
  104. package/out/auth/callbackUriParse.js +0 -59
  105. package/out/auth/types.js +0 -12
  106. package/out/auth-broker/config.js +0 -112
  107. package/out/auth-broker/jwt.js +0 -105
  108. package/out/auth-broker/pkce.js +0 -59
  109. package/out/auth-broker/server.js +0 -681
  110. package/out/auth-broker/store.js +0 -275
  111. package/out/authority/warrant.js +0 -206
  112. package/out/base-prompts/mcp-servers/.mcp.json +0 -13
  113. package/out/base-prompts/mcp-servers/langfuse-analytics/README.md +0 -142
  114. package/out/base-prompts/mcp-servers/langfuse-analytics/mcp.json +0 -13
  115. package/out/base-prompts/mcp-servers/langfuse-analytics/package-lock.json +0 -1192
  116. package/out/base-prompts/mcp-servers/langfuse-analytics/package.json +0 -23
  117. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/run-with-env.sh +0 -12
  118. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/sdk-test.js +0 -102
  119. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/simple-test.js +0 -137
  120. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-client.js +0 -118
  121. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-server.js +0 -209
  122. package/out/base-prompts/mcp-servers/langfuse-analytics/src/client.ts +0 -431
  123. package/out/base-prompts/mcp-servers/langfuse-analytics/src/index.ts +0 -270
  124. package/out/base-prompts/mcp-servers/langfuse-analytics/tsconfig.json +0 -18
  125. package/out/base-prompts/rules/AGENTS.md +0 -204
  126. package/out/base-prompts/skills/agent-browser/SKILL.md +0 -102
  127. package/out/base-prompts/skills/agent-builder/SKILL.md +0 -73
  128. package/out/base-prompts/skills/agent-builder/references/agent-pack.md +0 -63
  129. package/out/base-prompts/skills/agent-builder/references/crewai.md +0 -66
  130. package/out/base-prompts/skills/agent-builder/references/langchain.md +0 -60
  131. package/out/base-prompts/skills/agent-builder/references/raw-sdk.md +0 -73
  132. package/out/base-prompts/skills/agent-engine-creator/SKILL.md +0 -586
  133. package/out/base-prompts/skills/agent-engine-installer/SKILL.md +0 -106
  134. package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.mjs +0 -259
  135. package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.ps1 +0 -189
  136. package/out/base-prompts/skills/agenthippo-cli/SKILL.md +0 -69
  137. package/out/base-prompts/skills/agenthippo-cli/scripts/install.sh +0 -63
  138. package/out/base-prompts/skills/agentpack-creator/SKILL.md +0 -375
  139. package/out/base-prompts/skills/agentpack-creator/references/agent-templates.md +0 -261
  140. package/out/base-prompts/skills/agentpack-creator/references/agents-md-guide.md +0 -195
  141. package/out/base-prompts/skills/agentpack-creator/scripts/init_pack.py +0 -391
  142. package/out/base-prompts/skills/agentpack-creator/scripts/init_pack_test.py +0 -294
  143. package/out/base-prompts/skills/agentpack-testbench/SKILL.md +0 -135
  144. package/out/base-prompts/skills/algorithmic-art/LICENSE.txt +0 -202
  145. package/out/base-prompts/skills/algorithmic-art/SKILL.md +0 -405
  146. package/out/base-prompts/skills/algorithmic-art/templates/generator_template.js +0 -223
  147. package/out/base-prompts/skills/algorithmic-art/templates/viewer.html +0 -599
  148. package/out/base-prompts/skills/debug-agent/SKILL.md +0 -83
  149. package/out/base-prompts/skills/desktop-automation/SKILL.md +0 -166
  150. package/out/base-prompts/skills/desktop-automation/references/agenthippo-ui-recipes.md +0 -441
  151. package/out/base-prompts/skills/desktop-automation/scripts/windows-desktop-uia.psm1 +0 -280
  152. package/out/base-prompts/skills/docx/LICENSE.txt +0 -30
  153. package/out/base-prompts/skills/docx/SKILL.md +0 -197
  154. package/out/base-prompts/skills/docx/docx-js.md +0 -350
  155. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
  156. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
  157. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
  158. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
  159. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
  160. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
  161. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
  162. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
  163. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
  164. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
  165. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
  166. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
  167. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
  168. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
  169. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
  170. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
  171. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
  172. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
  173. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
  174. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
  175. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
  176. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
  177. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
  178. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
  179. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
  180. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
  181. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
  182. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
  183. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
  184. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
  185. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
  186. package/out/base-prompts/skills/docx/ooxml/schemas/mce/mc.xsd +0 -75
  187. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +0 -560
  188. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +0 -67
  189. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +0 -14
  190. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +0 -20
  191. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +0 -13
  192. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
  193. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +0 -8
  194. package/out/base-prompts/skills/docx/ooxml/scripts/pack.py +0 -159
  195. package/out/base-prompts/skills/docx/ooxml/scripts/unpack.py +0 -29
  196. package/out/base-prompts/skills/docx/ooxml/scripts/validate.py +0 -69
  197. package/out/base-prompts/skills/docx/ooxml/scripts/validation/__init__.py +0 -15
  198. package/out/base-prompts/skills/docx/ooxml/scripts/validation/base.py +0 -951
  199. package/out/base-prompts/skills/docx/ooxml/scripts/validation/docx.py +0 -274
  200. package/out/base-prompts/skills/docx/ooxml/scripts/validation/pptx.py +0 -315
  201. package/out/base-prompts/skills/docx/ooxml/scripts/validation/redlining.py +0 -279
  202. package/out/base-prompts/skills/docx/ooxml.md +0 -610
  203. package/out/base-prompts/skills/docx/scripts/__init__.py +0 -1
  204. package/out/base-prompts/skills/docx/scripts/document.py +0 -1276
  205. package/out/base-prompts/skills/docx/scripts/templates/comments.xml +0 -3
  206. package/out/base-prompts/skills/docx/scripts/templates/commentsExtended.xml +0 -3
  207. package/out/base-prompts/skills/docx/scripts/templates/commentsExtensible.xml +0 -3
  208. package/out/base-prompts/skills/docx/scripts/templates/commentsIds.xml +0 -3
  209. package/out/base-prompts/skills/docx/scripts/templates/people.xml +0 -3
  210. package/out/base-prompts/skills/docx/scripts/utilities.py +0 -374
  211. package/out/base-prompts/skills/gateway-outbound/SKILL.md +0 -136
  212. package/out/base-prompts/skills/mcp-builder/LICENSE.txt +0 -202
  213. package/out/base-prompts/skills/mcp-builder/SKILL.md +0 -328
  214. package/out/base-prompts/skills/mcp-builder/reference/evaluation.md +0 -602
  215. package/out/base-prompts/skills/mcp-builder/reference/mcp_best_practices.md +0 -249
  216. package/out/base-prompts/skills/mcp-builder/reference/node_mcp_server.md +0 -970
  217. package/out/base-prompts/skills/mcp-builder/reference/python_mcp_server.md +0 -719
  218. package/out/base-prompts/skills/mcp-builder/scripts/connections.py +0 -151
  219. package/out/base-prompts/skills/mcp-builder/scripts/evaluation.py +0 -373
  220. package/out/base-prompts/skills/mcp-builder/scripts/example_evaluation.xml +0 -22
  221. package/out/base-prompts/skills/mcp-builder/scripts/requirements.txt +0 -2
  222. package/out/base-prompts/skills/openclaw/SKILL.md +0 -406
  223. package/out/base-prompts/skills/openclaw/scripts/run-smoke.sh +0 -61
  224. package/out/base-prompts/skills/pdf/LICENSE.txt +0 -30
  225. package/out/base-prompts/skills/pdf/SKILL.md +0 -294
  226. package/out/base-prompts/skills/pdf/forms.md +0 -205
  227. package/out/base-prompts/skills/pdf/reference.md +0 -612
  228. package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes.py +0 -70
  229. package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes_test.py +0 -226
  230. package/out/base-prompts/skills/pdf/scripts/check_fillable_fields.py +0 -12
  231. package/out/base-prompts/skills/pdf/scripts/convert_pdf_to_images.py +0 -35
  232. package/out/base-prompts/skills/pdf/scripts/create_validation_image.py +0 -41
  233. package/out/base-prompts/skills/pdf/scripts/extract_form_field_info.py +0 -152
  234. package/out/base-prompts/skills/pdf/scripts/fill_fillable_fields.py +0 -114
  235. package/out/base-prompts/skills/pdf/scripts/fill_pdf_form_with_annotations.py +0 -108
  236. package/out/base-prompts/skills/pptx/LICENSE.txt +0 -30
  237. package/out/base-prompts/skills/pptx/SKILL.md +0 -484
  238. package/out/base-prompts/skills/pptx/html2pptx.md +0 -625
  239. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
  240. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
  241. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
  242. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
  243. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
  244. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
  245. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
  246. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
  247. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
  248. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
  249. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
  250. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
  251. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
  252. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
  253. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
  254. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
  255. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
  256. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
  257. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
  258. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
  259. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
  260. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
  261. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
  262. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
  263. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
  264. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
  265. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
  266. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
  267. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
  268. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
  269. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
  270. package/out/base-prompts/skills/pptx/ooxml/schemas/mce/mc.xsd +0 -75
  271. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +0 -560
  272. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +0 -67
  273. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +0 -14
  274. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +0 -20
  275. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +0 -13
  276. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
  277. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +0 -8
  278. package/out/base-prompts/skills/pptx/ooxml/scripts/pack.py +0 -159
  279. package/out/base-prompts/skills/pptx/ooxml/scripts/unpack.py +0 -29
  280. package/out/base-prompts/skills/pptx/ooxml/scripts/validate.py +0 -69
  281. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/__init__.py +0 -15
  282. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/base.py +0 -951
  283. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/docx.py +0 -274
  284. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/pptx.py +0 -315
  285. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/redlining.py +0 -279
  286. package/out/base-prompts/skills/pptx/ooxml.md +0 -427
  287. package/out/base-prompts/skills/pptx/scripts/html2pptx.js +0 -984
  288. package/out/base-prompts/skills/pptx/scripts/inventory.py +0 -1020
  289. package/out/base-prompts/skills/pptx/scripts/rearrange.py +0 -231
  290. package/out/base-prompts/skills/pptx/scripts/replace.py +0 -385
  291. package/out/base-prompts/skills/pptx/scripts/thumbnail.py +0 -450
  292. package/out/base-prompts/skills/prompt-lab/SKILL.md +0 -149
  293. package/out/base-prompts/skills/prompt-lab/scripts/query-analytics.sh +0 -2551
  294. package/out/base-prompts/skills/skill-creator/LICENSE.txt +0 -202
  295. package/out/base-prompts/skills/skill-creator/SKILL.md +0 -371
  296. package/out/base-prompts/skills/skill-creator/references/output-patterns.md +0 -82
  297. package/out/base-prompts/skills/skill-creator/references/workflows.md +0 -28
  298. package/out/base-prompts/skills/skill-creator/scripts/init_skill.py +0 -303
  299. package/out/base-prompts/skills/skill-creator/scripts/package_skill.py +0 -110
  300. package/out/base-prompts/skills/skill-creator/scripts/quick_validate.py +0 -95
  301. package/out/base-prompts/skills/skill-installer/SKILL.md +0 -251
  302. package/out/base-prompts/skills/skill-installer/references/store-api.md +0 -219
  303. package/out/base-prompts/skills/spotlight-dashboard/SKILL.md +0 -83
  304. package/out/base-prompts/skills/spotlight-insights/SKILL.md +0 -101
  305. package/out/base-prompts/skills/spotlight-optimize/SKILL.md +0 -93
  306. package/out/base-prompts/skills/spotlight-optimize/scripts/query-analytics.sh +0 -2551
  307. package/out/base-prompts/skills/spotlight-query/SKILL.md +0 -314
  308. package/out/base-prompts/skills/spotlight-query/references/otel-schema.md +0 -163
  309. package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.sh +0 -2551
  310. package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.test.sh +0 -744
  311. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/sessions/sess-test-1.jsonl +0 -14
  312. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/.duckdb-index.json +0 -7
  313. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/sample.jsonl +0 -5
  314. package/out/base-prompts/skills/spotlight-report/SKILL.md +0 -86
  315. package/out/base-prompts/skills/spotlight-visualize/SKILL.md +0 -92
  316. package/out/base-prompts/skills/spotlight-visualize/scripts/query-analytics.sh +0 -2551
  317. package/out/base-prompts/skills/xlsx/LICENSE.txt +0 -30
  318. package/out/base-prompts/skills/xlsx/SKILL.md +0 -289
  319. package/out/base-prompts/skills/xlsx/recalc.py +0 -178
  320. package/out/branding.js +0 -49
  321. package/out/cli/commands/anywhere.js +0 -66
  322. package/out/cli/commands/ask.js +0 -246
  323. package/out/cli/commands/auth-broker.js +0 -24
  324. package/out/cli/commands/auth.js +0 -219
  325. package/out/cli/commands/chat.js +0 -357
  326. package/out/cli/commands/conformance.js +0 -249
  327. package/out/cli/commands/data-plane.js +0 -151
  328. package/out/cli/commands/deploy.js +0 -263
  329. package/out/cli/commands/gateway.js +0 -218
  330. package/out/cli/commands/idp.js +0 -85
  331. package/out/cli/commands/pack.js +0 -399
  332. package/out/cli/commands/probe-custom-engine.js +0 -82
  333. package/out/cli/commands/sandbox.js +0 -351
  334. package/out/cli/commands/serve.js +0 -1950
  335. package/out/cli/commands/store.js +0 -927
  336. package/out/cli/credential-context.js +0 -108
  337. package/out/cli/data-plane/broker.js +0 -78
  338. package/out/cli/data-plane/connector-host.js +0 -121
  339. package/out/cli/data-plane/egress.js +0 -189
  340. package/out/cli/data-plane/gateway.js +0 -218
  341. package/out/cli/data-plane/index.js +0 -153
  342. package/out/cli/data-plane/registry.js +0 -89
  343. package/out/cli/data-plane/remote.js +0 -106
  344. package/out/cli/data-plane/sidecar.js +0 -225
  345. package/out/cli/deploy/blueprints/registry.js +0 -84
  346. package/out/cli/deploy/layers/identity.js +0 -175
  347. package/out/cli/deploy-init.js +0 -3023
  348. package/out/cli/deps.js +0 -342
  349. package/out/cli/idp-init.js +0 -338
  350. package/out/cli/pack-install-ref.js +0 -15
  351. package/out/cli/pack-loader.js +0 -418
  352. package/out/cli/pack-runtime.js +0 -61
  353. package/out/cli/runtime.js +0 -450
  354. package/out/cli/serve-hardening.js +0 -776
  355. package/out/cli/state-sync.js +0 -424
  356. package/out/cli/utils.js +0 -231
  357. package/out/engines/base.js +0 -188
  358. package/out/engines/claude.js +0 -1254
  359. package/out/engines/codex.js +0 -862
  360. package/out/engines/custom.js +0 -680
  361. package/out/engines/customEngineIpc.js +0 -95
  362. package/out/engines/customEngineRunnerEmbed.js +0 -107
  363. package/out/engines/customRegistry.js +0 -274
  364. package/out/engines/embeddedCustomEngineRunner.generated.js +0 -9
  365. package/out/engines/engineApiKey.js +0 -138
  366. package/out/engines/install.js +0 -372
  367. package/out/engines/persistentEngineWorker.js +0 -320
  368. package/out/engines/publish.js +0 -341
  369. package/out/evidence/sessionShip.js +0 -643
  370. package/out/gateway/bridge.js +0 -843
  371. package/out/gateway/cli-agent-id.js +0 -36
  372. package/out/gateway/client.js +0 -336
  373. package/out/gateway/client.test.js +0 -395
  374. package/out/gateway/gateway-scope-errors.js +0 -14
  375. package/out/gateway/gatewayBackup.js +0 -76
  376. package/out/gateway/installSource.js +0 -68
  377. package/out/gateway/lifecycle.js +0 -3190
  378. package/out/gateway/openclaw-channel-routing.js +0 -317
  379. package/out/gateway/outboundHandles.js +0 -373
  380. package/out/gateway/outboundPaths.js +0 -75
  381. package/out/gateway/outboundSend.js +0 -254
  382. package/out/gateway/protocol.js +0 -48
  383. package/out/gateway/secretsPreflight.js +0 -90
  384. package/out/gateway/sessionRouting.js +0 -77
  385. package/out/headless/index.js +0 -546
  386. package/out/headless/sync.js +0 -473
  387. package/out/live-e2e/mcp-demo/server.mjs +0 -32
  388. package/out/live-probe/connector.mjs +0 -8
  389. package/out/live-probe/env-report.mjs +0 -8
  390. package/out/mcp.js +0 -654
  391. package/out/pack/caps.js +0 -271
  392. package/out/pack/governor.js +0 -326
  393. package/out/pack/lease-schema.js +0 -273
  394. package/out/pack/lease.js +0 -682
  395. package/out/pack/reconcile.js +0 -217
  396. package/out/pack/signature.js +0 -839
  397. package/out/permissions.js +0 -108
  398. package/out/permissions.mapping.yaml +0 -68
  399. package/out/registry/index.js +0 -332
  400. package/out/runner/host.js +0 -141
  401. package/out/runner/protocol.js +0 -128
  402. package/out/runner/remoteEngine.js +0 -166
  403. package/out/runtime/interfaces/config-provider.js +0 -32
  404. package/out/runtime/interfaces/edit-tracker.js +0 -24
  405. package/out/runtime/interfaces/index.js +0 -29
  406. package/out/runtime/interfaces/logger.js +0 -40
  407. package/out/runtime/interfaces/permission-handler.js +0 -66
  408. package/out/runtime/interfaces/session-store.js +0 -342
  409. package/out/runtime/vscode/vscode-session-store.js +0 -22
  410. package/out/sandbox/containerDriver.js +0 -572
  411. package/out/sandbox/processDriver.js +0 -318
  412. package/out/sandbox/sandboxRunner.js +0 -303
  413. package/out/sandbox/support.js +0 -301
  414. package/out/sandbox/types.js +0 -55
  415. package/out/scripts/analytics/fleet-local-adapter.js +0 -246
  416. package/out/scripts/analytics/live-fleet-4-collector.cjs +0 -315
  417. package/out/scripts/analytics/live-fleet-4-daemon.mjs +0 -134
  418. package/out/scripts/analytics/live-fleet-4-widget.cjs +0 -433
  419. package/out/scripts/gateway/Dockerfile.serve +0 -45
  420. package/out/scripts/gateway/docker-serve.sh +0 -248
  421. package/out/scripts/gateway/setup-gateway-server.ps1 +0 -953
  422. package/out/scripts/gateway/setup-gateway-server.sh +0 -935
  423. package/out/session.js +0 -272
  424. package/out/shared/agentEngines.js +0 -170
  425. package/out/shared/index.js +0 -109
  426. package/out/shared/models.js +0 -136
  427. package/out/shared/providers.js +0 -205
  428. package/out/spotlight/install.js +0 -345
  429. package/out/spotlight/publish.js +0 -544
  430. package/out/spotlight/yaml.js +0 -31
  431. package/out/store/index.js +0 -786
  432. package/out/store/shared.js +0 -241
  433. package/out/test/agentEnvFile.test.js +0 -97
  434. package/out/test/agentYaml.test.js +0 -373
  435. package/out/test/agentpack.test.js +0 -639
  436. package/out/test/analytics-lifecycle.test.js +0 -174
  437. package/out/test/analytics.test.js +0 -756
  438. package/out/test/auth-baseurl.test.js +0 -132
  439. package/out/test/auth-bootstrapApply.test.js +0 -123
  440. package/out/test/auth-broker-hardening.test.js +0 -227
  441. package/out/test/auth-broker.test.js +0 -541
  442. package/out/test/auth-callback.test.js +0 -62
  443. package/out/test/blueprint-registry.test.js +0 -105
  444. package/out/test/branding.test.js +0 -242
  445. package/out/test/caps-governor.test.js +0 -460
  446. package/out/test/cli-agent-id.test.js +0 -55
  447. package/out/test/codex-new-file-survival.test.js +0 -119
  448. package/out/test/configuration-scopes.test.js +0 -131
  449. package/out/test/credential-context.test.js +0 -160
  450. package/out/test/custom-env-layering.test.js +0 -151
  451. package/out/test/customEngineInstall.test.js +0 -100
  452. package/out/test/customRegistry.test.js +0 -122
  453. package/out/test/data-plane-engine-wiring.test.js +0 -282
  454. package/out/test/data-plane.test.js +0 -624
  455. package/out/test/deploy-init.test.js +0 -860
  456. package/out/test/deps-auto-install.test.js +0 -81
  457. package/out/test/egress-broker.test.js +0 -172
  458. package/out/test/engine-integration.test.js +0 -297
  459. package/out/test/enginePaths.test.js +0 -195
  460. package/out/test/enginePublish.test.js +0 -82
  461. package/out/test/fleetDisplayUtils.test.js +0 -100
  462. package/out/test/gateway-client.test.js +0 -409
  463. package/out/test/gateway-install-source.test.js +0 -111
  464. package/out/test/gateway-lifecycle.test.js +0 -608
  465. package/out/test/gateway-outbound.test.js +0 -538
  466. package/out/test/gateway-reset-mappings.test.js +0 -221
  467. package/out/test/gateway-route-index.test.js +0 -235
  468. package/out/test/gateway-secrets-preflight.test.js +0 -127
  469. package/out/test/gateway-session-routing.test.js +0 -65
  470. package/out/test/gateway-session-store.test.js +0 -216
  471. package/out/test/github.test.js +0 -232
  472. package/out/test/governor-hardening.test.js +0 -222
  473. package/out/test/headless-sync.test.js +0 -301
  474. package/out/test/helpers/manifest-fixtures.js +0 -27
  475. package/out/test/idp-init.test.js +0 -151
  476. package/out/test/lease-signature.test.js +0 -357
  477. package/out/test/lease-trust-root.test.js +0 -273
  478. package/out/test/lease-validation.test.js +0 -224
  479. package/out/test/lease.test.js +0 -114
  480. package/out/test/localFleetAdapter.test.js +0 -91
  481. package/out/test/mcp-obo.test.js +0 -107
  482. package/out/test/mcp.test.js +0 -389
  483. package/out/test/model-routing.test.js +0 -133
  484. package/out/test/openclaw-channel-routing.test.js +0 -191
  485. package/out/test/pack-admission.test.js +0 -216
  486. package/out/test/pack-digest-parity.test.js +0 -140
  487. package/out/test/pack-reconcile.test.js +0 -183
  488. package/out/test/pack-signature-artifact.test.js +0 -103
  489. package/out/test/pack-signature.test.js +0 -176
  490. package/out/test/pack-trust-root.test.js +0 -202
  491. package/out/test/pathSafety.test.js +0 -63
  492. package/out/test/permissions.test.js +0 -175
  493. package/out/test/persistent-engine-worker.test.js +0 -220
  494. package/out/test/refreshPolicy.test.js +0 -60
  495. package/out/test/registry.test.js +0 -351
  496. package/out/test/remote-trust.test.js +0 -242
  497. package/out/test/runner-protocol.test.js +0 -249
  498. package/out/test/runtime.test.js +0 -349
  499. package/out/test/sandbox-driver.test.js +0 -553
  500. package/out/test/sandbox-hardening.test.js +0 -406
  501. package/out/test/sandbox-runner.test.js +0 -343
  502. package/out/test/sandbox-scope.test.js +0 -449
  503. package/out/test/scheduledFleetStorage.test.js +0 -72
  504. package/out/test/serve-cron-acceptance.test.js +0 -370
  505. package/out/test/serve-gateway-turn.test.js +0 -389
  506. package/out/test/serve-hardening.test.js +0 -591
  507. package/out/test/serveClient.test.js +0 -209
  508. package/out/test/sessionJsonlFleet.test.js +0 -78
  509. package/out/test/sessionShip.test.js +0 -618
  510. package/out/test/shared.test.js +0 -173
  511. package/out/test/skills-mcp.test.js +0 -299
  512. package/out/test/spotlightPublish.test.js +0 -327
  513. package/out/test/spotlightYaml.test.js +0 -58
  514. package/out/test/state-sync-peer.test.js +0 -156
  515. package/out/test/store-install-safe.test.js +0 -69
  516. package/out/test/store.test.js +0 -331
  517. package/out/test/stores.test.js +0 -504
  518. package/out/test/sync-switch.test.js +0 -251
  519. package/out/test/teamViewDiscovery.test.js +0 -90
  520. package/out/test/tls-bypass.test.js +0 -90
  521. package/out/test/toolPath.test.js +0 -120
  522. package/out/test/utils.test.js +0 -308
  523. package/out/test/viewsConfigMerge.test.js +0 -180
  524. package/out/test/warrant.test.js +0 -138
  525. package/out/test/windows-command-line.test.js +0 -75
  526. package/out/utils/agentEnvFile.js +0 -131
  527. package/out/utils/binary.js +0 -175
  528. package/out/utils/index.js +0 -37
  529. package/out/utils/network.js +0 -74
  530. package/out/utils/paths.js +0 -116
  531. package/out/utils/pid.js +0 -180
  532. package/out/utils/resolveNodeBinary.js +0 -168
  533. package/out/utils/windowsCommandLine.js +0 -29
  534. package/out/utils.js +0 -968
@@ -1,3742 +1,38 @@
1
- "use strict";
2
- /*---------------------------------------------------------------------------------------------
3
- * Copyright (c) AgentHippo.ai. All rights reserved.
4
- *--------------------------------------------------------------------------------------------*/
5
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- var desc = Object.getOwnPropertyDescriptor(m, k);
8
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
- desc = { enumerable: true, get: function() { return m[k]; } };
10
- }
11
- Object.defineProperty(o, k2, desc);
12
- }) : (function(o, m, k, k2) {
13
- if (k2 === undefined) k2 = k;
14
- o[k2] = m[k];
15
- }));
16
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
- Object.defineProperty(o, "default", { enumerable: true, value: v });
18
- }) : function(o, v) {
19
- o["default"] = v;
20
- });
21
- var __importStar = (this && this.__importStar) || (function () {
22
- var ownKeys = function(o) {
23
- ownKeys = Object.getOwnPropertyNames || function (o) {
24
- var ar = [];
25
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
- return ar;
27
- };
28
- return ownKeys(o);
29
- };
30
- return function (mod) {
31
- if (mod && mod.__esModule) return mod;
32
- var result = {};
33
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
- __setModuleDefault(result, mod);
35
- return result;
36
- };
37
- })();
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.AnalyticsDashboardProvider = exports.shouldRunScriptsOnNavigation = void 0;
40
- const vscode = __importStar(require("vscode"));
41
- const path = __importStar(require("path"));
42
- const os = __importStar(require("os"));
43
- const fs_1 = require("fs");
44
- const child_process_1 = require("child_process");
45
- const util_1 = require("util");
46
- const pythonRunner_1 = require("../../utils/pythonRunner");
47
- const toolPath_1 = require("../../utils/toolPath");
48
- const index_1 = require("../../utils/index");
49
- const fleetOrchestrator_1 = require("./fleetOrchestrator");
50
- const fleetDisplayUtils_1 = require("./fleetDisplayUtils");
51
- const fleetRowDefaults_1 = require("./fleetRowDefaults");
52
- const fleetTypes_1 = require("./fleetTypes");
53
- const viewsConfigMerge_1 = require("./viewsConfigMerge");
54
- const teamViewDiscovery_1 = require("./teamViewDiscovery");
55
- const pathSafety_1 = require("../../utils/pathSafety");
56
- const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
57
- var viewsConfigMerge_2 = require("./viewsConfigMerge");
58
- Object.defineProperty(exports, "shouldRunScriptsOnNavigation", { enumerable: true, get: function () { return viewsConfigMerge_2.shouldRunScriptsOnNavigation; } });
59
- function postToWebview(webview, message) {
60
- if (!webview) {
61
- return;
62
- }
63
- void webview.postMessage(message);
64
- }
65
- // -----------------------------------------------------------------------------
66
- // Constants
67
- // -----------------------------------------------------------------------------
68
- const FILTER_AUTOGEN_DEBOUNCE_MS = 700;
69
- const FILTER_AUTOGEN_COOLDOWN_MS = 12_000;
70
- const MAX_FILTER_AUTOGEN_HISTORY = 100;
71
- const DEFAULT_SEED_REGISTRY_FILENAME = 'default-seed-scripts.json';
72
- // Fleet widget excluded so Global/Workspace default scripts stay distinct; Fleet view has scripts: ['agent-fleet-widget.js'] only.
73
- const FALLBACK_DEFAULT_SEED_SCRIPT_NAMES = [
74
- 'generate-default-charts.py',
75
- 'duckdbLocator.js',
76
- 'wow-model-widget.js',
77
- 'session-timeline-widget.js',
78
- 'session-comparison-widget.js',
79
- 'hotspot-analysis-widget.js',
80
- 'token-cost-flamegraph-widget.js',
81
- ];
82
- const USER_SCRIPT_MAX_CONCURRENCY = 2;
83
- const WIDGET_RUN_LOG_RETENTION_COUNT = 20;
84
- /** Debounce views.yaml watcher reloads (global + workspace). */
85
- const VIEWS_CONFIG_DEBOUNCE_MS = 400;
86
- // -----------------------------------------------------------------------------
87
- // Provider Implementation
88
- // -----------------------------------------------------------------------------
89
- class AnalyticsDashboardProvider {
90
- extensionContext;
91
- queryRunner;
92
- artifactManager;
93
- outputChannel;
94
- getIndexingService;
95
- viewIdSuffix;
96
- static viewType = 'agentide.analyticsView';
97
- webviewView;
98
- artifactWatcher;
99
- viewsConfigWatcher;
100
- viewsConfigDebounceTimeout;
101
- generationAbortController;
102
- isBatchingArtifactUpdates = false;
103
- pendingArtifactRefresh = false;
104
- artifactRefreshQueue = Promise.resolve();
105
- pendingFilterAutoGenerateTimer;
106
- isRefreshInProgress = false;
107
- hasPendingRefreshRequest = false;
108
- activeDuckDbScriptCount = 0;
109
- duckDbScriptWaiters = [];
110
- duckDbHeavyScriptNames = new Set([
111
- 'generate-default-charts.py',
112
- 'wow-model-widget.js',
113
- 'session-timeline-widget.js',
114
- 'session-comparison-widget.js',
115
- 'agent-fleet-widget.js',
116
- ]);
117
- static FLEET_EXPORT_FILENAME = 'fleet.json';
118
- /** @deprecated Legacy export name; read fallback only. */
119
- static LEGACY_FLEET_EXPORT_FILENAME = 'agent-fleet.json';
120
- static DEFAULT_GALLERY_IGNORE = ['.json', '.csv'];
121
- fleetRefreshVersion = 0;
122
- autoGenerationAttemptByFilter = new Map();
123
- htmlPreviewPanels = new Map();
124
- viewsConfig = this.createDefaultViewsConfig();
125
- agentFilterId;
126
- disposables = [];
127
- /** Serializes create/duplicate/delete view so rapid UI clicks cannot race views.yaml. */
128
- viewConfigMutationQueue = Promise.resolve();
129
- constructor(extensionContext, queryRunner, artifactManager, outputChannel, getIndexingService,
130
- /** When 'team', uses a separate views config and defaults to Fleet view (for Agent Team sidebar). */
131
- viewIdSuffix) {
132
- this.extensionContext = extensionContext;
133
- this.queryRunner = queryRunner;
134
- this.artifactManager = artifactManager;
135
- this.outputChannel = outputChannel;
136
- this.getIndexingService = getIndexingService;
137
- this.viewIdSuffix = viewIdSuffix;
138
- }
139
- indexingService() {
140
- return this.getIndexingService?.();
141
- }
142
- // -------------------------------------------------------------------------
143
- // WebviewViewProvider Implementation
144
- // -------------------------------------------------------------------------
145
- resolveWebviewView(webviewView, _context, _token) {
146
- this.webviewView = webviewView;
147
- // Configure webview options
148
- this.syncArtifactContext();
149
- webviewView.webview.options = {
150
- enableScripts: true,
151
- localResourceRoots: [
152
- this.extensionContext.extensionUri,
153
- // Workspace-local and home-global analytics roots (store-installed global views).
154
- vscode.Uri.file(this.artifactManager.getAnalyticsRoot()),
155
- vscode.Uri.file(this.artifactManager.getGlobalAnalyticsRoot()),
156
- ],
157
- };
158
- // Set HTML content
159
- webviewView.webview.html = this.getHtmlContent(webviewView.webview);
160
- // Handle messages from webview
161
- this.disposables.push(webviewView.webview.onDidReceiveMessage(this.handleMessage.bind(this)));
162
- // Start watching artifacts for live updates (always read gallery ignore patterns from disabled-charts.yaml)
163
- this.artifactWatcher = this.artifactManager.watchForChanges((artifacts) => {
164
- if (this.isBatchingArtifactUpdates) {
165
- this.pendingArtifactRefresh = true;
166
- return;
167
- }
168
- this.loadDisabledChartsConfig().then((disabledConfig) => {
169
- this.sendArtifactsToWebview(artifacts, disabledConfig.galleryIgnore);
170
- });
171
- });
172
- this.disposables.push(this.artifactWatcher);
173
- // Watch views config (global home + workspace) so store installs and edits appear immediately.
174
- const scheduleViewsConfigReload = () => {
175
- if (this.viewsConfigDebounceTimeout) {
176
- clearTimeout(this.viewsConfigDebounceTimeout);
177
- }
178
- this.viewsConfigDebounceTimeout = setTimeout(() => {
179
- void this.handleViewsConfigChanged();
180
- }, VIEWS_CONFIG_DEBOUNCE_MS);
181
- };
182
- for (const configPath of this.getViewsConfigPathsToWatch()) {
183
- const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(path.dirname(configPath), path.basename(configPath)));
184
- this.disposables.push(watcher, watcher.onDidChange(scheduleViewsConfigReload), watcher.onDidCreate(scheduleViewsConfigReload), watcher.onDidDelete(scheduleViewsConfigReload));
185
- }
186
- // Cleanup old artifacts
187
- const retentionDays = vscode.workspace.getConfiguration('agentide.analytics')
188
- .get('artifactRetentionDays', 30);
189
- this.artifactManager.cleanup(retentionDays).then(result => {
190
- if (result.deletedCount > 0) {
191
- this.outputChannel.appendLine(`[Dashboard] Cleaned up ${result.deletedCount} old artifacts (${(result.freedBytes / 1024).toFixed(1)} KB)`);
192
- }
193
- });
194
- // Handle visibility changes
195
- this.disposables.push(webviewView.onDidChangeVisibility(() => {
196
- if (webviewView.visible) {
197
- this.onPanelBecameVisible();
198
- }
199
- }));
200
- // Initial load
201
- this.onPanelBecameVisible();
202
- }
203
- // -------------------------------------------------------------------------
204
- // Panel Lifecycle
205
- // -------------------------------------------------------------------------
206
- resolveWorkspaceRootPath() {
207
- const activeEditorUri = vscode.window.activeTextEditor?.document.uri;
208
- const activeFolder = activeEditorUri
209
- ? vscode.workspace.getWorkspaceFolder(activeEditorUri)
210
- : undefined;
211
- const folder = activeFolder ?? vscode.workspace.workspaceFolders?.[0];
212
- return folder?.uri.fsPath;
213
- }
214
- resolveWorkspaceProjectId() {
215
- const folderPath = this.resolveWorkspaceRootPath();
216
- if (folderPath) {
217
- const projectId = path.basename(folderPath).trim();
218
- if (projectId) {
219
- return projectId;
220
- }
221
- }
222
- const activeEditorUri = vscode.window.activeTextEditor?.document.uri;
223
- const activeFolder = activeEditorUri ? vscode.workspace.getWorkspaceFolder(activeEditorUri) : undefined;
224
- const folder = activeFolder ?? vscode.workspace.workspaceFolders?.[0];
225
- if (folder?.uri.fsPath) {
226
- const projectId = path.basename(folder.uri.fsPath).trim();
227
- if (projectId) {
228
- return projectId;
229
- }
230
- }
231
- const workspaceName = vscode.workspace.name?.trim();
232
- return workspaceName || undefined;
233
- }
234
- syncArtifactContext() {
235
- this.artifactManager.setWorkspaceRoot(this.resolveWorkspaceRootPath());
236
- const filters = this.getActiveFilters();
237
- this.artifactManager.setViewContext({
238
- scope: filters.scope === 'workspace' ? 'workspace' : 'global',
239
- projectId: filters.projectId,
240
- agentId: filters.agentId,
241
- viewId: this.getActiveViewId(),
242
- });
243
- }
244
- async withArtifactViewContext(view, fn) {
245
- this.artifactManager.setWorkspaceRoot(this.resolveWorkspaceRootPath());
246
- const activeView = this.getActiveView();
247
- const previousContext = this.getViewContext(activeView);
248
- this.artifactManager.setViewContext(this.getViewContext(view));
249
- try {
250
- return await fn();
251
- }
252
- finally {
253
- this.artifactManager.setViewContext(previousContext);
254
- }
255
- }
256
- runViewConfigMutation(fn) {
257
- const run = this.viewConfigMutationQueue.then(fn, fn);
258
- this.viewConfigMutationQueue = run.then(() => undefined, () => undefined);
259
- return run;
260
- }
261
- /** Fleet HTML widget; only the Fleet view should run it by default. */
262
- static FLEET_WIDGET_BASENAME = 'agent-fleet-widget.js';
263
- stripFleetWidgetFromGlobalWorkspace(viewId, scripts) {
264
- const id = this.normalizeViewId(viewId);
265
- if (id !== 'global' && id !== 'workspace') {
266
- return scripts;
267
- }
268
- return scripts.filter(s => this.normalizeBasename(s) !== AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME);
269
- }
270
- normalizeViewId(raw, fallback = 'global') {
271
- return (0, viewsConfigMerge_1.normalizeDashboardViewId)(raw, fallback);
272
- }
273
- getViewsConfigFilename() {
274
- return this.viewIdSuffix ? `views-${this.viewIdSuffix}.yaml` : 'views.yaml';
275
- }
276
- getGlobalViewsConfigPath() {
277
- return path.join(this.artifactManager.getGlobalAnalyticsRoot(), 'panel', this.getViewsConfigFilename());
278
- }
279
- getWorkspaceViewsConfigPath() {
280
- const workspaceRoot = this.resolveWorkspaceRootPath();
281
- if (!workspaceRoot) {
282
- return undefined;
283
- }
284
- return path.join(workspaceRoot, '.agent-hippo', 'analytics', 'panel', this.getViewsConfigFilename());
285
- }
286
- async readPanelConfigLayers(configFilename = this.getViewsConfigFilename()) {
287
- const globalPath = path.join(this.artifactManager.getGlobalAnalyticsRoot(), 'panel', configFilename);
288
- const workspaceRoot = this.resolveWorkspaceRootPath();
289
- const workspacePath = workspaceRoot
290
- ? path.join(workspaceRoot, '.agent-hippo', 'analytics', 'panel', configFilename)
291
- : undefined;
292
- return Promise.all([
293
- this.readViewsConfigFile(globalPath),
294
- workspacePath ? this.readViewsConfigFile(workspacePath) : Promise.resolve(undefined),
295
- ]);
296
- }
297
- getViewsConfigPathsToWatch() {
298
- const paths = [this.getGlobalViewsConfigPath()];
299
- const workspacePath = this.getWorkspaceViewsConfigPath();
300
- if (workspacePath && workspacePath !== paths[0]) {
301
- paths.push(workspacePath);
302
- }
303
- return paths;
304
- }
305
- /** Workspace and home analytics roots (store-installed global views live under home). */
306
- getAllowedAnalyticsRoots() {
307
- const roots = [
308
- path.resolve(this.artifactManager.getGlobalAnalyticsRoot()),
309
- path.resolve(this.artifactManager.getAnalyticsRoot()),
310
- ];
311
- return [...new Set(roots)];
312
- }
313
- isPathWithinAllowedAnalyticsRoots(filePath) {
314
- return (0, pathSafety_1.isPathUnderAnyRoot)(this.getAllowedAnalyticsRoots(), filePath);
315
- }
316
- isTeamSurface() {
317
- return this.viewIdSuffix === 'team';
318
- }
319
- getSurfaceMode() {
320
- return this.isTeamSurface() ? 'team' : 'spotlight';
321
- }
322
- async hydrateView(view, policy) {
323
- const panelPolicy = policy ?? view;
324
- const metadata = await this.readViewMetadata(view.id, view.scope);
325
- let resolved;
326
- if (metadata) {
327
- const id = this.normalizeViewId(metadata.id, view.id);
328
- let scripts = this.dedupeBasenames(metadata.scripts.length ? metadata.scripts : view.scripts);
329
- scripts = id === 'fleet'
330
- ? [AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME]
331
- : this.stripFleetWidgetFromGlobalWorkspace(id, scripts);
332
- resolved = {
333
- id,
334
- name: metadata.name,
335
- scope: metadata.scope,
336
- scripts,
337
- system: view.system === true || metadata.system === true,
338
- };
339
- }
340
- else {
341
- const base = view.id === 'fleet'
342
- ? { ...view, scripts: [AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME] }
343
- : view;
344
- resolved = {
345
- ...base,
346
- scripts: this.stripFleetWidgetFromGlobalWorkspace(base.id, base.scripts),
347
- };
348
- }
349
- return {
350
- ...resolved,
351
- refreshOnNavigate: panelPolicy.refreshOnNavigate,
352
- layout: panelPolicy.layout,
353
- preserveFleetIframe: panelPolicy.preserveFleetIframe,
354
- };
355
- }
356
- /** Agent Team: built-in Fleet plus views tagged `team` or `fleet` in view.yaml. */
357
- async buildTeamViewsConfig(preferredActiveId, spotlightPanel) {
358
- const policyById = new Map(spotlightPanel.views.map(view => [view.id, view]));
359
- const discovered = await (0, teamViewDiscovery_1.discoverTeamTaggedViews)(this.getAllowedAnalyticsRoots());
360
- const views = [
361
- {
362
- id: 'fleet',
363
- name: 'Fleet',
364
- scope: 'global',
365
- scripts: [AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME],
366
- system: true,
367
- },
368
- // Workflows is a built-in team view (like Fleet): adding it here keeps it
369
- // stable across reloads, unlike a postViewsState injection which flaps on
370
- // this surface because buildTeamViewsConfig rebuilds the list each time.
371
- (0, viewsConfigMerge_1.createDefaultWorkflowsView)(),
372
- ];
373
- for (const record of discovered) {
374
- const policy = policyById.get(record.id);
375
- views.push(await this.hydrateView({
376
- id: record.id,
377
- name: record.name,
378
- scope: record.scope,
379
- scripts: policy?.scripts ?? [],
380
- system: false,
381
- }, policy));
382
- }
383
- return this.finalizeTeamViewsConfig(views, preferredActiveId);
384
- }
385
- async loadTeamViewsConfig() {
386
- const [[globalLoaded, workspaceLoaded], [spotlightGlobal, spotlightWorkspace]] = await Promise.all([
387
- this.readPanelConfigLayers(),
388
- this.readPanelConfigLayers('views.yaml'),
389
- ]);
390
- const spotlightPanel = this.mergeViewsConfigs(spotlightGlobal, spotlightWorkspace);
391
- const preferredActiveId = this.normalizeViewId(workspaceLoaded?.activeViewId ?? globalLoaded?.activeViewId, 'fleet');
392
- return this.buildTeamViewsConfig(preferredActiveId, spotlightPanel);
393
- }
394
- /** Agent Team picker: fleet plus discovered views only (no Spotlight global/workspace stubs). */
395
- finalizeTeamViewsConfig(views, preferredActiveId) {
396
- const normalizedViews = views.map(view => ({
397
- ...view,
398
- id: this.normalizeViewId(view.id, 'fleet'),
399
- scripts: view.id === 'fleet'
400
- ? [AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME]
401
- : this.stripFleetWidgetFromGlobalWorkspace(view.id, this.dedupeBasenames(view.scripts)),
402
- }));
403
- if (!normalizedViews.some(view => view.id === 'fleet')) {
404
- normalizedViews.unshift({
405
- id: 'fleet',
406
- name: 'Fleet',
407
- scope: 'global',
408
- scripts: [AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME],
409
- system: true,
410
- });
411
- }
412
- return {
413
- version: 1,
414
- activeViewId: this.resolvePersistedActiveId(normalizedViews, preferredActiveId, 'fleet'),
415
- views: normalizedViews,
416
- };
417
- }
418
- getViewMetadataPath(viewId, scope) {
419
- const context = {
420
- scope,
421
- projectId: this.resolveWorkspaceProjectId(),
422
- agentId: this.agentFilterId,
423
- viewId,
424
- };
425
- return path.join(this.artifactManager.resolveViewRoot(context), 'view.yaml');
426
- }
427
- async readViewMetadata(viewId, scope) {
428
- const metadataPath = this.getViewMetadataPath(viewId, scope);
429
- try {
430
- const raw = await fs_1.promises.readFile(metadataPath, 'utf-8');
431
- const yaml = require('js-yaml');
432
- const parsed = yaml.load(raw);
433
- if (!parsed || typeof parsed !== 'object') {
434
- return undefined;
435
- }
436
- const now = new Date().toISOString();
437
- const versionField = parsed.version;
438
- const version = typeof versionField === 'string' && versionField.trim()
439
- ? versionField.trim()
440
- : typeof versionField === 'number'
441
- ? versionField
442
- : 1;
443
- return {
444
- version,
445
- id: this.normalizeViewId(typeof parsed.id === 'string' ? parsed.id : viewId, viewId),
446
- name: typeof parsed.name === 'string' && parsed.name.trim() ? parsed.name.trim() : viewId,
447
- scope: parsed.scope === 'workspace' ? 'workspace' : 'global',
448
- projectId: typeof parsed.projectId === 'string' ? parsed.projectId : '',
449
- agentId: typeof parsed.agentId === 'string' ? parsed.agentId : '',
450
- createdAt: typeof parsed.createdAt === 'string' && parsed.createdAt.trim() ? parsed.createdAt : now,
451
- updatedAt: typeof parsed.updatedAt === 'string' && parsed.updatedAt.trim() ? parsed.updatedAt : now,
452
- scripts: this.dedupeBasenames(this.parseStringArray(parsed.scripts)),
453
- system: parsed.system === true,
454
- description: typeof parsed.description === 'string' ? parsed.description.trim() : undefined,
455
- author: typeof parsed.author === 'string' ? parsed.author.trim() : undefined,
456
- tags: Array.isArray(parsed.tags)
457
- ? parsed.tags.filter((tag) => typeof tag === 'string')
458
- : undefined,
459
- };
460
- }
461
- catch {
462
- return undefined;
463
- }
464
- }
465
- async writeViewMetadata(view, createdAt) {
466
- const metadataPath = this.getViewMetadataPath(view.id, view.scope);
467
- await fs_1.promises.mkdir(path.dirname(metadataPath), { recursive: true });
468
- const now = new Date().toISOString();
469
- const existing = await this.readViewMetadata(view.id, view.scope);
470
- // Agent Team / Fleet view: persist only agent-fleet-widget.js by default.
471
- const scripts = view.id === 'fleet' ? ['agent-fleet-widget.js'] : this.dedupeBasenames(view.scripts);
472
- const payload = {
473
- version: existing?.version ?? '1.0.0',
474
- id: view.id,
475
- name: view.name,
476
- scope: view.scope,
477
- projectId: this.resolveWorkspaceProjectId() ?? '',
478
- agentId: this.agentFilterId ?? '',
479
- createdAt: createdAt ?? existing?.createdAt ?? now,
480
- updatedAt: now,
481
- scripts,
482
- system: view.system === true,
483
- ...(existing?.description ? { description: existing.description } : {}),
484
- ...(existing?.author ? { author: existing.author } : {}),
485
- ...(existing?.tags?.length ? { tags: existing.tags } : {}),
486
- };
487
- const yaml = require('js-yaml');
488
- const content = yaml.dump(payload, { lineWidth: 120, noRefs: true });
489
- await fs_1.promises.writeFile(metadataPath, content, 'utf-8');
490
- }
491
- getViewContext(view) {
492
- return {
493
- scope: view.scope,
494
- projectId: this.resolveWorkspaceProjectId(),
495
- agentId: this.agentFilterId,
496
- viewId: view.id,
497
- };
498
- }
499
- getViewScriptsDir(view) {
500
- return path.join(this.artifactManager.resolveViewRoot(this.getViewContext(view)), 'scripts');
501
- }
502
- async ensureViewOwnedScripts(view) {
503
- // Seed the run scripts plus any declared sidecars (e.g. the Workflows collector/daemon
504
- // the widget spawns) — driven by view config, not the view id.
505
- const expectedScripts = this.dedupeBasenames([...view.scripts, ...(view.bundledScripts ?? [])]);
506
- if (expectedScripts.length === 0) {
507
- return;
508
- }
509
- const scriptsDir = this.getViewScriptsDir(view);
510
- await fs_1.promises.mkdir(scriptsDir, { recursive: true });
511
- for (const scriptName of expectedScripts) {
512
- const destination = path.join(scriptsDir, scriptName);
513
- try {
514
- await fs_1.promises.access(destination);
515
- continue;
516
- }
517
- catch {
518
- // Missing destination; seed below when a bundled script exists.
519
- }
520
- const source = await this.findBundledAnalyticsScript(scriptName);
521
- if (!source) {
522
- this.outputChannel.appendLine(`[Dashboard] Could not seed script "${scriptName}" for view "${view.id}" (bundled script not found)`);
523
- continue;
524
- }
525
- try {
526
- await fs_1.promises.copyFile(source, destination);
527
- this.outputChannel.appendLine(`[Dashboard] Seeded missing view script "${scriptName}" for view "${view.id}"`);
528
- }
529
- catch (err) {
530
- this.outputChannel.appendLine(`[Dashboard] Failed seeding script "${scriptName}" for view "${view.id}": ${err}`);
531
- }
532
- }
533
- }
534
- async ensureViewOwnedDirectories(view) {
535
- const viewRoot = this.artifactManager.resolveViewRoot(this.getViewContext(view));
536
- for (const dir of [
537
- path.join(viewRoot, 'scripts'),
538
- path.join(viewRoot, 'charts'),
539
- path.join(viewRoot, 'reports'),
540
- path.join(viewRoot, 'exports'),
541
- path.join(viewRoot, 'images'),
542
- path.join(viewRoot, 'assets'),
543
- ]) {
544
- await fs_1.promises.mkdir(dir, { recursive: true });
545
- }
546
- }
547
- async ensureViewsFilesystemState() {
548
- for (const view of this.viewsConfig.views) {
549
- await this.ensureViewOwnedDirectories(view);
550
- await this.ensureViewOwnedScripts(view);
551
- await this.writeViewMetadata(view);
552
- }
553
- }
554
- async copyViewFiles(sourceView, targetView, folders) {
555
- const sourceRoot = this.artifactManager.resolveViewRoot({
556
- scope: sourceView.scope,
557
- projectId: this.resolveWorkspaceProjectId(),
558
- agentId: this.agentFilterId,
559
- viewId: sourceView.id,
560
- });
561
- const destinationRoot = this.artifactManager.resolveViewRoot({
562
- scope: targetView.scope,
563
- projectId: this.resolveWorkspaceProjectId(),
564
- agentId: this.agentFilterId,
565
- viewId: targetView.id,
566
- });
567
- for (const folder of folders) {
568
- const sourceDir = path.join(sourceRoot, folder);
569
- const destinationDir = path.join(destinationRoot, folder);
570
- await fs_1.promises.mkdir(destinationDir, { recursive: true });
571
- let entries = [];
572
- try {
573
- entries = await fs_1.promises.readdir(sourceDir);
574
- }
575
- catch {
576
- continue;
577
- }
578
- for (const entry of entries) {
579
- const sourcePath = path.join(sourceDir, entry);
580
- const destinationPath = path.join(destinationDir, entry);
581
- try {
582
- const stat = await fs_1.promises.stat(sourcePath);
583
- if (!stat.isFile()) {
584
- continue;
585
- }
586
- await fs_1.promises.copyFile(sourcePath, destinationPath);
587
- }
588
- catch {
589
- // Best effort copy.
590
- }
591
- }
592
- }
593
- }
594
- createDefaultViewsConfig() {
595
- const defaultActiveViewId = this.viewIdSuffix === 'team' ? 'fleet' : 'global';
596
- return {
597
- version: 1,
598
- activeViewId: defaultActiveViewId,
599
- views: [
600
- { id: 'global', name: 'Global', scope: 'global', scripts: [], system: true },
601
- { id: 'workspace', name: 'Workspace', scope: 'workspace', scripts: [], system: true },
602
- { id: 'fleet', name: 'Fleet', scope: 'global', scripts: ['agent-fleet-widget.js'], system: true },
603
- (0, viewsConfigMerge_1.createDefaultWorkflowsView)(),
604
- ],
605
- };
606
- }
607
- normalizeViewsConfig(raw, options) {
608
- const parsed = raw && typeof raw === 'object' ? raw : {};
609
- const rawViews = Array.isArray(parsed.views) ? parsed.views : [];
610
- const views = [];
611
- const seen = new Set();
612
- for (const item of rawViews) {
613
- if (!item || typeof item !== 'object') {
614
- continue;
615
- }
616
- const candidate = item;
617
- const id = this.normalizeViewId(typeof candidate.id === 'string' ? candidate.id : undefined, 'view');
618
- if (!id || seen.has(id)) {
619
- continue;
620
- }
621
- seen.add(id);
622
- const scope = candidate.scope === 'workspace' ? 'workspace' : 'global';
623
- const name = typeof candidate.name === 'string' && candidate.name.trim() ? candidate.name.trim() : id;
624
- const scripts = this.stripFleetWidgetFromGlobalWorkspace(id, this.dedupeBasenames(this.parseStringArray(candidate.scripts)));
625
- const bundledScripts = this.dedupeBasenames(this.parseStringArray(candidate.bundledScripts));
626
- const system = candidate.system === true;
627
- const refreshOnNavigate = candidate.refreshOnNavigate === 'always' || candidate.refreshOnNavigate === 'when-empty'
628
- ? candidate.refreshOnNavigate
629
- : undefined;
630
- const layout = candidate.layout === 'fleet-dashboard' || candidate.layout === 'gallery'
631
- ? candidate.layout
632
- : undefined;
633
- const preserveFleetIframe = candidate.preserveFleetIframe === true
634
- ? true
635
- : candidate.preserveFleetIframe === false
636
- ? false
637
- : undefined;
638
- views.push({
639
- id, name, scope, scripts,
640
- bundledScripts: bundledScripts.length ? bundledScripts : undefined,
641
- system, refreshOnNavigate, layout, preserveFleetIframe,
642
- });
643
- }
644
- if (!views.some(view => view.id === 'global')) {
645
- views.unshift({ id: 'global', name: 'Global', scope: 'global', scripts: [], system: true });
646
- }
647
- if (!views.some(view => view.id === 'workspace')) {
648
- views.push({ id: 'workspace', name: 'Workspace', scope: 'workspace', scripts: [], system: true });
649
- }
650
- if (!views.some(view => view.id === 'fleet')) {
651
- views.push({ id: 'fleet', name: 'Fleet', scope: 'global', scripts: ['agent-fleet-widget.js'], system: true });
652
- }
653
- if (!views.some(view => view.id === viewsConfigMerge_1.WORKFLOWS_VIEW_ID)) {
654
- views.push((0, viewsConfigMerge_1.createDefaultWorkflowsView)());
655
- }
656
- const defaultConfig = this.createDefaultViewsConfig();
657
- const rawActive = typeof parsed.activeViewId === 'string' ? parsed.activeViewId : defaultConfig.activeViewId;
658
- const activeViewId = this.normalizeViewId(rawActive, defaultConfig.activeViewId);
659
- const hasActive = views.some(view => view.id === activeViewId);
660
- return {
661
- version: 1,
662
- activeViewId: hasActive || options?.allowOrphanActiveId ? activeViewId : defaultConfig.activeViewId,
663
- views,
664
- };
665
- }
666
- async readViewsConfigFile(configPath) {
667
- try {
668
- const raw = await fs_1.promises.readFile(configPath, 'utf-8');
669
- const yaml = require('js-yaml');
670
- return this.normalizeViewsConfig(yaml.load(raw), { allowOrphanActiveId: true });
671
- }
672
- catch {
673
- return undefined;
674
- }
675
- }
676
- mergeViewsConfigs(globalConfig, workspaceConfig) {
677
- const defaults = this.createDefaultViewsConfig();
678
- const merged = (0, viewsConfigMerge_1.mergeDashboardViewsConfigs)(globalConfig, workspaceConfig, defaults);
679
- return this.normalizeViewsConfig({
680
- version: 1,
681
- activeViewId: merged.activeViewId,
682
- views: merged.views,
683
- });
684
- }
685
- async handleViewsConfigChanged() {
686
- const previousActiveViewId = this.getActiveViewId();
687
- await this.loadViewsConfig();
688
- if (!this.webviewView?.visible) {
689
- return;
690
- }
691
- await this.postViewsState();
692
- await this.postPanelState('default');
693
- if (this.getActiveViewId() !== previousActiveViewId) {
694
- await this.refreshAfterViewChange();
695
- await this.refreshChartsForActiveViewAfterSwitch();
696
- }
697
- }
698
- async loadViewsConfig() {
699
- if (this.isTeamSurface()) {
700
- const teamConfigPath = this.getGlobalViewsConfigPath();
701
- let needsPersist = false;
702
- try {
703
- await fs_1.promises.access(teamConfigPath);
704
- }
705
- catch {
706
- needsPersist = true;
707
- }
708
- this.viewsConfig = await this.loadTeamViewsConfig();
709
- if (needsPersist) {
710
- await this.saveViewsConfig(this.viewsConfig);
711
- }
712
- return this.viewsConfig;
713
- }
714
- const workspacePath = this.getWorkspaceViewsConfigPath();
715
- const [globalLoaded, workspaceLoaded] = await this.readPanelConfigLayers();
716
- let usedFallback = !globalLoaded && !workspaceLoaded;
717
- let loaded = this.mergeViewsConfigs(globalLoaded, workspaceLoaded);
718
- if (usedFallback) {
719
- const fallback = this.createDefaultViewsConfig();
720
- const allScripts = await this.artifactManager.listScripts({ disabledScripts: [] });
721
- const allScriptNames = this.dedupeBasenames(allScripts.map(script => script.filename));
722
- const fleetOnly = AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME;
723
- loaded = {
724
- ...fallback,
725
- views: fallback.views.map(view => ({
726
- ...view,
727
- scripts: view.scripts.length
728
- ? view.scripts
729
- : allScriptNames.filter((name) => view.id === 'fleet'
730
- ? this.normalizeBasename(name) === fleetOnly
731
- : this.normalizeBasename(name) !== fleetOnly),
732
- })),
733
- };
734
- }
735
- const hydratedViews = [];
736
- for (const view of loaded.views) {
737
- hydratedViews.push(await this.hydrateView(view));
738
- }
739
- const normalized = this.normalizeViewsConfig({
740
- version: 1,
741
- activeViewId: loaded.activeViewId,
742
- views: hydratedViews,
743
- });
744
- this.viewsConfig = normalized;
745
- const shouldPersist = usedFallback
746
- || this.shouldHealWorkspaceActiveViewId(workspaceLoaded, workspacePath, loaded.activeViewId);
747
- if (shouldPersist) {
748
- await this.saveViewsConfig(this.viewsConfig);
749
- }
750
- return this.viewsConfig;
751
- }
752
- resolvePersistedActiveId(views, preferredId, fallbackId) {
753
- const normalized = this.normalizeViewId(preferredId, fallbackId);
754
- return views.some(view => view.id === normalized) ? normalized : fallbackId;
755
- }
756
- /** Rewrite workspace views.yaml when merge healed an orphan workspace activeViewId. */
757
- shouldHealWorkspaceActiveViewId(workspaceLoaded, workspacePath, resolvedActiveViewId) {
758
- if (!workspacePath || !workspaceLoaded) {
759
- return false;
760
- }
761
- const stored = this.normalizeViewId(workspaceLoaded.activeViewId, '');
762
- const resolved = this.normalizeViewId(resolvedActiveViewId, '');
763
- return stored.length > 0 && stored !== resolved;
764
- }
765
- async saveViewsConfig(config) {
766
- const normalized = this.normalizeViewsConfig(config);
767
- const yaml = require('js-yaml');
768
- if (this.isTeamSurface()) {
769
- const defaultActive = 'fleet';
770
- const activeViewId = this.resolvePersistedActiveId(normalized.views, normalized.activeViewId, defaultActive);
771
- const teamPersist = {
772
- version: 1,
773
- activeViewId,
774
- views: [{
775
- id: 'fleet',
776
- name: 'Fleet',
777
- scope: 'global',
778
- scripts: [AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME],
779
- system: true,
780
- }],
781
- };
782
- const globalPath = this.getGlobalViewsConfigPath();
783
- await fs_1.promises.mkdir(path.dirname(globalPath), { recursive: true });
784
- await fs_1.promises.writeFile(globalPath, yaml.dump(teamPersist, { lineWidth: 120, noRefs: true }), 'utf-8');
785
- const workspacePath = this.getWorkspaceViewsConfigPath();
786
- if (workspacePath) {
787
- await fs_1.promises.mkdir(path.dirname(workspacePath), { recursive: true });
788
- await fs_1.promises.writeFile(workspacePath, yaml.dump({ version: 1, activeViewId, views: [] }, { lineWidth: 120, noRefs: true }), 'utf-8');
789
- }
790
- this.viewsConfig = normalized;
791
- await this.ensureViewsFilesystemState();
792
- return;
793
- }
794
- const globalViews = normalized.views.filter(view => view.scope !== 'workspace');
795
- const workspaceViews = normalized.views.filter(view => view.scope === 'workspace');
796
- const defaultActive = 'global';
797
- const unifiedActive = this.normalizeViewId(normalized.activeViewId, defaultActive);
798
- const activeViewId = normalized.views.some(view => view.id === unifiedActive)
799
- ? unifiedActive
800
- : defaultActive;
801
- const globalPath = this.getGlobalViewsConfigPath();
802
- const workspacePath = this.getWorkspaceViewsConfigPath();
803
- const [existingGlobal] = await Promise.all([
804
- this.readViewsConfigFile(globalPath),
805
- workspacePath ? this.readViewsConfigFile(workspacePath) : Promise.resolve(undefined),
806
- ]);
807
- // Per-repo selection lives in workspace views.yaml when a folder is open; global activeViewId is home-only.
808
- const globalActiveId = workspacePath
809
- ? this.resolvePersistedActiveId(globalViews, existingGlobal?.activeViewId ?? defaultActive, defaultActive)
810
- : activeViewId;
811
- const globalConfig = {
812
- version: 1,
813
- activeViewId: globalActiveId,
814
- views: globalViews,
815
- };
816
- await fs_1.promises.mkdir(path.dirname(globalPath), { recursive: true });
817
- await fs_1.promises.writeFile(globalPath, yaml.dump(globalConfig, { lineWidth: 120, noRefs: true }), 'utf-8');
818
- if (workspacePath) {
819
- const workspaceConfig = {
820
- version: 1,
821
- activeViewId,
822
- views: workspaceViews,
823
- };
824
- await fs_1.promises.mkdir(path.dirname(workspacePath), { recursive: true });
825
- await fs_1.promises.writeFile(workspacePath, yaml.dump(workspaceConfig, { lineWidth: 120, noRefs: true }), 'utf-8');
826
- }
827
- this.viewsConfig = normalized;
828
- await this.ensureViewsFilesystemState();
829
- }
830
- getActiveViewId() {
831
- const fallback = this.isTeamSurface() ? 'fleet' : 'global';
832
- const activeId = this.normalizeViewId(this.viewsConfig.activeViewId, fallback);
833
- if (this.viewsConfig.views.some(view => view.id === activeId)) {
834
- return activeId;
835
- }
836
- return fallback;
837
- }
838
- getActiveView() {
839
- const activeId = this.getActiveViewId();
840
- return this.viewsConfig.views.find(view => view.id === activeId) ?? this.createDefaultViewsConfig().views[0];
841
- }
842
- findViewById(viewId) {
843
- const normalized = this.normalizeViewId(viewId);
844
- return this.viewsConfig.views.find(view => view.id === normalized);
845
- }
846
- async postViewsState() {
847
- if (!this.viewsConfig.views.some(v => v.id === 'fleet')) {
848
- const fleetView = { id: 'fleet', name: 'Fleet', scope: 'global', scripts: ['agent-fleet-widget.js'], system: true };
849
- this.viewsConfig = {
850
- ...this.viewsConfig,
851
- views: [...this.viewsConfig.views, fleetView],
852
- };
853
- await this.ensureViewOwnedDirectories(fleetView);
854
- await this.writeViewMetadata(fleetView);
855
- await this.saveViewsConfig(this.viewsConfig);
856
- }
857
- // Spotlight-only safety net. The team surface already owns Workflows as a built-in
858
- // in buildTeamViewsConfig; injecting it here too would flap the picker (it rebuilds
859
- // the list each reload) and pollute the shared team config on disk.
860
- if (!this.isTeamSurface() && !this.viewsConfig.views.some(v => v.id === viewsConfigMerge_1.WORKFLOWS_VIEW_ID)) {
861
- const workflowsView = (0, viewsConfigMerge_1.createDefaultWorkflowsView)();
862
- this.viewsConfig = {
863
- ...this.viewsConfig,
864
- views: [...this.viewsConfig.views, workflowsView],
865
- };
866
- await this.ensureViewOwnedDirectories(workflowsView);
867
- await this.ensureViewOwnedScripts(workflowsView);
868
- await this.writeViewMetadata(workflowsView);
869
- await this.saveViewsConfig(this.viewsConfig);
870
- }
871
- const activeViewId = this.getActiveViewId();
872
- const views = this.viewsConfig.views.map(view => ({
873
- id: view.id,
874
- name: view.name,
875
- scope: view.scope,
876
- system: view.system === true,
877
- layout: (0, viewsConfigMerge_1.resolveGalleryLayout)(view),
878
- refreshOnNavigate: (0, viewsConfigMerge_1.resolveRefreshOnNavigate)(view),
879
- preserveFleetIframe: (0, viewsConfigMerge_1.resolvePreserveFleetIframe)(view),
880
- }));
881
- postToWebview(this.webviewView?.webview, {
882
- type: 'viewsLoaded',
883
- activeViewId,
884
- views,
885
- surfaceMode: this.getSurfaceMode(),
886
- });
887
- }
888
- async loadAndPostViewsState() {
889
- this.viewsConfig = await this.loadViewsConfig();
890
- await this.postViewsState();
891
- }
892
- /** Reload views.yaml from disk (global + workspace) and refresh the webview dropdown. */
893
- async refreshViewsFromDisk() {
894
- await this.loadAndPostViewsState();
895
- }
896
- async setActiveView(viewId) {
897
- const normalized = this.normalizeViewId(viewId);
898
- if (!this.viewsConfig.views.some(view => view.id === normalized)) {
899
- return false;
900
- }
901
- if (this.viewsConfig.activeViewId === normalized) {
902
- return true;
903
- }
904
- this.viewsConfig = {
905
- ...this.viewsConfig,
906
- activeViewId: normalized,
907
- };
908
- await this.saveViewsConfig(this.viewsConfig);
909
- return true;
910
- }
911
- /**
912
- * Switch the dashboard to the given view and persist. Caller should focus the panel after.
913
- * Used by "Open Agent Fleet" to open the dashboard with the Fleet view active.
914
- */
915
- async openWithView(viewId) {
916
- await this.loadViewsConfig();
917
- const switched = await this.setActiveView(viewId);
918
- if (!this.webviewView?.visible) {
919
- return;
920
- }
921
- await this.postViewsState();
922
- if (switched) {
923
- this.syncArtifactContext();
924
- await this.refreshAfterViewChange();
925
- await this.refreshChartsForActiveViewAfterSwitch();
926
- }
927
- }
928
- allocateViewId(name, fallback = 'view') {
929
- const baseId = this.normalizeViewId(name, fallback);
930
- let candidateId = baseId;
931
- let suffix = 2;
932
- while (this.viewsConfig.views.some(view => view.id === candidateId)) {
933
- candidateId = `${baseId}-${suffix++}`;
934
- }
935
- return candidateId;
936
- }
937
- async createView(name) {
938
- const trimmed = name.trim();
939
- if (!trimmed) {
940
- return undefined;
941
- }
942
- const candidateId = this.allocateViewId(trimmed, 'view');
943
- const sourceView = this.getActiveView();
944
- const newView = {
945
- id: candidateId,
946
- name: trimmed,
947
- scope: sourceView.scope,
948
- scripts: [],
949
- system: false,
950
- };
951
- await this.ensureViewOwnedDirectories(newView);
952
- await this.copyViewFiles(sourceView, newView, ['assets', 'images']);
953
- this.viewsConfig = {
954
- ...this.viewsConfig,
955
- activeViewId: newView.id,
956
- views: [...this.viewsConfig.views, newView],
957
- };
958
- await this.saveViewsConfig(this.viewsConfig);
959
- return newView;
960
- }
961
- async renameView(viewId, name) {
962
- const target = this.findViewById(viewId);
963
- if (!target || target.system) {
964
- return false;
965
- }
966
- const trimmed = name.trim();
967
- if (!trimmed) {
968
- return false;
969
- }
970
- this.viewsConfig = {
971
- ...this.viewsConfig,
972
- views: this.viewsConfig.views.map(view => view.id === target.id ? { ...view, name: trimmed } : view),
973
- };
974
- await this.saveViewsConfig(this.viewsConfig);
975
- return true;
976
- }
977
- async duplicateView(viewId) {
978
- const source = this.findViewById(viewId);
979
- if (!source) {
980
- return undefined;
981
- }
982
- const copyBaseName = `${source.name} Copy`;
983
- const candidateId = this.allocateViewId(copyBaseName, 'view');
984
- const copyDisplayName = (0, viewsConfigMerge_1.formatDisplayNameForAllocatedViewId)(copyBaseName, candidateId, (raw, fallback) => this.normalizeViewId(raw, fallback));
985
- const scripts = await this.resolveScriptBasenamesForDuplicate(source);
986
- const duplicate = {
987
- id: candidateId,
988
- name: copyDisplayName,
989
- scope: source.scope,
990
- scripts,
991
- system: false,
992
- };
993
- await this.ensureViewOwnedDirectories(duplicate);
994
- await this.copyViewFiles(source, duplicate, ['scripts', 'assets', 'images', 'charts', 'reports', 'exports']);
995
- this.viewsConfig = {
996
- ...this.viewsConfig,
997
- activeViewId: duplicate.id,
998
- views: [...this.viewsConfig.views, duplicate],
999
- };
1000
- await this.saveViewsConfig(this.viewsConfig);
1001
- return duplicate;
1002
- }
1003
- async deleteView(viewId) {
1004
- const target = this.findViewById(viewId);
1005
- if (!target || target.system) {
1006
- return false;
1007
- }
1008
- const nextActiveViewId = this.getActiveViewId() === target.id ? 'global' : this.getActiveViewId();
1009
- this.viewsConfig = {
1010
- ...this.viewsConfig,
1011
- activeViewId: nextActiveViewId,
1012
- views: this.viewsConfig.views.filter(view => view.id !== target.id),
1013
- };
1014
- const viewRoot = this.artifactManager.resolveViewRoot({
1015
- scope: target.scope,
1016
- projectId: this.resolveWorkspaceProjectId(),
1017
- agentId: this.agentFilterId,
1018
- viewId: target.id,
1019
- });
1020
- await fs_1.promises.rm(viewRoot, { recursive: true, force: true }).catch(() => undefined);
1021
- const panelStatePath = path.join(this.artifactManager.getAnalyticsRoot(), 'panel-state', `default--${target.id}.json`);
1022
- await fs_1.promises.rm(panelStatePath, { force: true }).catch(() => undefined);
1023
- await this.saveViewsConfig(this.viewsConfig);
1024
- return true;
1025
- }
1026
- async editViewScripts(viewId) {
1027
- const view = viewId ? this.findViewById(viewId) : this.getActiveView();
1028
- if (!view) {
1029
- return false;
1030
- }
1031
- const scriptRoot = path.join(this.artifactManager.resolveViewRoot({
1032
- scope: view.scope,
1033
- projectId: this.resolveWorkspaceProjectId(),
1034
- agentId: this.agentFilterId,
1035
- viewId: view.id,
1036
- }), 'scripts');
1037
- await fs_1.promises.mkdir(scriptRoot, { recursive: true });
1038
- await vscode.commands.executeCommand('revealInExplorer', vscode.Uri.file(scriptRoot));
1039
- return true;
1040
- }
1041
- async refreshAllViews() {
1042
- // Spotlight "refresh" should never change the user's currently-selected view.
1043
- // Refresh only the active view to avoid temporarily switching through other
1044
- // system views (notably `fleet`) during the refresh cycle.
1045
- const activeViewId = this.getActiveViewId();
1046
- await this.refreshCharts({ viewId: activeViewId });
1047
- }
1048
- getRefreshContext(options) {
1049
- const requestedViewId = options?.viewId ? this.normalizeViewId(options.viewId) : this.getActiveViewId();
1050
- const view = this.viewsConfig.views.find(item => item.id === requestedViewId) ?? this.getActiveView();
1051
- return {
1052
- viewId: view.id,
1053
- viewName: view.name,
1054
- };
1055
- }
1056
- postRefreshStatus(type, options) {
1057
- const refreshContext = this.getRefreshContext(options);
1058
- postToWebview(this.webviewView?.webview, {
1059
- type,
1060
- viewId: refreshContext.viewId,
1061
- viewName: refreshContext.viewName,
1062
- });
1063
- }
1064
- async getScriptsForView(options) {
1065
- const viewId = options?.viewId ? this.normalizeViewId(options.viewId) : this.getActiveViewId();
1066
- const view = this.viewsConfig.views.find(item => item.id === viewId) ?? this.getActiveView();
1067
- await this.ensureViewOwnedScripts(view);
1068
- return this.withArtifactViewContext(view, async () => {
1069
- const allScripts = await this.artifactManager.listScripts({ disabledScripts: [] });
1070
- return this.resolveScriptsForView(view, allScripts);
1071
- });
1072
- }
1073
- resolveScriptsForView(view, allScripts) {
1074
- if (!view.scripts.length) {
1075
- // System views (e.g. Global, Workspace) with no scripts → use all scripts except the Fleet-only widget.
1076
- // Fleet view with no scripts → only agent-fleet-widget.js. User-created "Add view" → empty canvas.
1077
- if (!view.system) {
1078
- return { view, scripts: [] };
1079
- }
1080
- if (view.id === 'fleet') {
1081
- return {
1082
- view,
1083
- scripts: allScripts.filter(s => this.normalizeBasename(s.filename) === AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME),
1084
- };
1085
- }
1086
- return {
1087
- view,
1088
- scripts: allScripts.filter(s => this.normalizeBasename(s.filename) !== AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME),
1089
- };
1090
- }
1091
- const selectedBasenames = new Set(view.scripts.map(name => this.normalizeBasename(name)));
1092
- return {
1093
- view,
1094
- scripts: allScripts.filter(script => selectedBasenames.has(this.normalizeBasename(script.filename))),
1095
- };
1096
- }
1097
- async resolveScriptBasenamesForDuplicate(source) {
1098
- if (source.scripts.length > 0) {
1099
- return this.dedupeBasenames(source.scripts);
1100
- }
1101
- const selection = await this.withArtifactViewContext(source, async () => {
1102
- const allScripts = await this.artifactManager.listScripts({ disabledScripts: [] });
1103
- return this.resolveScriptsForView(source, allScripts);
1104
- });
1105
- return this.dedupeBasenames(selection.scripts.map(script => script.filename));
1106
- }
1107
- getScopedPanelId(panelId) {
1108
- const normalizedPanelId = this.normalizePanelStateId(panelId);
1109
- const viewId = this.getActiveViewId();
1110
- return `${normalizedPanelId}--${viewId}`;
1111
- }
1112
- async refreshDashboardData() {
1113
- this.queryRunner.invalidateCache();
1114
- void this.fetchDashboardBootstrapData().then(bootstrap => {
1115
- void this.postFilterState(bootstrap?.agentIds);
1116
- this.triggerSummaryCardsUpdate(bootstrap?.summary);
1117
- });
1118
- const refreshedBootstrap = await this.fetchDashboardBootstrapData();
1119
- await this.postFilterState(refreshedBootstrap?.agentIds);
1120
- this.triggerSummaryCardsUpdate(refreshedBootstrap?.summary);
1121
- await this.updateDataFreshnessHint(refreshedBootstrap
1122
- ? { hasData: refreshedBootstrap.hasData, dataHash: refreshedBootstrap.dataHash }
1123
- : undefined);
1124
- }
1125
- /**
1126
- * Reload gallery and summary after the active view or filters change.
1127
- * Policy: artifact-first — always load from disk here; script execution is gated separately
1128
- * (see PLAN/spotlight-refresh-policy.md and maybeRefreshChartsAfterViewSwitch).
1129
- */
1130
- async refreshAfterViewChange() {
1131
- this.queryRunner.invalidateCache();
1132
- const bootstrapPromise = this.fetchDashboardBootstrapData();
1133
- await this.loadAndSendArtifacts();
1134
- this.scheduleAutoGenerateForCurrentFilter();
1135
- const bootstrap = await bootstrapPromise;
1136
- await this.postFilterState(bootstrap?.agentIds);
1137
- this.triggerSummaryCardsUpdate(bootstrap?.summary);
1138
- await this.updateDataFreshnessHint(bootstrap ? { hasData: bootstrap.hasData, dataHash: bootstrap.dataHash } : undefined);
1139
- }
1140
- /**
1141
- * Whether navigation should run widget scripts (view switch after load).
1142
- * Fleet always runs; otherwise run only when the view has no chart artifacts.
1143
- */
1144
- async shouldRunScriptsOnNavigation() {
1145
- this.syncArtifactContext();
1146
- const artifacts = await this.artifactManager.listArtifacts();
1147
- return (0, viewsConfigMerge_1.shouldRunScriptsOnNavigation)(this.getActiveView(), artifacts.map(artifact => artifact.type));
1148
- }
1149
- /**
1150
- * Run widget scripts after view switch when policy allows (compute when empty; fleet always).
1151
- */
1152
- async refreshChartsForActiveViewAfterSwitch() {
1153
- try {
1154
- if (!(await this.shouldRunScriptsOnNavigation())) {
1155
- this.outputChannel.appendLine('[Dashboard] View switch: load only (chart artifacts present; use Refresh to re-run scripts)');
1156
- return;
1157
- }
1158
- await this.refreshCharts({ viewId: this.getActiveViewId() });
1159
- }
1160
- catch (err) {
1161
- this.outputChannel.appendLine(`[Dashboard] View-switch chart refresh failed: ${err}`);
1162
- }
1163
- }
1164
- getActiveFilters() {
1165
- const activeView = this.getActiveView();
1166
- return {
1167
- scope: activeView.scope,
1168
- projectId: this.resolveWorkspaceProjectId(),
1169
- agentId: this.agentFilterId,
1170
- };
1171
- }
1172
- async fetchDashboardBootstrapData() {
1173
- try {
1174
- return await this.queryRunner.getDashboardBootstrap(this.getActiveFilters());
1175
- }
1176
- catch (err) {
1177
- const message = err instanceof Error ? err.message : String(err);
1178
- this.outputChannel.appendLine(`[Dashboard] Could not load dashboard bootstrap data: ${message}`);
1179
- if (message.toLowerCase().includes('indexed analytics db')) {
1180
- this.webviewView?.webview.postMessage({
1181
- type: 'notifyIndexing',
1182
- message: 'Spotlight index unavailable. Re-index traces (Setup Spotlight) and refresh.',
1183
- });
1184
- }
1185
- return undefined;
1186
- }
1187
- }
1188
- triggerSummaryCardsUpdate(summaryOverride) {
1189
- void this.sendSummaryCards(summaryOverride);
1190
- }
1191
- async postFilterState(availableAgentIdsOverride) {
1192
- const filters = this.getActiveFilters();
1193
- let availableAgentIds = [];
1194
- if (availableAgentIdsOverride) {
1195
- availableAgentIds = [...availableAgentIdsOverride];
1196
- }
1197
- else {
1198
- try {
1199
- availableAgentIds = await this.queryRunner.getAgentIds({
1200
- scope: filters.scope,
1201
- projectId: filters.projectId,
1202
- });
1203
- }
1204
- catch (err) {
1205
- this.outputChannel.appendLine(`[Dashboard] Could not load agent filters: ${err}`);
1206
- }
1207
- }
1208
- if (this.agentFilterId && !availableAgentIds.includes(this.agentFilterId)) {
1209
- this.agentFilterId = undefined;
1210
- }
1211
- postToWebview(this.webviewView?.webview, {
1212
- type: 'filtersUpdated',
1213
- scope: filters.scope,
1214
- projectId: filters.projectId ?? '',
1215
- agentId: this.agentFilterId ?? '',
1216
- agentIds: availableAgentIds,
1217
- activeViewId: this.getActiveViewId(),
1218
- });
1219
- }
1220
- resolveIndexScriptPathForScripts() {
1221
- const candidate = path.resolve(__dirname, '../../scripts/analytics/index-traces.py');
1222
- return (0, fs_1.existsSync)(candidate) ? candidate : '';
1223
- }
1224
- buildScriptEnvironment(outputDir, disabledCharts = []) {
1225
- this.syncArtifactContext();
1226
- const filters = this.getActiveFilters();
1227
- const projectIdForScope = filters.scope === 'workspace' ? (filters.projectId ?? '') : '';
1228
- const workspaceRoot = this.resolveWorkspaceRootPath() ?? '';
1229
- const tracesDir = path.join((0, index_1.getAgentideHome)(), 'analytics', 'traces');
1230
- const indexingService = this.indexingService();
1231
- const indexedPath = indexingService?.getIndexPath().trim() ?? '';
1232
- const indexDbPath = indexedPath || path.join(tracesDir, 'analytics.duckdb');
1233
- const isIndexed = indexedPath.length > 0;
1234
- const indexScriptPath = this.resolveIndexScriptPathForScripts();
1235
- const analyticsConfig = vscode.workspace.getConfiguration('agentide.analytics');
1236
- const duckdbMemoryLimitMb = Math.max(64, analyticsConfig.get('duckdb.memoryLimitMb', 1024) ?? 1024);
1237
- const duckdbThreads = Math.max(1, analyticsConfig.get('duckdb.threads', 2) ?? 2);
1238
- const duckdbQueryTimeoutSeconds = Math.max(5, analyticsConfig.get('duckdb.queryTimeoutSeconds', 30) ?? 30);
1239
- const duckdbMaxResultRows = Math.max(100, analyticsConfig.get('duckdb.maxResultRows', 5000) ?? 5000);
1240
- const duckdbTempDirectory = (analyticsConfig.get('duckdb.tempDirectory', '') ?? '').trim();
1241
- return {
1242
- ...process.env,
1243
- // Augment PATH so uv/python/pip are found even when VS Code is
1244
- // launched from the Dock (which inherits a minimal PATH).
1245
- PATH: (0, toolPath_1.augmentPathForTools)(process.env.PATH ?? ''),
1246
- AGENTIDE_TRACES_DIR: tracesDir,
1247
- AGENTIDE_CHART_DIR: this.artifactManager.getChartDir(),
1248
- AGENTIDE_REPORT_DIR: this.artifactManager.getReportDir(),
1249
- AGENTIDE_EXPORT_DIR: this.artifactManager.getExportDir(),
1250
- AGENTIDE_IMAGE_DIR: this.artifactManager.getImageDir(),
1251
- AGENTIDE_ASSET_DIR: this.artifactManager.getAssetDir(),
1252
- AGENTIDE_VIEW_ROOT: this.artifactManager.getViewRoot(),
1253
- AGENTIDE_VIEW_ID: this.getActiveViewId(),
1254
- AGENTIDE_OUTPUT_DIR: outputDir,
1255
- AGENTIDE_ANALYTICS_SCOPE: filters.scope ?? 'global',
1256
- AGENTIDE_ANALYTICS_PROJECT_ID: projectIdForScope,
1257
- AGENTIDE_ANALYTICS_AGENT_ID: filters.agentId ?? '',
1258
- AGENTIDE_WORKSPACE_ROOT: workspaceRoot,
1259
- AGENTIDE_DISABLED_CHARTS: disabledCharts.join(','),
1260
- // Pass indexing status to Python script for fallback logic
1261
- AGENTIDE_INDEX_DB_PATH: indexDbPath,
1262
- AGENTIDE_USE_DUCKDB: isIndexed ? 'true' : 'false',
1263
- AGENTIDE_INDEX_SCRIPT_PATH: indexScriptPath,
1264
- AGENTIDE_DUCKDB_MEMORY_LIMIT_MB: String(duckdbMemoryLimitMb),
1265
- AGENTIDE_DUCKDB_THREADS: String(duckdbThreads),
1266
- AGENTIDE_DUCKDB_QUERY_TIMEOUT_SECONDS: String(duckdbQueryTimeoutSeconds),
1267
- AGENTIDE_DUCKDB_MAX_RESULT_ROWS: String(duckdbMaxResultRows),
1268
- AGENTIDE_DUCKDB_TEMP_DIRECTORY: duckdbTempDirectory,
1269
- };
1270
- }
1271
- parseStringArray(raw) {
1272
- if (!Array.isArray(raw)) {
1273
- return [];
1274
- }
1275
- return raw
1276
- .filter((entry) => typeof entry === 'string')
1277
- .map(entry => entry.trim())
1278
- .filter(entry => entry.length > 0);
1279
- }
1280
- normalizeBasename(value) {
1281
- return path.basename(value.trim()).toLowerCase();
1282
- }
1283
- dedupeBasenames(values) {
1284
- const seen = new Set();
1285
- const deduped = [];
1286
- for (const value of values) {
1287
- const trimmed = value.trim();
1288
- if (!trimmed) {
1289
- continue;
1290
- }
1291
- const basename = path.basename(trimmed);
1292
- const key = basename.toLowerCase();
1293
- if (!key || seen.has(key)) {
1294
- continue;
1295
- }
1296
- seen.add(key);
1297
- deduped.push(basename);
1298
- }
1299
- return deduped;
1300
- }
1301
- dedupeCaseInsensitive(values) {
1302
- const seen = new Set();
1303
- const deduped = [];
1304
- for (const value of values) {
1305
- const trimmed = value.trim();
1306
- if (!trimmed) {
1307
- continue;
1308
- }
1309
- const key = trimmed.toLowerCase();
1310
- if (seen.has(key)) {
1311
- continue;
1312
- }
1313
- seen.add(key);
1314
- deduped.push(trimmed);
1315
- }
1316
- return deduped;
1317
- }
1318
- parseDisabledScriptsByView(raw) {
1319
- if (Array.isArray(raw)) {
1320
- const allScripts = this.dedupeBasenames(this.parseStringArray(raw));
1321
- return allScripts.length > 0 ? { all: allScripts } : {};
1322
- }
1323
- if (!raw || typeof raw !== 'object') {
1324
- return {};
1325
- }
1326
- const scriptsByView = {};
1327
- for (const [rawViewId, rawScripts] of Object.entries(raw)) {
1328
- const normalizedViewId = this.normalizeViewId(rawViewId, 'all');
1329
- const scripts = this.dedupeBasenames(this.parseStringArray(rawScripts));
1330
- if (scripts.length > 0) {
1331
- scriptsByView[normalizedViewId] = scripts;
1332
- }
1333
- }
1334
- return scriptsByView;
1335
- }
1336
- mergeDisabledScriptsByView(primary, fallback) {
1337
- const merged = {};
1338
- for (const [viewId, scripts] of Object.entries(fallback)) {
1339
- merged[viewId] = this.dedupeBasenames(scripts);
1340
- }
1341
- for (const [viewId, scripts] of Object.entries(primary)) {
1342
- merged[viewId] = this.dedupeBasenames([...(merged[viewId] ?? []), ...scripts]);
1343
- }
1344
- return merged;
1345
- }
1346
- getDisabledScriptsForView(config, viewId) {
1347
- const normalizedViewId = this.normalizeViewId(viewId, 'global');
1348
- return this.dedupeBasenames([
1349
- ...(config.scriptsByView.all ?? []),
1350
- ...(config.scriptsByView[normalizedViewId] ?? []),
1351
- ]);
1352
- }
1353
- async loadDisabledChartsConfig() {
1354
- const configPath = this.getDisabledChartsConfigPath();
1355
- try {
1356
- const raw = await fs_1.promises.readFile(configPath, 'utf-8');
1357
- const yaml = require('js-yaml');
1358
- const parsed = yaml.load(raw);
1359
- const disabled = parsed?.disabled && typeof parsed.disabled === 'object'
1360
- ? parsed.disabled
1361
- : {};
1362
- const scriptsByView = this.mergeDisabledScriptsByView(this.parseDisabledScriptsByView(parsed?.scripts), this.parseDisabledScriptsByView(disabled.scripts));
1363
- const hasGalleryIgnoreSetting = Boolean((parsed && (Object.prototype.hasOwnProperty.call(parsed, 'galleryIgnore') || Object.prototype.hasOwnProperty.call(parsed, 'gallery_ignore')))
1364
- || (Object.prototype.hasOwnProperty.call(disabled, 'galleryIgnore') || Object.prototype.hasOwnProperty.call(disabled, 'gallery_ignore')));
1365
- const galleryIgnore = this.dedupeCaseInsensitive([
1366
- ...this.parseStringArray(parsed?.galleryIgnore),
1367
- ...this.parseStringArray(parsed?.gallery_ignore),
1368
- ...this.parseStringArray(disabled.galleryIgnore),
1369
- ...this.parseStringArray(disabled.gallery_ignore),
1370
- ]);
1371
- return {
1372
- scriptsByView,
1373
- charts: this.dedupeBasenames([
1374
- ...this.parseStringArray(parsed?.charts),
1375
- ...this.parseStringArray(disabled.charts),
1376
- ]),
1377
- galleryIgnore: hasGalleryIgnoreSetting
1378
- ? galleryIgnore
1379
- : [...AnalyticsDashboardProvider.DEFAULT_GALLERY_IGNORE],
1380
- };
1381
- }
1382
- catch {
1383
- return {
1384
- scriptsByView: {},
1385
- charts: [],
1386
- galleryIgnore: [...AnalyticsDashboardProvider.DEFAULT_GALLERY_IGNORE],
1387
- };
1388
- }
1389
- }
1390
- async postDisabledChartsState(disabledConfig) {
1391
- const resolvedConfig = disabledConfig ?? await this.loadDisabledChartsConfig();
1392
- this.webviewView?.webview.postMessage({
1393
- type: 'disabledChartsUpdated',
1394
- charts: resolvedConfig.charts,
1395
- });
1396
- }
1397
- async saveDisabledChartsConfig(config) {
1398
- const configPath = this.getDisabledChartsConfigPath();
1399
- await fs_1.promises.mkdir(path.dirname(configPath), { recursive: true });
1400
- const scriptsByView = {};
1401
- for (const [rawViewId, rawScripts] of Object.entries(config.scriptsByView)) {
1402
- const viewId = this.normalizeViewId(rawViewId, 'all');
1403
- const scripts = this.dedupeBasenames(rawScripts);
1404
- if (scripts.length > 0) {
1405
- scriptsByView[viewId] = scripts;
1406
- }
1407
- }
1408
- const yaml = require('js-yaml');
1409
- const content = yaml.dump({
1410
- scripts: scriptsByView,
1411
- charts: this.dedupeBasenames(config.charts),
1412
- galleryIgnore: this.dedupeCaseInsensitive(config.galleryIgnore),
1413
- }, { lineWidth: 120, noRefs: true });
1414
- await fs_1.promises.writeFile(configPath, content, 'utf-8');
1415
- }
1416
- isPathWithinDir(pathValue, dirPath) {
1417
- return (0, pathSafety_1.isPathUnderRoot)(dirPath, pathValue);
1418
- }
1419
- async disableChartByFilename(filename) {
1420
- const normalizedFilename = path.basename(filename.trim());
1421
- if (!normalizedFilename) {
1422
- return;
1423
- }
1424
- const disabledConfig = await this.loadDisabledChartsConfig();
1425
- const existing = new Set(disabledConfig.charts.map(name => this.normalizeBasename(name)));
1426
- const key = this.normalizeBasename(normalizedFilename);
1427
- if (!key || existing.has(key)) {
1428
- return;
1429
- }
1430
- await this.saveDisabledChartsConfig({
1431
- scriptsByView: disabledConfig.scriptsByView,
1432
- charts: [...disabledConfig.charts, normalizedFilename],
1433
- galleryIgnore: disabledConfig.galleryIgnore,
1434
- });
1435
- await this.postDisabledChartsState();
1436
- }
1437
- normalizePanelStateId(panelId) {
1438
- const normalized = (panelId ?? 'default')
1439
- .trim()
1440
- .toLowerCase()
1441
- .replace(/[^a-z0-9._-]+/g, '_')
1442
- .replace(/^_+|_+$/g, '');
1443
- return normalized || 'default';
1444
- }
1445
- getPanelStatePath(panelId) {
1446
- const scopedPanelId = this.getScopedPanelId(panelId);
1447
- return path.join(this.artifactManager.getAnalyticsRoot(), 'panel-state', `${scopedPanelId}.json`);
1448
- }
1449
- async loadPanelState(panelId) {
1450
- const statePath = this.getPanelStatePath(panelId);
1451
- try {
1452
- const raw = await fs_1.promises.readFile(statePath, 'utf-8');
1453
- return JSON.parse(raw);
1454
- }
1455
- catch {
1456
- return {};
1457
- }
1458
- }
1459
- async savePanelState(panelId, state) {
1460
- const statePath = this.getPanelStatePath(panelId);
1461
- await fs_1.promises.mkdir(path.dirname(statePath), { recursive: true });
1462
- const payload = state && typeof state === 'object' ? state : {};
1463
- await fs_1.promises.writeFile(statePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');
1464
- }
1465
- async postPanelState(panelId) {
1466
- const normalizedPanelId = this.normalizePanelStateId(panelId);
1467
- const state = await this.loadPanelState(panelId);
1468
- const fleetInterval = Math.max(0, vscode.workspace.getConfiguration('agentide.analytics').get('fleetAutoRefreshIntervalSeconds', 0));
1469
- this.webviewView?.webview.postMessage({
1470
- type: 'panelStateLoaded',
1471
- panelId: normalizedPanelId,
1472
- state,
1473
- fleetAutoRefreshIntervalSeconds: fleetInterval,
1474
- surfaceMode: this.getSurfaceMode(),
1475
- });
1476
- }
1477
- async updateDataFreshnessHint(dataHint) {
1478
- const filters = this.getActiveFilters();
1479
- const hasData = dataHint?.hasData ?? await this.queryRunner.hasData(filters);
1480
- if (!hasData) {
1481
- this.webviewView?.webview.postMessage({ type: 'dataFresh' });
1482
- return;
1483
- }
1484
- const manifest = await this.artifactManager.getManifest();
1485
- const currentHash = dataHint?.dataHash ?? await this.queryRunner.getDataHash(filters);
1486
- if (!manifest) {
1487
- // No manifest yet; don't show stale hint until first generation.
1488
- this.webviewView?.webview.postMessage({ type: 'dataFresh' });
1489
- return;
1490
- }
1491
- if (manifest.dataHash !== currentHash) {
1492
- this.webviewView?.webview.postMessage({
1493
- type: 'dataStale',
1494
- generatedAt: manifest.generatedAt,
1495
- });
1496
- }
1497
- else {
1498
- this.webviewView?.webview.postMessage({ type: 'dataFresh' });
1499
- }
1500
- }
1501
- /** Panel visible: load artifacts, then apply refresh policy (see PLAN/spotlight-refresh-policy.md). */
1502
- async onPanelBecameVisible() {
1503
- await this.loadAndPostViewsState();
1504
- this.syncArtifactContext();
1505
- await this.postPanelState('default');
1506
- await this.seedWorkspaceScripts();
1507
- const bootstrapPromise = this.fetchDashboardBootstrapData();
1508
- // Load existing artifacts immediately; don't block chart refresh on summary queries.
1509
- await this.loadAndSendArtifacts();
1510
- await this.postPanelState('default');
1511
- await this.ensureChartsForCurrentFilterIfNeeded({ trigger: 'panel-open' });
1512
- await this.autoRefreshDefaultCharts();
1513
- const bootstrap = await bootstrapPromise;
1514
- await this.postFilterState(bootstrap?.agentIds);
1515
- this.triggerSummaryCardsUpdate(bootstrap?.summary);
1516
- await this.updateDataFreshnessHint(bootstrap ? { hasData: bootstrap.hasData, dataHash: bootstrap.dataHash } : undefined);
1517
- }
1518
- getFilterCacheKey(filters = this.getActiveFilters()) {
1519
- const scope = filters.scope === 'workspace' ? 'workspace' : 'global';
1520
- const projectId = filters.scope === 'workspace' ? (filters.projectId ?? '') : '';
1521
- const agentId = filters.agentId ?? '';
1522
- return `${scope}::${projectId}::${agentId}`;
1523
- }
1524
- pruneAutoGenerationHistory() {
1525
- if (this.autoGenerationAttemptByFilter.size <= MAX_FILTER_AUTOGEN_HISTORY) {
1526
- return;
1527
- }
1528
- const entries = [...this.autoGenerationAttemptByFilter.entries()]
1529
- .sort((a, b) => a[1] - b[1]);
1530
- const removeCount = this.autoGenerationAttemptByFilter.size - MAX_FILTER_AUTOGEN_HISTORY;
1531
- for (let i = 0; i < removeCount; i++) {
1532
- this.autoGenerationAttemptByFilter.delete(entries[i][0]);
1533
- }
1534
- }
1535
- scheduleAutoGenerateForCurrentFilter() {
1536
- if (this.pendingFilterAutoGenerateTimer) {
1537
- clearTimeout(this.pendingFilterAutoGenerateTimer);
1538
- }
1539
- const scheduledFilterKey = this.getFilterCacheKey();
1540
- this.pendingFilterAutoGenerateTimer = setTimeout(() => {
1541
- void this.ensureChartsForCurrentFilterIfNeeded({
1542
- trigger: 'filter-switch',
1543
- expectedFilterKey: scheduledFilterKey,
1544
- respectCooldown: true,
1545
- });
1546
- }, FILTER_AUTOGEN_DEBOUNCE_MS);
1547
- }
1548
- async ensureChartsForCurrentFilterIfNeeded(options) {
1549
- this.syncArtifactContext();
1550
- const filters = this.getActiveFilters();
1551
- const filterKey = this.getFilterCacheKey(filters);
1552
- if (options?.expectedFilterKey && options.expectedFilterKey !== filterKey) {
1553
- return;
1554
- }
1555
- if (this.isRefreshInProgress) {
1556
- return;
1557
- }
1558
- if (options?.respectCooldown) {
1559
- const lastAttempt = this.autoGenerationAttemptByFilter.get(filterKey) ?? 0;
1560
- if (Date.now() - lastAttempt < FILTER_AUTOGEN_COOLDOWN_MS) {
1561
- this.outputChannel.appendLine('[Dashboard] Auto-generation skipped (cooldown active)');
1562
- return;
1563
- }
1564
- }
1565
- const artifacts = await this.artifactManager.listArtifacts();
1566
- const hasCharts = artifacts.some(artifact => artifact.type === 'chart');
1567
- if (hasCharts) {
1568
- return;
1569
- }
1570
- const hasData = options?.hasDataHint ?? await this.queryRunner.hasData(filters);
1571
- if (!hasData) {
1572
- return;
1573
- }
1574
- this.autoGenerationAttemptByFilter.set(filterKey, Date.now());
1575
- this.pruneAutoGenerationHistory();
1576
- this.outputChannel.appendLine('[Dashboard] No cached charts for selected filters; auto-generating');
1577
- await this.refreshCharts();
1578
- }
1579
- /**
1580
- * Panel-open chart bootstrap: compute when empty; hint when stale (no silent regen).
1581
- * Aligns with PLAN/spotlight-refresh-policy.md — no manifest → refreshCharts;
1582
- * stale hash → dataStale; fresh → dataFresh.
1583
- */
1584
- async autoRefreshDefaultCharts(options) {
1585
- const config = vscode.workspace.getConfiguration('agentide.analytics');
1586
- const autoGenerate = config.get('autoGenerateDefaults', true);
1587
- if (!autoGenerate) {
1588
- return;
1589
- }
1590
- const filters = this.getActiveFilters();
1591
- const hasData = options?.hasDataHint ?? await this.queryRunner.hasData(filters);
1592
- if (!hasData) {
1593
- return;
1594
- }
1595
- const manifest = await this.artifactManager.getManifest();
1596
- const currentHash = options?.dataHashHint ?? await this.queryRunner.getDataHash(filters);
1597
- if (!manifest) {
1598
- // No manifest = first time; run the full script set (seeds + runs all
1599
- // widget scripts, not just generate-default-charts.py).
1600
- await this.refreshCharts();
1601
- }
1602
- else if (manifest.dataHash !== currentHash) {
1603
- // Data changed since last generation - notify user, don't auto-regenerate
1604
- this.webviewView?.webview.postMessage({
1605
- type: 'dataStale',
1606
- generatedAt: manifest.generatedAt,
1607
- });
1608
- }
1609
- else {
1610
- this.webviewView?.webview.postMessage({ type: 'dataFresh' });
1611
- }
1612
- }
1613
- // -------------------------------------------------------------------------
1614
- // Default Chart Generation (script-based)
1615
- // -------------------------------------------------------------------------
1616
- /**
1617
- * Run the bundled Python script to generate default dashboard charts.
1618
- * This is fast and reliable - no agent/LLM needed.
1619
- */
1620
- async runDefaultChartScript(options) {
1621
- this.syncArtifactContext();
1622
- const preserveExistingContent = options?.preserveExistingContent ?? false;
1623
- let refreshOutcome = 'finished';
1624
- let refreshError = '';
1625
- // Cancel any in-flight chart generation before starting a new one.
1626
- // This sends SIGTERM to the Python/DuckDB subprocess via the AbortSignal,
1627
- // preventing stale processes from piling up when users click refresh rapidly.
1628
- this.generationAbortController?.abort();
1629
- this.generationAbortController = new AbortController();
1630
- // Notify if indexing is in progress
1631
- const indexingService = this.indexingService();
1632
- if (indexingService && indexingService.isIndexing()) {
1633
- this.outputChannel.appendLine('[Dashboard] Background DuckDB indexing in progress; using JSONL fallback for charts');
1634
- if (!preserveExistingContent) {
1635
- this.webviewView?.webview.postMessage({
1636
- type: 'notifyIndexing',
1637
- message: 'DuckDB index is being built in background. Charts will use JSONL for now (slower).',
1638
- });
1639
- }
1640
- }
1641
- if (preserveExistingContent) {
1642
- this.startSmoothRefresh({ batchArtifactUpdates: true });
1643
- }
1644
- else {
1645
- this.webviewView?.webview.postMessage({ type: 'generatingStarted' });
1646
- }
1647
- try {
1648
- const tracesDir = path.join((0, index_1.getAgentideHome)(), 'analytics', 'traces');
1649
- const outputDir = (await this.artifactManager.ensureArtifactDir()).fsPath;
1650
- const disabledConfig = await this.loadDisabledChartsConfig();
1651
- const scriptEnv = this.buildScriptEnvironment(outputDir, disabledConfig.charts);
1652
- // Find the chart generation script
1653
- const scriptPath = await this.findChartScript();
1654
- if (!scriptPath) {
1655
- throw new Error('Chart generation script not found');
1656
- }
1657
- this.outputChannel.appendLine(`[Dashboard] Running default chart script: ${scriptPath}`);
1658
- this.outputChannel.appendLine(`[Dashboard] Traces: ${tracesDir}, Output: ${outputDir}`);
1659
- // Run with dependency management via shared Python runner.
1660
- const deps = await this.readPythonDeps();
1661
- let result;
1662
- try {
1663
- const runResult = await (0, pythonRunner_1.runPythonWithDeps)({
1664
- scriptPath,
1665
- args: [tracesDir, outputDir],
1666
- deps,
1667
- analyticsRoot: this.artifactManager.getAnalyticsRoot(),
1668
- requirementsPath: path.join(this.artifactManager.getScriptDir(), 'requirements.txt'),
1669
- execOptions: {
1670
- timeout: 60_000,
1671
- maxBuffer: 5 * 1024 * 1024,
1672
- env: scriptEnv,
1673
- signal: this.generationAbortController?.signal,
1674
- },
1675
- log: (message) => this.outputChannel.appendLine(message),
1676
- });
1677
- result = { stdout: runResult.stdout, stderr: runResult.stderr };
1678
- }
1679
- catch (err) {
1680
- // generate-default-charts.py uses exit code 2 for "no data" but still
1681
- // prints JSON to stdout. Recover that stdout so we can handle no_data cleanly.
1682
- const stdout = err.stdout;
1683
- const stderr = err.stderr;
1684
- if (typeof stdout === 'string' && stdout.trim().startsWith('{')) {
1685
- result = {
1686
- stdout,
1687
- stderr: typeof stderr === 'string' ? stderr : '',
1688
- };
1689
- }
1690
- else {
1691
- throw err;
1692
- }
1693
- }
1694
- if (result.stderr) {
1695
- this.outputChannel.appendLine(`[Dashboard] Chart script stderr: ${result.stderr}`);
1696
- }
1697
- // Parse result JSON
1698
- let scriptResult;
1699
- try {
1700
- scriptResult = JSON.parse(result.stdout);
1701
- }
1702
- catch {
1703
- throw new Error(`Script output not valid JSON: ${result.stdout.slice(0, 200)}`);
1704
- }
1705
- if (scriptResult.status === 'no_data') {
1706
- this.outputChannel.appendLine('[Dashboard] No trace data available');
1707
- if (!preserveExistingContent) {
1708
- this.webviewView?.webview.postMessage({ type: 'generatingCancelled' });
1709
- }
1710
- else {
1711
- refreshOutcome = 'noData';
1712
- }
1713
- return;
1714
- }
1715
- if (scriptResult.status !== 'ok') {
1716
- throw new Error(scriptResult.error || 'Chart generation failed');
1717
- }
1718
- // Build and save manifest
1719
- const dataHash = await this.queryRunner.getDataHash(this.getActiveFilters());
1720
- const manifestEntries = (scriptResult.charts ?? []).map(c => ({
1721
- filename: path.basename(c.file),
1722
- path: path.relative(outputDir, c.file),
1723
- type: 'chart',
1724
- source: 'default',
1725
- title: c.title,
1726
- createdAt: new Date().toISOString(),
1727
- generator: 'generate-default-charts.py',
1728
- }));
1729
- const manifest = {
1730
- version: 1,
1731
- generatedAt: scriptResult.generated_at || new Date().toISOString(),
1732
- dataHash,
1733
- prompt: 'auto-generated',
1734
- artifacts: manifestEntries,
1735
- };
1736
- await this.artifactManager.saveManifest(manifest);
1737
- await this.writeFleetExportForCurrentView();
1738
- this.outputChannel.appendLine(`[Dashboard] Generated ${manifestEntries.length} default charts`);
1739
- // Reload artifacts in panel
1740
- await this.loadAndSendArtifacts();
1741
- }
1742
- catch (err) {
1743
- if (err instanceof Error && err.name === 'AbortError') {
1744
- if (!preserveExistingContent) {
1745
- this.webviewView?.webview.postMessage({ type: 'generatingCancelled' });
1746
- }
1747
- else {
1748
- refreshOutcome = 'failed';
1749
- refreshError = 'Refresh cancelled';
1750
- }
1751
- }
1752
- else {
1753
- const errorMessage = err instanceof Error ? err.message : String(err);
1754
- this.outputChannel.appendLine(`[Dashboard] Chart generation failed: ${errorMessage}`);
1755
- if (preserveExistingContent) {
1756
- refreshOutcome = 'failed';
1757
- refreshError = errorMessage;
1758
- }
1759
- else {
1760
- this.webviewView?.webview.postMessage({
1761
- type: 'generatingFailed',
1762
- error: errorMessage,
1763
- });
1764
- }
1765
- }
1766
- }
1767
- finally {
1768
- if (preserveExistingContent) {
1769
- try {
1770
- await this.finishSmoothRefresh({ notifyFinished: false });
1771
- }
1772
- catch (finishErr) {
1773
- this.outputChannel.appendLine(`[Dashboard] Failed finishing default-refresh state: ${finishErr}`);
1774
- this.webviewView?.webview.postMessage({ type: 'refreshFinished' });
1775
- }
1776
- if (refreshOutcome === 'finished') {
1777
- this.webviewView?.webview.postMessage({ type: 'refreshFinished' });
1778
- }
1779
- else if (refreshOutcome === 'noData') {
1780
- this.webviewView?.webview.postMessage({ type: 'refreshNoData' });
1781
- }
1782
- else {
1783
- this.webviewView?.webview.postMessage({
1784
- type: 'refreshFailed',
1785
- error: refreshError || 'Refresh failed',
1786
- });
1787
- }
1788
- }
1789
- this.generationAbortController = undefined;
1790
- }
1791
- }
1792
- /**
1793
- * Find the chart generation script in bundled or dev locations.
1794
- */
1795
- async findChartScript() {
1796
- const candidates = this.getBundledAnalyticsScriptCandidates('generate-default-charts.py');
1797
- for (const candidate of candidates) {
1798
- try {
1799
- await fs_1.promises.access(candidate);
1800
- return candidate;
1801
- }
1802
- catch {
1803
- // Try next
1804
- }
1805
- }
1806
- return null;
1807
- }
1808
- async findBundledAnalyticsScript(filename) {
1809
- const candidates = this.getBundledAnalyticsScriptCandidates(filename);
1810
- for (const candidate of candidates) {
1811
- try {
1812
- await fs_1.promises.access(candidate);
1813
- return candidate;
1814
- }
1815
- catch {
1816
- // Try next.
1817
- }
1818
- }
1819
- return null;
1820
- }
1821
- getBundledAnalyticsScriptCandidates(filename) {
1822
- const extensionPath = this.extensionContext.extensionPath;
1823
- const runtimeRelativeCandidates = [
1824
- path.resolve(__dirname, '../../scripts', 'analytics', filename),
1825
- path.resolve(__dirname, '../../../scripts', 'analytics', filename),
1826
- path.resolve(__dirname, '../../../../extensions', 'agentide', 'scripts', 'analytics', filename),
1827
- ];
1828
- const extensionRelativeCandidates = [
1829
- path.join(extensionPath, 'out', 'scripts', 'analytics', filename),
1830
- path.join(extensionPath, 'scripts', 'analytics', filename),
1831
- path.join(extensionPath, '..', 'scripts', 'analytics', filename),
1832
- path.join(extensionPath, '..', '..', 'scripts', 'analytics', filename),
1833
- path.join(extensionPath, '..', '..', '..', 'extensions', 'agentide', 'scripts', 'analytics', filename),
1834
- ];
1835
- return [...new Set([...runtimeRelativeCandidates, ...extensionRelativeCandidates])];
1836
- }
1837
- async loadBundledDefaultSeedScriptNames() {
1838
- const fallbackScriptNames = [...FALLBACK_DEFAULT_SEED_SCRIPT_NAMES];
1839
- const registryPath = await this.findBundledAnalyticsScript(DEFAULT_SEED_REGISTRY_FILENAME);
1840
- if (!registryPath) {
1841
- this.outputChannel.appendLine(`[Dashboard] Missing default seed registry: ${DEFAULT_SEED_REGISTRY_FILENAME}. Using built-in defaults.`);
1842
- return fallbackScriptNames;
1843
- }
1844
- try {
1845
- const raw = await fs_1.promises.readFile(registryPath, 'utf-8');
1846
- const parsed = JSON.parse(raw);
1847
- const registryScriptNames = this.parseStringArray(parsed.seedScripts)
1848
- .map(entry => path.basename(entry))
1849
- .filter(Boolean);
1850
- if (registryScriptNames.length === 0) {
1851
- this.outputChannel.appendLine(`[Dashboard] Seed registry ${registryPath} had no scripts. Using built-in defaults.`);
1852
- }
1853
- return this.dedupeBasenames([...registryScriptNames, ...fallbackScriptNames]);
1854
- }
1855
- catch (err) {
1856
- this.outputChannel.appendLine(`[Dashboard] Failed reading seed registry ${registryPath}: ${err}`);
1857
- return fallbackScriptNames;
1858
- }
1859
- }
1860
- getDisabledChartsConfigPath() {
1861
- return path.join(this.artifactManager.getAnalyticsRoot(), 'disabled-charts.yaml');
1862
- }
1863
- hasDuplicateShebangMarkers(content) {
1864
- const shebangMatches = content.match(/^#!/gm);
1865
- return (shebangMatches?.length ?? 0) > 1;
1866
- }
1867
- hasStaleDuckDbLookup(filename, content) {
1868
- const targetScripts = new Set(['session-timeline-widget.js', 'session-comparison-widget.js', 'wow-model-widget.js']);
1869
- if (!targetScripts.has(filename)) {
1870
- return false;
1871
- }
1872
- const usesSharedLocator = content.includes(`require('./duckdbLocator')`);
1873
- const hasLegacyInlineFinder = content.includes('function findDuckdb()')
1874
- && content.includes("path.join(home, '.agent-hippo', 'bin', 'duckdb')");
1875
- const hasWindowsCandidate = content.includes('duckdb.exe');
1876
- const hasLegacyHomeLookup = content.includes("const home = process.env.HOME || ''");
1877
- // Repair stale seeded scripts that still use legacy inline lookup and miss
1878
- // current Windows-safe logic and shared helper import.
1879
- return !usesSharedLocator && (hasLegacyInlineFinder || hasLegacyHomeLookup || !hasWindowsCandidate);
1880
- }
1881
- async repairCorruptedBundledDefaultScripts(defaultScriptNames) {
1882
- const scriptsDir = this.artifactManager.getScriptDir();
1883
- let repairedCount = 0;
1884
- for (const rawFilename of defaultScriptNames) {
1885
- const filename = path.basename(rawFilename);
1886
- if (!filename) {
1887
- continue;
1888
- }
1889
- const destinationPath = path.join(scriptsDir, filename);
1890
- let content;
1891
- try {
1892
- content = await fs_1.promises.readFile(destinationPath, 'utf-8');
1893
- }
1894
- catch {
1895
- continue;
1896
- }
1897
- const needsShebangRepair = this.hasDuplicateShebangMarkers(content);
1898
- const needsDuckdbRepair = this.hasStaleDuckDbLookup(filename, content);
1899
- if (!needsShebangRepair && !needsDuckdbRepair) {
1900
- continue;
1901
- }
1902
- const sourcePath = await this.findBundledAnalyticsScript(filename);
1903
- if (!sourcePath) {
1904
- this.outputChannel.appendLine(`[Dashboard] Could not repair script ${filename}: source not found`);
1905
- continue;
1906
- }
1907
- if (path.resolve(sourcePath) === path.resolve(destinationPath)) {
1908
- continue;
1909
- }
1910
- try {
1911
- await fs_1.promises.copyFile(sourcePath, destinationPath);
1912
- repairedCount += 1;
1913
- const reason = needsShebangRepair ? 'duplicate shebang markers' : 'stale DuckDB lookup';
1914
- this.outputChannel.appendLine(`[Dashboard] Repaired default script ${filename} (${reason})`);
1915
- }
1916
- catch (err) {
1917
- this.outputChannel.appendLine(`[Dashboard] Failed repairing default script ${filename}: ${err}`);
1918
- }
1919
- }
1920
- return repairedCount;
1921
- }
1922
- /**
1923
- * Bundled DuckDB/chart scripts belong on Workspace-style views, not custom views
1924
- * (e.g. live-fleet-2) that declare their own script list.
1925
- */
1926
- viewNeedsBundledDefaultScripts(view, defaultScriptNames) {
1927
- if (view.scripts.length === 0) {
1928
- // System views with no scripts (Workspace) run every script on disk except Fleet widget.
1929
- return view.system === true && view.id !== 'fleet';
1930
- }
1931
- const defaultBasenames = new Set(defaultScriptNames.map(name => path.basename(name).toLowerCase()));
1932
- return this.dedupeBasenames(view.scripts).some(script => defaultBasenames.has(script.toLowerCase()));
1933
- }
1934
- async ensureBundledDefaultScriptsPresent() {
1935
- const defaultScriptNames = await this.loadBundledDefaultSeedScriptNames();
1936
- if (defaultScriptNames.length === 0) {
1937
- this.outputChannel.appendLine('[Dashboard] No default scripts listed in seed registry');
1938
- return;
1939
- }
1940
- const activeView = this.getActiveView();
1941
- if (!this.viewNeedsBundledDefaultScripts(activeView, defaultScriptNames)) {
1942
- return;
1943
- }
1944
- const existingScripts = await this.artifactManager.listScripts();
1945
- const existingByBasename = new Set(existingScripts.map(script => path.basename(script.filename).toLowerCase()));
1946
- const seedCandidates = [];
1947
- for (const filename of defaultScriptNames) {
1948
- if (existingByBasename.has(path.basename(filename).toLowerCase())) {
1949
- continue;
1950
- }
1951
- const sourcePath = await this.findBundledAnalyticsScript(filename);
1952
- if (sourcePath) {
1953
- seedCandidates.push({ sourcePath, filename });
1954
- }
1955
- }
1956
- if (seedCandidates.length > 0) {
1957
- await this.artifactManager.ensureSeedScripts(seedCandidates);
1958
- this.outputChannel.appendLine(`[Dashboard] Seeded ${seedCandidates.length} missing default script(s)`);
1959
- }
1960
- else {
1961
- this.outputChannel.appendLine(`[Dashboard] No missing default scripts were resolvable from extension path: ${this.extensionContext.extensionPath}`);
1962
- }
1963
- const repairedCount = await this.repairCorruptedBundledDefaultScripts(defaultScriptNames);
1964
- if (repairedCount > 0) {
1965
- this.outputChannel.appendLine(`[Dashboard] Repaired ${repairedCount} default script(s)`);
1966
- }
1967
- }
1968
- async seedWorkspaceScripts() {
1969
- // Seed requirements.txt for Python script dependencies
1970
- await this.seedRequirements();
1971
- // Seed disabled-charts config for script/chart opt-out.
1972
- await this.seedDisabledChartsConfig();
1973
- // Ensure all bundled default scripts exist; never overwrite user edits.
1974
- await this.ensureBundledDefaultScriptsPresent();
1975
- // Ensure widget log folder exists even before first refresh so users can
1976
- // verify where per-run JSONL logs will appear.
1977
- try {
1978
- await fs_1.promises.mkdir(this.getWidgetRunLogsDir(), { recursive: true });
1979
- }
1980
- catch (err) {
1981
- this.outputChannel.appendLine(`[Dashboard] Failed creating widget log directory: ${err}`);
1982
- }
1983
- // Seed panel files (panel.js, panel.css, panel.html) into .agent-hippo/analytics/panel/
1984
- await this.seedPanelFiles();
1985
- }
1986
- async seedDisabledChartsConfig() {
1987
- const configPath = this.getDisabledChartsConfigPath();
1988
- try {
1989
- await fs_1.promises.access(configPath);
1990
- return;
1991
- }
1992
- catch {
1993
- // Missing file; seed default template.
1994
- }
1995
- const template = [
1996
- '# Disable script execution or chart outputs without deleting files.',
1997
- '# scripts: per-view script filenames in analytics/views/<viewId>/scripts to skip running.',
1998
- '# Use "all" to apply to every view id.',
1999
- 'scripts:',
2000
- ' all: []',
2001
- ' # all:',
2002
- ' # - wow-model-widget.js',
2003
- ' # fleet:',
2004
- ' # - agent-fleet-widget.js',
2005
- '# charts: output filenames in analytics/views/.../charts to disable/hide.',
2006
- 'charts:',
2007
- ' # Hotspot analysis is opt-in by default; remove this entry to enable it.',
2008
- ' - hotspot-analysis-widget.html',
2009
- '# galleryIgnore: suffixes or exact filenames hidden from gallery cards.',
2010
- 'galleryIgnore:',
2011
- ` - ${AnalyticsDashboardProvider.DEFAULT_GALLERY_IGNORE[0]}`,
2012
- ` - ${AnalyticsDashboardProvider.DEFAULT_GALLERY_IGNORE[1]}`,
2013
- '',
2014
- ].join('\n');
2015
- try {
2016
- await fs_1.promises.writeFile(configPath, template, 'utf-8');
2017
- }
2018
- catch (err) {
2019
- this.outputChannel.appendLine(`[Dashboard] Failed seeding disabled-charts.yaml: ${err}`);
2020
- }
2021
- }
2022
- /**
2023
- * Seed a default requirements.txt in the scripts directory.
2024
- * Users and agents can edit this file to add/remove Python dependencies.
2025
- * Non-destructive: never overwrites an existing file.
2026
- */
2027
- async seedRequirements() {
2028
- const scriptsDir = this.artifactManager.getScriptDir();
2029
- await fs_1.promises.mkdir(scriptsDir, { recursive: true });
2030
- const reqPath = path.join(scriptsDir, 'requirements.txt');
2031
- try {
2032
- await fs_1.promises.access(reqPath);
2033
- return; // Already exists, respect user edits.
2034
- }
2035
- catch {
2036
- // Missing, seed it.
2037
- }
2038
- try {
2039
- await fs_1.promises.writeFile(reqPath, [
2040
- '# Python dependencies for analytics scripts.',
2041
- '# Add packages here - they are auto-installed when scripts run.',
2042
- 'matplotlib',
2043
- 'duckdb',
2044
- '',
2045
- ].join('\n'), 'utf-8');
2046
- }
2047
- catch (err) {
2048
- this.outputChannel.appendLine(`[Dashboard] Failed seeding requirements.txt: ${err}`);
2049
- }
2050
- }
2051
- /**
2052
- * Seed default panel files from bundled webview assets into
2053
- * the user's analytics panel directory (.agent-hippo/analytics/panel/).
2054
- * Non-destructive: existing files are never overwritten.
2055
- */
2056
- async seedPanelFiles() {
2057
- const panelDir = this.getPanelDir();
2058
- await fs_1.promises.mkdir(panelDir, { recursive: true });
2059
- const webviewBasePath = this.getWebviewPath();
2060
- const bundledDir = path.join(this.extensionContext.extensionPath, webviewBasePath);
2061
- // Bundled source → user destination filename.
2062
- // panel.js is deliberately NOT seeded: getHtmlContent always loads the bundled panel.js,
2063
- // so a seeded copy is never executed. Writing one only created a file that looks editable,
2064
- // silently diverges from the bundle, and makes "my dashboard edit did nothing" reports.
2065
- const seedMap = [
2066
- { bundled: 'styles.css', userFile: 'panel.css' },
2067
- { bundled: 'analytics-panel.html', userFile: 'panel.html' },
2068
- ];
2069
- // Explain a pre-existing seeded copy rather than deleting the user's file.
2070
- try {
2071
- await fs_1.promises.access(path.join(panelDir, 'panel.js'));
2072
- this.outputChannel.appendLine(`[Dashboard] Note: ${path.join(panelDir, 'panel.js')} is not used — the bundled panel.js always loads. ` +
2073
- `Edit panel.html / panel.css to customize the dashboard.`);
2074
- }
2075
- catch {
2076
- // No stale copy — nothing to explain.
2077
- }
2078
- for (const { bundled, userFile } of seedMap) {
2079
- const dest = path.join(panelDir, userFile);
2080
- try {
2081
- await fs_1.promises.access(dest);
2082
- continue; // Already exists, respect user modifications.
2083
- }
2084
- catch {
2085
- // Missing, seed it.
2086
- }
2087
- const source = path.join(bundledDir, bundled);
2088
- try {
2089
- await fs_1.promises.copyFile(source, dest);
2090
- }
2091
- catch (err) {
2092
- this.outputChannel.appendLine(`[Dashboard] Failed seeding panel file ${userFile}: ${err}`);
2093
- }
2094
- }
2095
- await this.patchUserPanelHtmlOnDisk();
2096
- }
2097
- /**
2098
- * Get the user's panel directory under the analytics root.
2099
- */
2100
- getPanelDir() {
2101
- return this.artifactManager.getPanelDir();
2102
- }
2103
- // -------------------------------------------------------------------------
2104
- // Refresh: re-run widget scripts (manual, Fleet, or "compute when empty")
2105
- // Policy: PLAN/spotlight-refresh-policy.md — manual + Fleet always enter here;
2106
- // navigation should gate before calling refreshCharts on view switch.
2107
- // -------------------------------------------------------------------------
2108
- /**
2109
- * Run assigned widget scripts for the active view (or requested view when provided).
2110
- * Writes artifacts and updates manifest.dataHash.
2111
- */
2112
- async refreshCharts(options) {
2113
- if (options?.viewId) {
2114
- const switched = await this.setActiveView(options.viewId);
2115
- if (!switched) {
2116
- this.outputChannel.appendLine(`[Dashboard] Ignoring refresh for unknown view: ${options.viewId}`);
2117
- return;
2118
- }
2119
- }
2120
- this.syncArtifactContext();
2121
- if (this.isRefreshInProgress) {
2122
- this.hasPendingRefreshRequest = true;
2123
- this.outputChannel.appendLine('[Dashboard] Refresh already running; queued one follow-up refresh');
2124
- return;
2125
- }
2126
- const refreshContext = this.getRefreshContext(options);
2127
- this.isRefreshInProgress = true;
2128
- let rerunRequested = false;
2129
- let logContext;
2130
- let refreshOutcome = 'ok';
2131
- let refreshOutcomeError = '';
2132
- try {
2133
- try {
2134
- logContext = await this.startWidgetRunLog();
2135
- }
2136
- catch (logErr) {
2137
- this.outputChannel.appendLine(`[Dashboard] Failed to initialize widget run log: ${logErr}`);
2138
- }
2139
- await this.ensureBundledDefaultScriptsPresent();
2140
- const disabledConfig = await this.loadDisabledChartsConfig();
2141
- const scriptSelection = await this.getScriptsForView(options);
2142
- const disabledScriptNames = new Set(this.getDisabledScriptsForView(disabledConfig, scriptSelection.view.id)
2143
- .map(name => this.normalizeBasename(name)));
2144
- const userScripts = scriptSelection.scripts.filter(script => !disabledScriptNames.has(this.normalizeBasename(script.filename)));
2145
- if (userScripts.length === 0) {
2146
- this.outputChannel.appendLine(`[Dashboard] No scripts assigned to view "${scriptSelection.view.name}"`);
2147
- await this.finishSmoothRefresh({ notifyFinished: false, refresh: options });
2148
- postToWebview(this.webviewView?.webview, { type: 'refreshNoData', viewId: refreshContext.viewId });
2149
- return;
2150
- }
2151
- this.startSmoothRefresh({ batchArtifactUpdates: false, refresh: options });
2152
- let refreshError = null;
2153
- let widgetRunResult = { totalScripts: 0, failedScripts: 0 };
2154
- try {
2155
- const hasFleetScript = userScripts.some(s => this.normalizeBasename(s.filename) === 'agent-fleet-widget.js');
2156
- if (hasFleetScript) {
2157
- // Use latest DuckDB data (avoid 30s cache) and request a quick re-index so next refresh is fresher
2158
- this.queryRunner.invalidateCache();
2159
- this.indexingService()?.scheduleIndexing(true, { debounceMs: 500 });
2160
- const activeView = this.getActiveView();
2161
- await this.ensureViewOwnedDirectories(activeView);
2162
- await this.writeFleetExportForCurrentView();
2163
- }
2164
- this.outputChannel.appendLine(`[Dashboard] Refreshing view "${scriptSelection.view.name}" via ${userScripts.length} script(s)`);
2165
- widgetRunResult = await this.runUserScripts(userScripts, disabledConfig.charts, logContext);
2166
- if (widgetRunResult.failedScripts > 0 && refreshOutcome === 'ok') {
2167
- refreshOutcome = 'failed';
2168
- refreshOutcomeError = `${widgetRunResult.failedScripts}/${widgetRunResult.totalScripts} widget scripts failed`;
2169
- }
2170
- const dataHash = await this.queryRunner.getDataHash(this.getActiveFilters());
2171
- const manifest = await this.artifactManager.getManifest() ?? {
2172
- version: 1,
2173
- generatedAt: new Date().toISOString(),
2174
- dataHash,
2175
- prompt: '',
2176
- artifacts: [],
2177
- };
2178
- manifest.generatedAt = new Date().toISOString();
2179
- manifest.dataHash = dataHash;
2180
- await this.artifactManager.saveManifest(manifest);
2181
- if (this.shouldUpdateFleetExportAfterRefresh(hasFleetScript)) {
2182
- await this.writeFleetExportForCurrentView();
2183
- }
2184
- await this.enqueueArtifactRefresh();
2185
- }
2186
- catch (err) {
2187
- const errorMessage = err instanceof Error ? err.message : String(err);
2188
- this.outputChannel.appendLine(`[Dashboard] Refresh failed: ${errorMessage}`);
2189
- refreshError = errorMessage;
2190
- refreshOutcome = errorMessage.toLowerCase().includes('cancel') ? 'cancelled' : 'failed';
2191
- refreshOutcomeError = errorMessage;
2192
- }
2193
- finally {
2194
- try {
2195
- await this.finishSmoothRefresh({ notifyFinished: true, refresh: options });
2196
- }
2197
- catch (finishErr) {
2198
- this.outputChannel.appendLine(`[Dashboard] Failed finishing refresh state: ${finishErr}`);
2199
- this.postRefreshStatus('refreshFinished', options);
2200
- }
2201
- if (refreshError !== null) {
2202
- postToWebview(this.webviewView?.webview, {
2203
- type: 'refreshFailed',
2204
- viewId: refreshContext.viewId,
2205
- error: refreshError,
2206
- });
2207
- }
2208
- }
2209
- }
2210
- catch (err) {
2211
- const errorMessage = err instanceof Error ? err.message : String(err);
2212
- this.outputChannel.appendLine(`[Dashboard] Refresh failed: ${errorMessage}`);
2213
- postToWebview(this.webviewView?.webview, {
2214
- type: 'refreshFailed',
2215
- viewId: refreshContext.viewId,
2216
- error: errorMessage,
2217
- });
2218
- this.postRefreshStatus('refreshFinished', options);
2219
- refreshOutcome = errorMessage.toLowerCase().includes('cancel') ? 'cancelled' : 'failed';
2220
- refreshOutcomeError = errorMessage;
2221
- }
2222
- finally {
2223
- try {
2224
- await this.finishWidgetRunLog(logContext, refreshOutcome, refreshOutcomeError);
2225
- }
2226
- catch (logErr) {
2227
- this.outputChannel.appendLine(`[Dashboard] Failed finalizing widget run log: ${logErr}`);
2228
- }
2229
- rerunRequested = this.hasPendingRefreshRequest;
2230
- this.hasPendingRefreshRequest = false;
2231
- this.isRefreshInProgress = false;
2232
- }
2233
- if (rerunRequested) {
2234
- this.outputChannel.appendLine('[Dashboard] Running queued follow-up refresh');
2235
- await this.refreshCharts({ viewId: this.getActiveViewId() });
2236
- }
2237
- }
2238
- async refreshWidgetScriptsOnly(requestedScripts) {
2239
- const normalizedRequested = Array.from(new Set(requestedScripts
2240
- .map(script => this.normalizeBasename(script))
2241
- .filter(script => !!script)));
2242
- if (normalizedRequested.length === 0) {
2243
- return;
2244
- }
2245
- const refreshContext = {
2246
- viewId: this.getActiveViewId(),
2247
- viewName: this.getActiveView().name,
2248
- };
2249
- this.startSmoothRefresh({
2250
- batchArtifactUpdates: true,
2251
- refresh: refreshContext,
2252
- });
2253
- try {
2254
- this.syncArtifactContext();
2255
- await this.ensureBundledDefaultScriptsPresent();
2256
- const requestedSet = new Set(normalizedRequested);
2257
- const isFleetWidgetRefresh = requestedSet.has('agent-fleet-widget.js') && this.getActiveViewId() === 'fleet';
2258
- if (isFleetWidgetRefresh) {
2259
- const activeView = this.getActiveView();
2260
- await this.ensureViewOwnedDirectories(activeView);
2261
- await this.writeFleetExportForCurrentView();
2262
- }
2263
- const scriptSelection = await this.getScriptsForView({ viewId: this.getActiveViewId() });
2264
- const disabledConfig = await this.loadDisabledChartsConfig();
2265
- const disabledScriptNames = new Set(this.getDisabledScriptsForView(disabledConfig, scriptSelection.view.id)
2266
- .map(name => this.normalizeBasename(name)));
2267
- const scriptsToRun = scriptSelection.scripts.filter(script => requestedSet.has(this.normalizeBasename(script.filename))
2268
- && !disabledScriptNames.has(this.normalizeBasename(script.filename)));
2269
- if (scriptsToRun.length === 0) {
2270
- await this.enqueueArtifactRefresh();
2271
- await this.postViewExportSnapshot({ respondWhenMissing: true });
2272
- return;
2273
- }
2274
- await this.runUserScripts(scriptsToRun, disabledConfig.charts);
2275
- if (isFleetWidgetRefresh) {
2276
- // Bump version so non-preserveFleetIframe paths reload the iframe. preserveFleetIframe
2277
- // views (the default for fleet-dashboard layout) ignore this and rely on the
2278
- // viewExportSnapshot postMessage from postViewExportSnapshot below — the widget's
2279
- // DOM-diff renderer applies updates in-place without flicker.
2280
- this.fleetRefreshVersion += 1;
2281
- }
2282
- await this.enqueueArtifactRefresh();
2283
- await this.postViewExportSnapshot({ respondWhenMissing: true });
2284
- }
2285
- finally {
2286
- await this.finishSmoothRefresh({
2287
- notifyFinished: true,
2288
- refresh: refreshContext,
2289
- });
2290
- }
2291
- }
2292
- /**
2293
- * Run user-created scripts from <workspace>/.agent-hippo/analytics/views/<viewId>/scripts/.
2294
- * Each script receives env vars for traces dir and output dirs.
2295
- */
2296
- isDuckDbHeavyScript(script) {
2297
- return this.duckDbHeavyScriptNames.has(path.basename(script.filename).toLowerCase());
2298
- }
2299
- getWidgetRunLogsDir() {
2300
- return path.join(this.artifactManager.getAnalyticsRoot(), 'logs', 'widgets');
2301
- }
2302
- createWidgetRunId() {
2303
- const now = new Date();
2304
- const ts = now.toISOString().replace(/[:.]/g, '-');
2305
- const nonce = Math.random().toString(36).slice(2, 8);
2306
- return `refresh-${ts}-${nonce}`;
2307
- }
2308
- async appendWidgetRunLogLine(logPath, payload) {
2309
- const serialized = `${JSON.stringify(payload)}\n`;
2310
- await fs_1.promises.appendFile(logPath, serialized, 'utf-8');
2311
- }
2312
- /** Fleet export is only needed for the Fleet view or views that run agent-fleet-widget.js. */
2313
- shouldUpdateFleetExportAfterRefresh(hasFleetScript) {
2314
- return this.getActiveViewId() === 'fleet' || hasFleetScript;
2315
- }
2316
- async writeFleetExportForCurrentView() {
2317
- const filters = this.getActiveFilters();
2318
- const exportPath = path.join(this.artifactManager.getExportDir(), AnalyticsDashboardProvider.FLEET_EXPORT_FILENAME);
2319
- const normalizeSessionsForCompare = (sessions) => {
2320
- if (!Array.isArray(sessions)) {
2321
- return [];
2322
- }
2323
- return sessions
2324
- .map((s) => (s && typeof s === 'object' ? s : null))
2325
- .filter((s) => Boolean(s))
2326
- .slice()
2327
- .sort((a, b) => {
2328
- const rankDiff = (0, fleetTypes_1.rankFleetStatus)(a.status) - (0, fleetTypes_1.rankFleetStatus)(b.status);
2329
- if (rankDiff !== 0) {
2330
- return rankDiff;
2331
- }
2332
- const aStarted = Date.parse(String(a.startedAt ?? ''));
2333
- const bStarted = Date.parse(String(b.startedAt ?? ''));
2334
- if (Number.isFinite(aStarted) && Number.isFinite(bStarted) && aStarted !== bStarted) {
2335
- return bStarted - aStarted;
2336
- }
2337
- return String(a.sessionId ?? '').localeCompare(String(b.sessionId ?? ''));
2338
- });
2339
- };
2340
- const comparableFromPayload = (payload) => ({
2341
- scope: payload.scope ?? 'global',
2342
- projectId: payload.projectId ?? '',
2343
- agentId: payload.agentId ?? '',
2344
- totals: payload.totals ?? {},
2345
- sessions: normalizeSessionsForCompare(payload.sessions),
2346
- });
2347
- const readComparable = async () => {
2348
- try {
2349
- const raw = await fs_1.promises.readFile(exportPath, 'utf-8');
2350
- const parsed = JSON.parse(raw);
2351
- return JSON.stringify(comparableFromPayload(parsed));
2352
- }
2353
- catch {
2354
- return null;
2355
- }
2356
- };
2357
- const previousComparable = await readComparable();
2358
- try {
2359
- const result = await (0, fleetOrchestrator_1.runFleetRefresh)({ scope: filters.scope ?? 'global', projectId: filters.projectId, agentId: filters.agentId }, {
2360
- queryRunner: this.queryRunner,
2361
- exportPath,
2362
- onError: (sourceId, err) => {
2363
- this.outputChannel.appendLine(`[Dashboard] Fleet source "${sourceId}" failed: ${String(err)}`);
2364
- },
2365
- });
2366
- if (!result.success) {
2367
- this.outputChannel.appendLine(`[Dashboard] Fleet refresh completed with source errors (failed: ${result.failedSources.join(', ') || 'none'}).`);
2368
- }
2369
- }
2370
- catch (err) {
2371
- this.outputChannel.appendLine(`[Dashboard] Fleet refresh failed: ${err}`);
2372
- return;
2373
- }
2374
- const nextComparable = await readComparable();
2375
- const meaningfulChange = previousComparable === null || nextComparable !== previousComparable;
2376
- if (meaningfulChange && this.getActiveViewId() === 'fleet') {
2377
- this.fleetRefreshVersion += 1;
2378
- // Regenerate agent-fleet.html from the updated JSON so gallery mtime and iframe content stay in sync.
2379
- await this.refreshFleetWidgetFromCurrentExport();
2380
- // During refreshCharts, the outer flow calls enqueueArtifactRefresh after scripts; skip mid-refresh to avoid flicker.
2381
- if (!this.isRefreshInProgress) {
2382
- await this.enqueueArtifactRefresh();
2383
- }
2384
- }
2385
- }
2386
- async handleFleetCardTitleUpdate(sourceId, sessionIdValue, cardTitleValue) {
2387
- const sessionId = String(sessionIdValue ?? '').trim();
2388
- if (!sessionId) {
2389
- return;
2390
- }
2391
- const cardTitle = String(cardTitleValue ?? '').trim();
2392
- const fleetTitleLogPaths = Array.from(new Set([
2393
- path.join((0, index_1.getAgentideHome)(), 'analytics', 'fleet-title-updates.log'),
2394
- path.join(this.artifactManager.getAnalyticsRoot(), 'fleet-title-updates.log'),
2395
- ]));
2396
- const logLine = async (status, err) => {
2397
- const message = `${new Date().toISOString()}\tsessionId=${sessionId}\tcardTitle=${JSON.stringify(cardTitle)}\t${status}`;
2398
- const extra = err !== undefined ? `\terror=${String(err)}` : '';
2399
- await Promise.all(fleetTitleLogPaths.map(async (p) => {
2400
- await fs_1.promises.mkdir(path.dirname(p), { recursive: true });
2401
- await fs_1.promises.appendFile(p, `${message}${extra}\n`, 'utf-8').catch(() => undefined);
2402
- }));
2403
- };
2404
- await logLine('requested');
2405
- try {
2406
- await this.queryRunner.updateFleetCardTitle(sourceId, sessionId, cardTitle);
2407
- // Ensure widget export/html reflect the persisted title (writeFleetExport regenerates HTML when Fleet is active).
2408
- await this.writeFleetExportForCurrentView();
2409
- await logLine('persisted');
2410
- this.outputChannel.appendLine(`[Dashboard] Fleet title edit persisted for session=${sessionId}`);
2411
- }
2412
- catch (err) {
2413
- await logLine('failed', err);
2414
- this.outputChannel.appendLine(`[Dashboard] Fleet title edit failed for session=${sessionId}: ${err}`);
2415
- }
2416
- }
2417
- async refreshFleetWidgetFromCurrentExport() {
2418
- const activeViewId = this.getActiveViewId();
2419
- if (activeViewId !== 'fleet') {
2420
- return;
2421
- }
2422
- const activeView = this.getActiveView();
2423
- if (!activeView) {
2424
- return;
2425
- }
2426
- this.syncArtifactContext();
2427
- await this.ensureBundledDefaultScriptsPresent();
2428
- await this.ensureViewOwnedDirectories(activeView);
2429
- const scriptSelection = await this.getScriptsForView({ viewId: activeViewId });
2430
- const disabledConfig = await this.loadDisabledChartsConfig();
2431
- const disabledScriptNames = new Set(this.getDisabledScriptsForView(disabledConfig, scriptSelection.view.id)
2432
- .map(name => this.normalizeBasename(name)));
2433
- const scriptsToRun = scriptSelection.scripts.filter(script => {
2434
- const basename = this.normalizeBasename(script.filename);
2435
- return basename === 'agent-fleet-widget.js' && !disabledScriptNames.has(basename);
2436
- });
2437
- await this.runUserScripts(scriptsToRun, disabledConfig.charts);
2438
- }
2439
- buildFleetDisconnectPrompt(sessionId, channelType, channelTarget) {
2440
- const safeSessionId = sessionId.trim();
2441
- const safeChannelType = (channelType ?? '').trim() || 'unknown';
2442
- const safeChannelTarget = (channelTarget ?? '').trim();
2443
- return [
2444
- 'Disconnect this Agent Anywhere channel session.',
2445
- `Session ID: ${safeSessionId}`,
2446
- `Channel Type: ${safeChannelType}`,
2447
- safeChannelTarget ? `Channel Target: ${safeChannelTarget}` : '',
2448
- 'If direct disconnect tooling is unavailable, provide the exact command/workflow to disconnect safely.',
2449
- ].filter(Boolean).join('\n');
2450
- }
2451
- async handleFleetDisconnectRequest(sessionIdValue, channelType, channelTarget) {
2452
- const sessionId = sessionIdValue.trim();
2453
- if (!sessionId) {
2454
- return;
2455
- }
2456
- const choice = await vscode.window.showWarningMessage(`Disconnect channel for session ${sessionId}?`, { modal: true }, 'Disconnect');
2457
- if (choice !== 'Disconnect') {
2458
- return;
2459
- }
2460
- await this.openVsCodeChat({
2461
- prompt: this.buildFleetDisconnectPrompt(sessionId, channelType, channelTarget),
2462
- });
2463
- }
2464
- /**
2465
- * Builds the VS Code chat session resource URI for a local session id.
2466
- * Matches workbench LocalChatSessionUri: vscode-chat-session://local/{base64url(sessionId)}
2467
- */
2468
- buildLocalChatSessionResource(sessionId) {
2469
- const base64url = Buffer.from(sessionId, 'utf-8')
2470
- .toString('base64')
2471
- .replace(/\+/g, '-')
2472
- .replace(/\//g, '_')
2473
- .replace(/=+$/, '');
2474
- return `vscode-chat-session://local/${base64url}`;
2475
- }
2476
- async resolveFleetSessionOpenPreferences(sessionId, model, engine, agentId, taskTitle) {
2477
- const row = (0, fleetRowDefaults_1.buildFleetSessionRow)('local', {
2478
- sessionId,
2479
- agentId,
2480
- status: 'in_progress',
2481
- startedAt: new Date().toISOString(),
2482
- endedAt: '',
2483
- taskTitle: String(taskTitle ?? '').trim() || sessionId,
2484
- model: String(model ?? '').trim() || 'unknown',
2485
- engine: String(engine ?? '').trim(),
2486
- channelType: 'none',
2487
- channelTarget: '',
2488
- turns: 0,
2489
- totalTokens: 0,
2490
- });
2491
- if (!taskTitle?.trim()) {
2492
- try {
2493
- const titleMap = await vscode.commands.executeCommand('agentide.resolveFleetSessionTitles', [sessionId]);
2494
- const resolved = typeof titleMap?.[sessionId] === 'string' ? titleMap[sessionId].trim() : '';
2495
- if (resolved) {
2496
- row.taskTitle = resolved;
2497
- row.cardTitle = row.cardTitle ?? resolved;
2498
- }
2499
- }
2500
- catch {
2501
- // Workbench command unavailable outside AgentHippo builds.
2502
- }
2503
- }
2504
- (0, fleetDisplayUtils_1.enrichFleetRowMetadata)(row);
2505
- return {
2506
- model: row.model,
2507
- engine: row.engine,
2508
- modeName: (0, fleetDisplayUtils_1.fleetEngineIdToChatModeName)(row.engine),
2509
- };
2510
- }
2511
- /**
2512
- * Opens the actual chat session by id in this workspace when it exists;
2513
- * otherwise opens chat with a prompt to investigate the session.
2514
- */
2515
- async openSpotlightForSession(sessionIdValue, channelType, channelTarget, model, engine, agentId) {
2516
- let sessionId = sessionIdValue.trim();
2517
- if (!sessionId) {
2518
- return;
2519
- }
2520
- // Fleet schedule cards use sessionId schedule:<id>; channelTarget stores the chat session id.
2521
- if (sessionId.startsWith('schedule:')) {
2522
- const linked = String(channelTarget ?? '').trim();
2523
- if (linked) {
2524
- sessionId = linked;
2525
- }
2526
- }
2527
- const sessionResource = this.buildLocalChatSessionResource(sessionId);
2528
- const resolved = await this.resolveFleetSessionOpenPreferences(sessionId, model, engine, agentId);
2529
- const preferredModel = resolved.model;
2530
- const preferredEngine = resolved.engine;
2531
- const packId = String(agentId ?? '').trim() || 'default';
2532
- const fallbackPrompt = [
2533
- 'Show the session investigation for this agent session.',
2534
- `Session ID: ${sessionId}`,
2535
- channelType ? `Channel: ${channelType}` : '',
2536
- channelTarget ? `Target: ${channelTarget}` : '',
2537
- 'Include: the live chat transcript, a chronological feed of the agent\'s actions/logs, and the exact prompt or payload that initiated the task.',
2538
- ].filter(Boolean).join('\n');
2539
- try {
2540
- await vscode.commands.executeCommand('workbench.action.chat.open', {
2541
- sessionResource,
2542
- mode: resolved.modeName ?? 'agent',
2543
- });
2544
- // Restore engine/model from fleet card (scheduled jobs often have no DuckDB token rows).
2545
- for (let attempt = 0; attempt < 5; attempt += 1) {
2546
- await new Promise(resolve => setTimeout(resolve, attempt === 0 ? 150 : 100));
2547
- const chatState = await vscode.commands.executeCommand('agentide.getChatState');
2548
- const activeSessionId = chatState?.sessionId?.trim() ?? '';
2549
- if (activeSessionId !== sessionId) {
2550
- continue;
2551
- }
2552
- await this.applyFleetSessionChatPreferences(packId, preferredEngine, preferredModel);
2553
- return;
2554
- }
2555
- await this.applyFleetSessionChatPreferences(packId, preferredEngine, preferredModel);
2556
- // workbench.action.chat.open may silently fall back to current session when sessionResource is unknown.
2557
- // Verify active session and use prompt fallback if we didn't land on the requested id.
2558
- const chatState = await vscode.commands.executeCommand('agentide.getChatState');
2559
- const activeSessionId = chatState?.sessionId?.trim() ?? '';
2560
- if (activeSessionId === sessionId) {
2561
- return;
2562
- }
2563
- this.outputChannel.appendLine(`[Dashboard] Could not open session ${sessionId} directly (active=${activeSessionId || 'unknown'}); falling back to investigation prompt`);
2564
- await this.openVsCodeChat({ prompt: fallbackPrompt });
2565
- }
2566
- catch {
2567
- // Session may not exist in this workspace; fall back to opening chat with investigation prompt.
2568
- await this.openVsCodeChat({ prompt: fallbackPrompt });
2569
- }
2570
- }
2571
- async applyFleetSessionChatPreferences(packId, preferredEngine, preferredModel) {
2572
- if (!preferredEngine && !preferredModel) {
2573
- return;
2574
- }
2575
- try {
2576
- await vscode.commands.executeCommand('agentide.applyPackPreferences', {
2577
- packId,
2578
- preferredEngine: preferredEngine || undefined,
2579
- preferredModel: preferredModel || undefined,
2580
- silent: true,
2581
- });
2582
- }
2583
- catch (err) {
2584
- this.outputChannel.appendLine(`[Dashboard] Could not restore fleet session engine/model: ${err}`);
2585
- }
2586
- }
2587
- async startWidgetRunLog() {
2588
- const logsDir = this.getWidgetRunLogsDir();
2589
- await fs_1.promises.mkdir(logsDir, { recursive: true });
2590
- await this.trimOldWidgetRunLogs();
2591
- const runId = this.createWidgetRunId();
2592
- const logPath = path.join(logsDir, `${runId}.jsonl`);
2593
- const startedAt = new Date().toISOString();
2594
- const filters = this.getActiveFilters();
2595
- await this.appendWidgetRunLogLine(logPath, {
2596
- type: 'refresh_started',
2597
- runId,
2598
- startedAt,
2599
- scope: filters.scope,
2600
- projectId: filters.projectId ?? '',
2601
- agentId: filters.agentId ?? '',
2602
- });
2603
- this.outputChannel.appendLine(`[Dashboard] Widget refresh log: ${logPath}`);
2604
- return { runId, logPath, startedAt };
2605
- }
2606
- async trimOldWidgetRunLogs() {
2607
- const logsDir = this.getWidgetRunLogsDir();
2608
- let entries = [];
2609
- try {
2610
- const dirEntries = await fs_1.promises.readdir(logsDir, { withFileTypes: true });
2611
- entries = await Promise.all(dirEntries
2612
- .filter(entry => entry.isFile() && entry.name.endsWith('.jsonl'))
2613
- .map(async (entry) => {
2614
- const fullPath = path.join(logsDir, entry.name);
2615
- const stat = await fs_1.promises.stat(fullPath);
2616
- return { name: entry.name, fullPath, mtimeMs: stat.mtimeMs };
2617
- }));
2618
- }
2619
- catch {
2620
- return;
2621
- }
2622
- if (entries.length <= WIDGET_RUN_LOG_RETENTION_COUNT) {
2623
- return;
2624
- }
2625
- entries.sort((a, b) => b.mtimeMs - a.mtimeMs);
2626
- const stale = entries.slice(WIDGET_RUN_LOG_RETENTION_COUNT);
2627
- await Promise.all(stale.map(entry => fs_1.promises.unlink(entry.fullPath).catch(() => undefined)));
2628
- }
2629
- async finishWidgetRunLog(context, status, error) {
2630
- if (!context) {
2631
- return;
2632
- }
2633
- await this.appendWidgetRunLogLine(context.logPath, {
2634
- type: 'refresh_finished',
2635
- runId: context.runId,
2636
- status,
2637
- endedAt: new Date().toISOString(),
2638
- error: error ?? '',
2639
- });
2640
- }
2641
- async acquireDuckDbScriptSlot(script) {
2642
- if (!this.isDuckDbHeavyScript(script)) {
2643
- return;
2644
- }
2645
- while (this.activeDuckDbScriptCount >= 1) {
2646
- await new Promise(resolve => this.duckDbScriptWaiters.push(resolve));
2647
- }
2648
- this.activeDuckDbScriptCount += 1;
2649
- this.outputChannel.appendLine(`[Dashboard] DuckDB gate acquired: ${script.filename}`);
2650
- }
2651
- releaseDuckDbScriptSlot(script) {
2652
- if (!this.isDuckDbHeavyScript(script)) {
2653
- return;
2654
- }
2655
- this.activeDuckDbScriptCount = Math.max(0, this.activeDuckDbScriptCount - 1);
2656
- this.outputChannel.appendLine(`[Dashboard] DuckDB gate released: ${script.filename}`);
2657
- const next = this.duckDbScriptWaiters.shift();
2658
- next?.();
2659
- }
2660
- async runUserScripts(scripts, disabledCharts, logContext) {
2661
- this.syncArtifactContext();
2662
- const outputDir = (await this.artifactManager.ensureArtifactDir()).fsPath;
2663
- if (scripts.length === 0) {
2664
- this.outputChannel.appendLine('[Dashboard] All scripts disabled; nothing to run');
2665
- return { totalScripts: 0, failedScripts: 0 };
2666
- }
2667
- // Seed requirements.txt only when this run includes Python widgets.
2668
- if (scripts.some(script => script.filename.toLowerCase().endsWith('.py'))) {
2669
- await this.seedRequirements();
2670
- }
2671
- const env = this.buildScriptEnvironment(outputDir, disabledCharts);
2672
- const maxConcurrency = Math.max(1, Math.min(USER_SCRIPT_MAX_CONCURRENCY, scripts.length));
2673
- const failures = [];
2674
- let nextScriptIndex = 0;
2675
- const worker = async () => {
2676
- while (true) {
2677
- const currentIndex = nextScriptIndex++;
2678
- if (currentIndex >= scripts.length) {
2679
- return;
2680
- }
2681
- const script = scripts[currentIndex];
2682
- const scriptStartedAtMs = Date.now();
2683
- let acquiredDuckDbSlot = false;
2684
- let duckDbGateWaitMs = 0;
2685
- let scriptStatus = 'ok';
2686
- let scriptError = '';
2687
- let scriptStdout = '';
2688
- let scriptStderr = '';
2689
- try {
2690
- const gateWaitStartedAtMs = Date.now();
2691
- await this.acquireDuckDbScriptSlot(script);
2692
- duckDbGateWaitMs = Date.now() - gateWaitStartedAtMs;
2693
- acquiredDuckDbSlot = true;
2694
- const executionResult = await this.executeUserScript(script, env);
2695
- scriptStdout = executionResult.stdout;
2696
- scriptStderr = executionResult.stderr;
2697
- }
2698
- catch (error) {
2699
- scriptStatus = 'failed';
2700
- scriptError = error instanceof Error ? error.message : String(error);
2701
- failures.push({ script, error });
2702
- }
2703
- finally {
2704
- if (acquiredDuckDbSlot) {
2705
- this.releaseDuckDbScriptSlot(script);
2706
- }
2707
- if (logContext) {
2708
- try {
2709
- const finishedAt = new Date().toISOString();
2710
- const durationMs = Date.now() - scriptStartedAtMs;
2711
- const filters = this.getActiveFilters();
2712
- await this.appendWidgetRunLogLine(logContext.logPath, {
2713
- type: 'script_run',
2714
- runId: logContext.runId,
2715
- script: script.filename,
2716
- language: script.language,
2717
- status: scriptStatus,
2718
- error: scriptError,
2719
- stdout: scriptStdout,
2720
- stderr: scriptStderr,
2721
- durationMs,
2722
- duckDbGateWaitMs,
2723
- finishedAt,
2724
- scope: filters.scope,
2725
- projectId: filters.projectId ?? '',
2726
- agentId: filters.agentId ?? '',
2727
- });
2728
- }
2729
- catch (logErr) {
2730
- this.outputChannel.appendLine(`[Dashboard] Failed writing script run log: ${logErr}`);
2731
- }
2732
- }
2733
- // Queue artifact refresh after each script; during smooth refresh
2734
- // this is batched and emitted once at the end to avoid HTML flicker.
2735
- await this.enqueueArtifactRefresh();
2736
- }
2737
- }
2738
- };
2739
- await Promise.all(Array.from({ length: maxConcurrency }, () => worker()));
2740
- if (failures.length === scripts.length) {
2741
- throw new Error('All refresh scripts failed. Check output logs for details.');
2742
- }
2743
- if (failures.length > 0) {
2744
- this.outputChannel.appendLine(`[Dashboard] Refresh completed with ${failures.length} script failure(s)`);
2745
- for (const failure of failures) {
2746
- const message = failure.error instanceof Error ? failure.error.message : String(failure.error);
2747
- this.outputChannel.appendLine(`[Dashboard] Script failed: ${failure.script.filename}: ${message}`);
2748
- }
2749
- }
2750
- return { totalScripts: scripts.length, failedScripts: failures.length };
2751
- }
2752
- isJavascriptEsmScopeError(error) {
2753
- const stderr = typeof error.stderr === 'string'
2754
- ? error.stderr
2755
- : '';
2756
- const message = error instanceof Error ? error.message : String(error);
2757
- const haystack = [message, stderr].join('\n').toLowerCase();
2758
- return haystack.includes('require is not defined in es module scope')
2759
- || haystack.includes('module is not defined in es module scope')
2760
- || haystack.includes('exports is not defined in es module scope');
2761
- }
2762
- async runJavascriptWithCommonJsRetry(scriptPath, execOpts) {
2763
- try {
2764
- return await execFileAsync('node', [scriptPath], execOpts);
2765
- }
2766
- catch (err) {
2767
- const extension = path.extname(scriptPath).toLowerCase();
2768
- if (extension !== '.js' || !this.isJavascriptEsmScopeError(err)) {
2769
- throw err;
2770
- }
2771
- const parsed = path.parse(scriptPath);
2772
- this.outputChannel.appendLine(`[Dashboard] Retrying JavaScript script as CommonJS: ${parsed.base}`);
2773
- const sandboxRoot = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), `agentide-cjs-run-${parsed.name}-`));
2774
- const sandboxScriptsDir = path.join(sandboxRoot, 'scripts');
2775
- const sandboxScriptPath = path.join(sandboxScriptsDir, parsed.base);
2776
- try {
2777
- // Copy the whole script directory into a sandbox and force CommonJS mode there.
2778
- // This keeps relative requires (e.g. ./duckdbLocator) working on both Unix and Windows.
2779
- await fs_1.promises.cp(parsed.dir, sandboxScriptsDir, { recursive: true });
2780
- await fs_1.promises.writeFile(path.join(sandboxScriptsDir, 'package.json'), JSON.stringify({ type: 'commonjs' }), 'utf-8');
2781
- return await execFileAsync('node', [sandboxScriptPath], execOpts);
2782
- }
2783
- finally {
2784
- await fs_1.promises.rm(sandboxRoot, { recursive: true, force: true }).catch(() => undefined);
2785
- }
2786
- }
2787
- }
2788
- async executeUserScript(script, env) {
2789
- const scriptPath = script.uri.fsPath;
2790
- this.outputChannel.appendLine(`[Dashboard] Running user script: ${script.filename}`);
2791
- const execOpts = { timeout: 60_000, maxBuffer: 5 * 1024 * 1024, env };
2792
- let result;
2793
- switch (script.language) {
2794
- case 'python':
2795
- {
2796
- const deps = await this.readPythonDeps();
2797
- const runResult = await (0, pythonRunner_1.runPythonWithDeps)({
2798
- scriptPath,
2799
- args: [],
2800
- deps,
2801
- analyticsRoot: this.artifactManager.getAnalyticsRoot(),
2802
- requirementsPath: path.join(this.artifactManager.getScriptDir(), 'requirements.txt'),
2803
- execOptions: execOpts,
2804
- log: (message) => this.outputChannel.appendLine(message),
2805
- });
2806
- result = { stdout: runResult.stdout, stderr: runResult.stderr };
2807
- }
2808
- break;
2809
- case 'javascript':
2810
- result = await this.runJavascriptWithCommonJsRetry(scriptPath, execOpts);
2811
- break;
2812
- case 'shell':
2813
- result = await execFileAsync('bash', [scriptPath], execOpts);
2814
- break;
2815
- default:
2816
- throw new Error(`Unsupported script language: ${script.language}`);
2817
- }
2818
- if (result.stderr) {
2819
- this.outputChannel.appendLine(`[Dashboard] ${script.filename} stderr: ${result.stderr.slice(0, 500)}`);
2820
- }
2821
- if (result.stdout) {
2822
- this.outputChannel.appendLine(`[Dashboard] ${script.filename} output: ${result.stdout.slice(0, 300)}`);
2823
- }
2824
- return result;
2825
- }
2826
- // -------------------------------------------------------------------------
2827
- // Python Dependency Management
2828
- // -------------------------------------------------------------------------
2829
- /**
2830
- * Run a Python script with automatic dependency resolution.
2831
- * Chain: uv run --with <deps> → pip install --user + python3 → actionable error.
2832
- */
2833
- /**
2834
- * Read Python dependencies from .agent-hippo/analytics/views/<viewId>/scripts/requirements.txt.
2835
- * Returns an empty array if the file doesn't exist.
2836
- */
2837
- async readPythonDeps() {
2838
- const reqFile = path.join(this.artifactManager.getScriptDir(), 'requirements.txt');
2839
- try {
2840
- const content = await fs_1.promises.readFile(reqFile, 'utf-8');
2841
- return content
2842
- .split('\n')
2843
- .map(line => line.replace(/#.*$/, '').trim()) // strip comments
2844
- .filter(line => line.length > 0);
2845
- }
2846
- catch {
2847
- return []; // no requirements.txt
2848
- }
2849
- }
2850
- // -------------------------------------------------------------------------
2851
- // Summary Cards
2852
- // -------------------------------------------------------------------------
2853
- async sendSummaryCards(summaryOverride) {
2854
- try {
2855
- const summary = summaryOverride ?? await this.queryRunner.getCostSummary(this.getActiveFilters());
2856
- const cards = [
2857
- { label: 'Total Cost', value: `$${summary.totalCost.toFixed(2)}`, icon: '\u{1F4B0}' },
2858
- { label: 'Requests', value: summary.totalRequests.toLocaleString(), icon: '\u{1F4CA}' },
2859
- { label: 'Avg Latency', value: `${Math.round(summary.avgDurationMs)}ms`, icon: '\u26A1' },
2860
- { label: 'Top Model', value: this.formatModelName(summary.topModel), icon: '\u{1F916}' },
2861
- ];
2862
- this.webviewView?.webview.postMessage({
2863
- type: 'summaryCards',
2864
- cards,
2865
- });
2866
- }
2867
- catch (err) {
2868
- // Keep the previous summary cards on errors (for example during scope
2869
- // transitions) instead of replacing with a misleading zeroed snapshot.
2870
- this.outputChannel.appendLine(`[Dashboard] Could not load summary: ${err}`);
2871
- }
2872
- }
2873
- formatModelName(model) {
2874
- if (!model || model === 'unknown') {
2875
- return '-';
2876
- }
2877
- // Shorten model names for display
2878
- return model
2879
- .replace('claude-', '')
2880
- .replace('gpt-', '')
2881
- .replace('-20', '')
2882
- .slice(0, 12);
2883
- }
2884
- // -------------------------------------------------------------------------
2885
- // Artifact Management
2886
- // -------------------------------------------------------------------------
2887
- /**
2888
- * Fleet widget outputs `agent-fleet.html` / `agent-fleet.csv` in the active view charts dir.
2889
- * Global and Workspace should not show those cards (Fleet view only), including stale files
2890
- * left from older configs that ran the fleet script on every view.
2891
- */
2892
- filterFleetGalleryArtifactsForActiveView(artifacts) {
2893
- const id = this.getActiveViewId();
2894
- if (id !== 'global' && id !== 'workspace') {
2895
- return artifacts;
2896
- }
2897
- return artifacts.filter((a) => {
2898
- const n = a.filename.trim().toLowerCase();
2899
- return n !== 'agent-fleet.html' && n !== 'agent-fleet.csv';
2900
- });
2901
- }
2902
- async loadAndSendArtifacts() {
2903
- this.syncArtifactContext();
2904
- const [rawArtifacts, disabledConfig] = await Promise.all([
2905
- this.artifactManager.listArtifacts(),
2906
- this.loadDisabledChartsConfig(),
2907
- ]);
2908
- const artifacts = this.filterFleetGalleryArtifactsForActiveView(rawArtifacts);
2909
- this.sendArtifactsToWebview(artifacts, disabledConfig.galleryIgnore);
2910
- await this.postDisabledChartsState(disabledConfig);
2911
- // Also send manifest
2912
- const manifest = await this.artifactManager.getManifest();
2913
- if (manifest) {
2914
- this.webviewView?.webview.postMessage({
2915
- type: 'manifestUpdated',
2916
- manifest,
2917
- });
2918
- }
2919
- // Refresh any open HTML preview panels so they show the latest artifact content
2920
- // (e.g. Fleet in editor preview stays in sync when user clicks Manual Refresh in panel)
2921
- // Run after manifest so a failure here cannot block the main panel update.
2922
- try {
2923
- const artifactPaths = new Set(rawArtifacts.filter(a => a?.uri).map(a => path.resolve(a.uri.fsPath)));
2924
- for (const [normalizedPath, panel] of this.htmlPreviewPanels.entries()) {
2925
- if (artifactPaths.has(normalizedPath)) {
2926
- this.setPreviewPanelHtml(panel, normalizedPath).catch(err => {
2927
- this.outputChannel.appendLine(`[Dashboard] Failed refreshing preview for ${normalizedPath}: ${err}`);
2928
- });
2929
- }
2930
- }
2931
- }
2932
- catch (err) {
2933
- this.outputChannel.appendLine(`[Dashboard] Error refreshing open preview panels: ${err}`);
2934
- }
2935
- }
2936
- /** Path convention for view-owned snapshot exports: exports/<viewId>.json (built-in fleet: fleet.json). */
2937
- getViewExportSnapshotPath(viewId) {
2938
- const id = (viewId ?? this.getActiveViewId()).trim();
2939
- if (!id) {
2940
- return undefined;
2941
- }
2942
- const exportDir = this.artifactManager.getExportDir();
2943
- const view = id === this.getActiveViewId()
2944
- ? this.getActiveView()
2945
- : this.viewsConfig.views.find(entry => entry.id === id);
2946
- if (id === 'fleet' || (view && (0, viewsConfigMerge_1.viewRunsFleetWidget)(view))) {
2947
- return path.join(exportDir, AnalyticsDashboardProvider.FLEET_EXPORT_FILENAME);
2948
- }
2949
- return path.join(exportDir, `${id}.json`);
2950
- }
2951
- /** Resolve fleet export JSON for read (prefers fleet.json, falls back to legacy agent-fleet.json). */
2952
- resolveFleetExportReadPath() {
2953
- const exportDir = this.artifactManager.getExportDir();
2954
- const primary = path.join(exportDir, AnalyticsDashboardProvider.FLEET_EXPORT_FILENAME);
2955
- if ((0, fs_1.existsSync)(primary)) {
2956
- return primary;
2957
- }
2958
- const legacy = path.join(exportDir, AnalyticsDashboardProvider.LEGACY_FLEET_EXPORT_FILENAME);
2959
- return (0, fs_1.existsSync)(legacy) ? legacy : primary;
2960
- }
2961
- /** Active view opts into iframe live updates via exports/<viewId>.json snapshots. */
2962
- activeViewPushesExportSnapshot() {
2963
- return (0, viewsConfigMerge_1.shouldPreserveFleetIframe)(this.getActiveView());
2964
- }
2965
- /**
2966
- * Push export JSON to the panel when the active view has preserveFleetIframe.
2967
- * Forwards the full parsed exports/<viewId>.json (generic passthrough); widgets normalize shape.
2968
- * Large session[] arrays are chunked for iframe delivery.
2969
- */
2970
- async postViewExportSnapshot(options) {
2971
- if (!this.webviewView) {
2972
- return;
2973
- }
2974
- const allowSnapshot = this.activeViewPushesExportSnapshot()
2975
- || options?.respondWhenMissing === true;
2976
- if (!allowSnapshot) {
2977
- return;
2978
- }
2979
- const exportPath = this.getViewExportSnapshotPath();
2980
- if (!exportPath) {
2981
- return;
2982
- }
2983
- const readPath = exportPath.endsWith(AnalyticsDashboardProvider.FLEET_EXPORT_FILENAME)
2984
- ? this.resolveFleetExportReadPath()
2985
- : exportPath;
2986
- if (!options?.respondWhenMissing && !(0, fs_1.existsSync)(readPath)) {
2987
- return;
2988
- }
2989
- try {
2990
- const raw = await fs_1.promises.readFile(readPath, 'utf-8');
2991
- const parsed = JSON.parse(raw);
2992
- const messages = (0, viewsConfigMerge_1.buildViewExportSnapshotMessages)(parsed);
2993
- for (const message of messages) {
2994
- this.webviewView.webview.postMessage({
2995
- type: viewsConfigMerge_1.VIEW_EXPORT_SNAPSHOT_MESSAGE,
2996
- payload: message.payload,
2997
- meta: message.meta,
2998
- });
2999
- }
3000
- }
3001
- catch (err) {
3002
- const isEnoent = err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT';
3003
- if (!options?.respondWhenMissing && isEnoent) {
3004
- return;
3005
- }
3006
- this.webviewView.webview.postMessage({
3007
- type: viewsConfigMerge_1.VIEW_EXPORT_SNAPSHOT_MESSAGE,
3008
- payload: {
3009
- generatedAt: '',
3010
- totals: {},
3011
- },
3012
- meta: {
3013
- ok: isEnoent,
3014
- sessionCount: 0,
3015
- error: err instanceof Error ? err.message : String(err),
3016
- cliActive: false,
3017
- daemonHint: false,
3018
- },
3019
- });
3020
- }
3021
- }
3022
- sendArtifactsToWebview(artifacts, galleryIgnorePatterns) {
3023
- if (!this.webviewView) {
3024
- return;
3025
- }
3026
- const webviewArtifacts = artifacts.map(a => ({
3027
- // Per-artifact cache bust using modifiedAt so only changed images reload;
3028
- // unchanged charts keep their cached URL and don't flicker.
3029
- webviewUri: this.webviewView.webview.asWebviewUri(a.uri).toString() + `?v=${a.modifiedAt.getTime()}`,
3030
- localPath: a.uri.fsPath,
3031
- filename: a.filename,
3032
- size: a.size,
3033
- modifiedAt: a.modifiedAt.toISOString(),
3034
- type: a.type,
3035
- format: a.format,
3036
- generator: a.generator,
3037
- }));
3038
- const activeViewId = this.getActiveViewId();
3039
- this.webviewView.webview.postMessage({
3040
- type: 'artifactsUpdated',
3041
- artifacts: webviewArtifacts,
3042
- activeViewId,
3043
- fleetRefreshVersion: activeViewId === 'fleet' ? this.fleetRefreshVersion : undefined,
3044
- galleryIgnorePatterns,
3045
- });
3046
- void this.postViewExportSnapshot();
3047
- }
3048
- async postArtifactContent(pathValue, format) {
3049
- const targetPath = pathValue?.trim();
3050
- if (!targetPath || !this.webviewView) {
3051
- return;
3052
- }
3053
- const normalizedTarget = path.resolve(targetPath);
3054
- if (!this.isPathWithinAllowedAnalyticsRoots(normalizedTarget)) {
3055
- this.webviewView.webview.postMessage({
3056
- type: 'artifactContent',
3057
- path: targetPath,
3058
- format: format ?? 'html',
3059
- error: 'Access denied for preview path.',
3060
- });
3061
- return;
3062
- }
3063
- try {
3064
- const stat = await fs_1.promises.stat(normalizedTarget);
3065
- if (!stat.isFile()) {
3066
- throw new Error('Preview target is not a file.');
3067
- }
3068
- if (stat.size > 2 * 1024 * 1024) {
3069
- throw new Error('Preview file is too large (>2MB).');
3070
- }
3071
- const content = await fs_1.promises.readFile(normalizedTarget, 'utf-8');
3072
- this.webviewView.webview.postMessage({
3073
- type: 'artifactContent',
3074
- path: targetPath,
3075
- format: format ?? 'html',
3076
- content,
3077
- });
3078
- }
3079
- catch (err) {
3080
- this.outputChannel.appendLine(`[Dashboard] Failed loading artifact preview for ${normalizedTarget}: ${err}`);
3081
- this.webviewView.webview.postMessage({
3082
- type: 'artifactContent',
3083
- path: targetPath,
3084
- format: format ?? 'html',
3085
- error: err instanceof Error ? err.message : String(err),
3086
- });
3087
- }
3088
- }
3089
- async openHtmlArtifactPreview(pathValue) {
3090
- const targetPath = pathValue?.trim();
3091
- if (!targetPath) {
3092
- return;
3093
- }
3094
- const normalizedTarget = path.resolve(targetPath);
3095
- let panel = this.htmlPreviewPanels.get(normalizedTarget);
3096
- if (!panel) {
3097
- panel = vscode.window.createWebviewPanel('agentide.analyticsHtmlPreview', `Preview: ${path.basename(normalizedTarget)}`, vscode.ViewColumn.Active, {
3098
- enableScripts: true,
3099
- retainContextWhenHidden: true,
3100
- localResourceRoots: [
3101
- vscode.Uri.file(path.dirname(normalizedTarget)),
3102
- ],
3103
- });
3104
- this.htmlPreviewPanels.set(normalizedTarget, panel);
3105
- panel.onDidDispose(() => {
3106
- this.htmlPreviewPanels.delete(normalizedTarget);
3107
- });
3108
- // Forward widget messages (e.g. openSpotlightForSession) from preview to extension
3109
- panel.webview.onDidReceiveMessage(this.handleMessage.bind(this));
3110
- }
3111
- else {
3112
- panel.reveal(vscode.ViewColumn.Active);
3113
- }
3114
- await this.setPreviewPanelHtml(panel, normalizedTarget);
3115
- }
3116
- /** Load artifact HTML from disk and set it on the preview panel (used for open and for refresh). */
3117
- async setPreviewPanelHtml(panel, normalizedTarget) {
3118
- const nonce = getNonce();
3119
- try {
3120
- const rawHtml = await fs_1.promises.readFile(normalizedTarget, 'utf-8');
3121
- const withNonce = rawHtml.replace(/<script(?![^>]*\bnonce=)([^>]*)>/gi, `<script nonce="${nonce}"$1>`);
3122
- const csp = `<meta http-equiv="Content-Security-Policy" content="
1
+ /*! Copyright (c) 2026 AgentHippo.ai. All rights reserved. Proprietary; see LICENSE. */
2
+ "use strict";var __createBinding=this&&this.__createBinding||(Object.create?(function(m,e,t,i){i===void 0&&(i=t);var s=Object.getOwnPropertyDescriptor(e,t);(!s||("get"in s?!e.__esModule:s.writable||s.configurable))&&(s={enumerable:!0,get:function(){return e[t]}}),Object.defineProperty(m,i,s)}):(function(m,e,t,i){i===void 0&&(i=t),m[i]=e[t]})),__setModuleDefault=this&&this.__setModuleDefault||(Object.create?(function(m,e){Object.defineProperty(m,"default",{enumerable:!0,value:e})}):function(m,e){m.default=e}),__importStar=this&&this.__importStar||(function(){var m=function(e){return m=Object.getOwnPropertyNames||function(t){var i=[];for(var s in t)Object.prototype.hasOwnProperty.call(t,s)&&(i[i.length]=s);return i},m(e)};return function(e){if(e&&e.__esModule)return e;var t={};if(e!=null)for(var i=m(e),s=0;s<i.length;s++)i[s]!=="default"&&__createBinding(t,e,i[s]);return __setModuleDefault(t,e),t}})();Object.defineProperty(exports,"__esModule",{value:!0}),exports.AnalyticsDashboardProvider=exports.shouldRunScriptsOnNavigation=void 0;const vscode=__importStar(require("vscode")),path=__importStar(require("path")),os=__importStar(require("os")),fs_1=require("fs"),child_process_1=require("child_process"),util_1=require("util"),pythonRunner_1=require("../../utils/pythonRunner"),toolPath_1=require("../../utils/toolPath"),index_1=require("../../utils/index"),fleetOrchestrator_1=require("./fleetOrchestrator"),fleetDisplayUtils_1=require("./fleetDisplayUtils"),fleetRowDefaults_1=require("./fleetRowDefaults"),fleetTypes_1=require("./fleetTypes"),viewsConfigMerge_1=require("./viewsConfigMerge"),teamViewDiscovery_1=require("./teamViewDiscovery"),pathSafety_1=require("../../utils/pathSafety"),execFileAsync=(0,util_1.promisify)(child_process_1.execFile);var viewsConfigMerge_2=require("./viewsConfigMerge");Object.defineProperty(exports,"shouldRunScriptsOnNavigation",{enumerable:!0,get:function(){return viewsConfigMerge_2.shouldRunScriptsOnNavigation}});function postToWebview(m,e){m&&m.postMessage(e)}const FILTER_AUTOGEN_DEBOUNCE_MS=700,FILTER_AUTOGEN_COOLDOWN_MS=12e3,MAX_FILTER_AUTOGEN_HISTORY=100,DEFAULT_SEED_REGISTRY_FILENAME="default-seed-scripts.json",FALLBACK_DEFAULT_SEED_SCRIPT_NAMES=["generate-default-charts.py","duckdbLocator.js","wow-model-widget.js","session-timeline-widget.js","session-comparison-widget.js","hotspot-analysis-widget.js","token-cost-flamegraph-widget.js"],USER_SCRIPT_MAX_CONCURRENCY=2,WIDGET_RUN_LOG_RETENTION_COUNT=20,VIEWS_CONFIG_DEBOUNCE_MS=400;class AnalyticsDashboardProvider{extensionContext;queryRunner;artifactManager;outputChannel;getIndexingService;viewIdSuffix;static viewType="agentide.analyticsView";webviewView;artifactWatcher;viewsConfigWatcher;viewsConfigDebounceTimeout;generationAbortController;isBatchingArtifactUpdates=!1;pendingArtifactRefresh=!1;artifactRefreshQueue=Promise.resolve();pendingFilterAutoGenerateTimer;isRefreshInProgress=!1;hasPendingRefreshRequest=!1;activeDuckDbScriptCount=0;duckDbScriptWaiters=[];duckDbHeavyScriptNames=new Set(["generate-default-charts.py","wow-model-widget.js","session-timeline-widget.js","session-comparison-widget.js","agent-fleet-widget.js"]);static FLEET_EXPORT_FILENAME="fleet.json";static LEGACY_FLEET_EXPORT_FILENAME="agent-fleet.json";static DEFAULT_GALLERY_IGNORE=[".json",".csv"];fleetRefreshVersion=0;autoGenerationAttemptByFilter=new Map;htmlPreviewPanels=new Map;viewsConfig=this.createDefaultViewsConfig();agentFilterId;disposables=[];viewConfigMutationQueue=Promise.resolve();constructor(e,t,i,s,a,r){this.extensionContext=e,this.queryRunner=t,this.artifactManager=i,this.outputChannel=s,this.getIndexingService=a,this.viewIdSuffix=r}indexingService(){return this.getIndexingService?.()}resolveWebviewView(e,t,i){this.webviewView=e,this.syncArtifactContext(),e.webview.options={enableScripts:!0,localResourceRoots:[this.extensionContext.extensionUri,vscode.Uri.file(this.artifactManager.getAnalyticsRoot()),vscode.Uri.file(this.artifactManager.getGlobalAnalyticsRoot())]},e.webview.html=this.getHtmlContent(e.webview),this.disposables.push(e.webview.onDidReceiveMessage(this.handleMessage.bind(this))),this.artifactWatcher=this.artifactManager.watchForChanges(r=>{if(this.isBatchingArtifactUpdates){this.pendingArtifactRefresh=!0;return}this.loadDisabledChartsConfig().then(n=>{this.sendArtifactsToWebview(r,n.galleryIgnore)})}),this.disposables.push(this.artifactWatcher);const s=()=>{this.viewsConfigDebounceTimeout&&clearTimeout(this.viewsConfigDebounceTimeout),this.viewsConfigDebounceTimeout=setTimeout(()=>{this.handleViewsConfigChanged()},VIEWS_CONFIG_DEBOUNCE_MS)};for(const r of this.getViewsConfigPathsToWatch()){const n=vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(path.dirname(r),path.basename(r)));this.disposables.push(n,n.onDidChange(s),n.onDidCreate(s),n.onDidDelete(s))}const a=vscode.workspace.getConfiguration("agentide.analytics").get("artifactRetentionDays",30);this.artifactManager.cleanup(a).then(r=>{r.deletedCount>0&&this.outputChannel.appendLine(`[Dashboard] Cleaned up ${r.deletedCount} old artifacts (${(r.freedBytes/1024).toFixed(1)} KB)`)}),this.disposables.push(e.onDidChangeVisibility(()=>{e.visible&&this.onPanelBecameVisible()})),this.onPanelBecameVisible()}resolveWorkspaceRootPath(){const e=vscode.window.activeTextEditor?.document.uri;return((e?vscode.workspace.getWorkspaceFolder(e):void 0)??vscode.workspace.workspaceFolders?.[0])?.uri.fsPath}resolveWorkspaceProjectId(){const e=this.resolveWorkspaceRootPath();if(e){const r=path.basename(e).trim();if(r)return r}const t=vscode.window.activeTextEditor?.document.uri,s=(t?vscode.workspace.getWorkspaceFolder(t):void 0)??vscode.workspace.workspaceFolders?.[0];if(s?.uri.fsPath){const r=path.basename(s.uri.fsPath).trim();if(r)return r}return vscode.workspace.name?.trim()||void 0}syncArtifactContext(){this.artifactManager.setWorkspaceRoot(this.resolveWorkspaceRootPath());const e=this.getActiveFilters();this.artifactManager.setViewContext({scope:e.scope==="workspace"?"workspace":"global",projectId:e.projectId,agentId:e.agentId,viewId:this.getActiveViewId()})}async withArtifactViewContext(e,t){this.artifactManager.setWorkspaceRoot(this.resolveWorkspaceRootPath());const i=this.getActiveView(),s=this.getViewContext(i);this.artifactManager.setViewContext(this.getViewContext(e));try{return await t()}finally{this.artifactManager.setViewContext(s)}}runViewConfigMutation(e){const t=this.viewConfigMutationQueue.then(e,e);return this.viewConfigMutationQueue=t.then(()=>{},()=>{}),t}static FLEET_WIDGET_BASENAME="agent-fleet-widget.js";stripFleetWidgetFromGlobalWorkspace(e,t){const i=this.normalizeViewId(e);return i!=="global"&&i!=="workspace"?t:t.filter(s=>this.normalizeBasename(s)!==AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME)}normalizeViewId(e,t="global"){return(0,viewsConfigMerge_1.normalizeDashboardViewId)(e,t)}getViewsConfigFilename(){return this.viewIdSuffix?`views-${this.viewIdSuffix}.yaml`:"views.yaml"}getGlobalViewsConfigPath(){return path.join(this.artifactManager.getGlobalAnalyticsRoot(),"panel",this.getViewsConfigFilename())}getWorkspaceViewsConfigPath(){const e=this.resolveWorkspaceRootPath();if(e)return path.join(e,".agent-hippo","analytics","panel",this.getViewsConfigFilename())}async readPanelConfigLayers(e=this.getViewsConfigFilename()){const t=path.join(this.artifactManager.getGlobalAnalyticsRoot(),"panel",e),i=this.resolveWorkspaceRootPath(),s=i?path.join(i,".agent-hippo","analytics","panel",e):void 0;return Promise.all([this.readViewsConfigFile(t),s?this.readViewsConfigFile(s):Promise.resolve(void 0)])}getViewsConfigPathsToWatch(){const e=[this.getGlobalViewsConfigPath()],t=this.getWorkspaceViewsConfigPath();return t&&t!==e[0]&&e.push(t),e}getAllowedAnalyticsRoots(){const e=[path.resolve(this.artifactManager.getGlobalAnalyticsRoot()),path.resolve(this.artifactManager.getAnalyticsRoot())];return[...new Set(e)]}isPathWithinAllowedAnalyticsRoots(e){return(0,pathSafety_1.isPathUnderAnyRoot)(this.getAllowedAnalyticsRoots(),e)}isTeamSurface(){return this.viewIdSuffix==="team"}getSurfaceMode(){return this.isTeamSurface()?"team":"spotlight"}async hydrateView(e,t){const i=t??e,s=await this.readViewMetadata(e.id,e.scope);let a;if(s){const r=this.normalizeViewId(s.id,e.id);let n=this.dedupeBasenames(s.scripts.length?s.scripts:e.scripts);n=r==="fleet"?[AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME]:this.stripFleetWidgetFromGlobalWorkspace(r,n),a={id:r,name:s.name,scope:s.scope,scripts:n,system:e.system===!0||s.system===!0}}else{const r=e.id==="fleet"?{...e,scripts:[AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME]}:e;a={...r,scripts:this.stripFleetWidgetFromGlobalWorkspace(r.id,r.scripts)}}return{...a,refreshOnNavigate:i.refreshOnNavigate,layout:i.layout,preserveFleetIframe:i.preserveFleetIframe}}async buildTeamViewsConfig(e,t){const i=new Map(t.views.map(r=>[r.id,r])),s=await(0,teamViewDiscovery_1.discoverTeamTaggedViews)(this.getAllowedAnalyticsRoots()),a=[{id:"fleet",name:"Fleet",scope:"global",scripts:[AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME],system:!0},(0,viewsConfigMerge_1.createDefaultWorkflowsView)()];for(const r of s){const n=i.get(r.id);a.push(await this.hydrateView({id:r.id,name:r.name,scope:r.scope,scripts:n?.scripts??[],system:!1},n))}return this.finalizeTeamViewsConfig(a,e)}async loadTeamViewsConfig(){const[[e,t],[i,s]]=await Promise.all([this.readPanelConfigLayers(),this.readPanelConfigLayers("views.yaml")]),a=this.mergeViewsConfigs(i,s),r=this.normalizeViewId(t?.activeViewId??e?.activeViewId,"fleet");return this.buildTeamViewsConfig(r,a)}finalizeTeamViewsConfig(e,t){const i=e.map(s=>({...s,id:this.normalizeViewId(s.id,"fleet"),scripts:s.id==="fleet"?[AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME]:this.stripFleetWidgetFromGlobalWorkspace(s.id,this.dedupeBasenames(s.scripts))}));return i.some(s=>s.id==="fleet")||i.unshift({id:"fleet",name:"Fleet",scope:"global",scripts:[AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME],system:!0}),{version:1,activeViewId:this.resolvePersistedActiveId(i,t,"fleet"),views:i}}getViewMetadataPath(e,t){const i={scope:t,projectId:this.resolveWorkspaceProjectId(),agentId:this.agentFilterId,viewId:e};return path.join(this.artifactManager.resolveViewRoot(i),"view.yaml")}async readViewMetadata(e,t){const i=this.getViewMetadataPath(e,t);try{const s=await fs_1.promises.readFile(i,"utf-8"),r=require("js-yaml").load(s);if(!r||typeof r!="object")return;const n=new Date().toISOString(),c=r.version;return{version:typeof c=="string"&&c.trim()?c.trim():typeof c=="number"?c:1,id:this.normalizeViewId(typeof r.id=="string"?r.id:e,e),name:typeof r.name=="string"&&r.name.trim()?r.name.trim():e,scope:r.scope==="workspace"?"workspace":"global",projectId:typeof r.projectId=="string"?r.projectId:"",agentId:typeof r.agentId=="string"?r.agentId:"",createdAt:typeof r.createdAt=="string"&&r.createdAt.trim()?r.createdAt:n,updatedAt:typeof r.updatedAt=="string"&&r.updatedAt.trim()?r.updatedAt:n,scripts:this.dedupeBasenames(this.parseStringArray(r.scripts)),system:r.system===!0,description:typeof r.description=="string"?r.description.trim():void 0,author:typeof r.author=="string"?r.author.trim():void 0,tags:Array.isArray(r.tags)?r.tags.filter(o=>typeof o=="string"):void 0}}catch{return}}async writeViewMetadata(e,t){const i=this.getViewMetadataPath(e.id,e.scope);await fs_1.promises.mkdir(path.dirname(i),{recursive:!0});const s=new Date().toISOString(),a=await this.readViewMetadata(e.id,e.scope),r=e.id==="fleet"?["agent-fleet-widget.js"]:this.dedupeBasenames(e.scripts),n={version:a?.version??"1.0.0",id:e.id,name:e.name,scope:e.scope,projectId:this.resolveWorkspaceProjectId()??"",agentId:this.agentFilterId??"",createdAt:t??a?.createdAt??s,updatedAt:s,scripts:r,system:e.system===!0,...a?.description?{description:a.description}:{},...a?.author?{author:a.author}:{},...a?.tags?.length?{tags:a.tags}:{}},d=require("js-yaml").dump(n,{lineWidth:120,noRefs:!0});await fs_1.promises.writeFile(i,d,"utf-8")}getViewContext(e){return{scope:e.scope,projectId:this.resolveWorkspaceProjectId(),agentId:this.agentFilterId,viewId:e.id}}getViewScriptsDir(e){return path.join(this.artifactManager.resolveViewRoot(this.getViewContext(e)),"scripts")}async ensureViewOwnedScripts(e){const t=this.dedupeBasenames([...e.scripts,...e.bundledScripts??[]]);if(t.length===0)return;const i=this.getViewScriptsDir(e);await fs_1.promises.mkdir(i,{recursive:!0});for(const s of t){const a=path.join(i,s);try{await fs_1.promises.access(a);continue}catch{}const r=await this.findBundledAnalyticsScript(s);if(!r){this.outputChannel.appendLine(`[Dashboard] Could not seed script "${s}" for view "${e.id}" (bundled script not found)`);continue}try{await fs_1.promises.copyFile(r,a),this.outputChannel.appendLine(`[Dashboard] Seeded missing view script "${s}" for view "${e.id}"`)}catch(n){this.outputChannel.appendLine(`[Dashboard] Failed seeding script "${s}" for view "${e.id}": ${n}`)}}}async ensureViewOwnedDirectories(e){const t=this.artifactManager.resolveViewRoot(this.getViewContext(e));for(const i of[path.join(t,"scripts"),path.join(t,"charts"),path.join(t,"reports"),path.join(t,"exports"),path.join(t,"images"),path.join(t,"assets")])await fs_1.promises.mkdir(i,{recursive:!0})}async ensureViewsFilesystemState(){for(const e of this.viewsConfig.views)await this.ensureViewOwnedDirectories(e),await this.ensureViewOwnedScripts(e),await this.writeViewMetadata(e)}async copyViewFiles(e,t,i){const s=this.artifactManager.resolveViewRoot({scope:e.scope,projectId:this.resolveWorkspaceProjectId(),agentId:this.agentFilterId,viewId:e.id}),a=this.artifactManager.resolveViewRoot({scope:t.scope,projectId:this.resolveWorkspaceProjectId(),agentId:this.agentFilterId,viewId:t.id});for(const r of i){const n=path.join(s,r),c=path.join(a,r);await fs_1.promises.mkdir(c,{recursive:!0});let d=[];try{d=await fs_1.promises.readdir(n)}catch{continue}for(const o of d){const l=path.join(n,o),p=path.join(c,o);try{if(!(await fs_1.promises.stat(l)).isFile())continue;await fs_1.promises.copyFile(l,p)}catch{}}}}createDefaultViewsConfig(){return{version:1,activeViewId:this.viewIdSuffix==="team"?"fleet":"global",views:[{id:"global",name:"Global",scope:"global",scripts:[],system:!0},{id:"workspace",name:"Workspace",scope:"workspace",scripts:[],system:!0},{id:"fleet",name:"Fleet",scope:"global",scripts:["agent-fleet-widget.js"],system:!0},(0,viewsConfigMerge_1.createDefaultWorkflowsView)()]}}normalizeViewsConfig(e,t){const i=e&&typeof e=="object"?e:{},s=Array.isArray(i.views)?i.views:[],a=[],r=new Set;for(const l of s){if(!l||typeof l!="object")continue;const p=l,h=this.normalizeViewId(typeof p.id=="string"?p.id:void 0,"view");if(!h||r.has(h))continue;r.add(h);const u=p.scope==="workspace"?"workspace":"global",f=typeof p.name=="string"&&p.name.trim()?p.name.trim():h,g=this.stripFleetWidgetFromGlobalWorkspace(h,this.dedupeBasenames(this.parseStringArray(p.scripts))),w=this.dedupeBasenames(this.parseStringArray(p.bundledScripts)),y=p.system===!0,v=p.refreshOnNavigate==="always"||p.refreshOnNavigate==="when-empty"?p.refreshOnNavigate:void 0,b=p.layout==="fleet-dashboard"||p.layout==="gallery"?p.layout:void 0,C=p.preserveFleetIframe===!0?!0:p.preserveFleetIframe===!1?!1:void 0;a.push({id:h,name:f,scope:u,scripts:g,bundledScripts:w.length?w:void 0,system:y,refreshOnNavigate:v,layout:b,preserveFleetIframe:C})}a.some(l=>l.id==="global")||a.unshift({id:"global",name:"Global",scope:"global",scripts:[],system:!0}),a.some(l=>l.id==="workspace")||a.push({id:"workspace",name:"Workspace",scope:"workspace",scripts:[],system:!0}),a.some(l=>l.id==="fleet")||a.push({id:"fleet",name:"Fleet",scope:"global",scripts:["agent-fleet-widget.js"],system:!0}),a.some(l=>l.id===viewsConfigMerge_1.WORKFLOWS_VIEW_ID)||a.push((0,viewsConfigMerge_1.createDefaultWorkflowsView)());const n=this.createDefaultViewsConfig(),c=typeof i.activeViewId=="string"?i.activeViewId:n.activeViewId,d=this.normalizeViewId(c,n.activeViewId);return{version:1,activeViewId:a.some(l=>l.id===d)||t?.allowOrphanActiveId?d:n.activeViewId,views:a}}async readViewsConfigFile(e){try{const t=await fs_1.promises.readFile(e,"utf-8"),i=require("js-yaml");return this.normalizeViewsConfig(i.load(t),{allowOrphanActiveId:!0})}catch{return}}mergeViewsConfigs(e,t){const i=this.createDefaultViewsConfig(),s=(0,viewsConfigMerge_1.mergeDashboardViewsConfigs)(e,t,i);return this.normalizeViewsConfig({version:1,activeViewId:s.activeViewId,views:s.views})}async handleViewsConfigChanged(){const e=this.getActiveViewId();await this.loadViewsConfig(),this.webviewView?.visible&&(await this.postViewsState(),await this.postPanelState("default"),this.getActiveViewId()!==e&&(await this.refreshAfterViewChange(),await this.refreshChartsForActiveViewAfterSwitch()))}async loadViewsConfig(){if(this.isTeamSurface()){const d=this.getGlobalViewsConfigPath();let o=!1;try{await fs_1.promises.access(d)}catch{o=!0}return this.viewsConfig=await this.loadTeamViewsConfig(),o&&await this.saveViewsConfig(this.viewsConfig),this.viewsConfig}const e=this.getWorkspaceViewsConfigPath(),[t,i]=await this.readPanelConfigLayers();let s=!t&&!i,a=this.mergeViewsConfigs(t,i);if(s){const d=this.createDefaultViewsConfig(),o=await this.artifactManager.listScripts({disabledScripts:[]}),l=this.dedupeBasenames(o.map(h=>h.filename)),p=AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME;a={...d,views:d.views.map(h=>({...h,scripts:h.scripts.length?h.scripts:l.filter(u=>h.id==="fleet"?this.normalizeBasename(u)===p:this.normalizeBasename(u)!==p)}))}}const r=[];for(const d of a.views)r.push(await this.hydrateView(d));const n=this.normalizeViewsConfig({version:1,activeViewId:a.activeViewId,views:r});return this.viewsConfig=n,(s||this.shouldHealWorkspaceActiveViewId(i,e,a.activeViewId))&&await this.saveViewsConfig(this.viewsConfig),this.viewsConfig}resolvePersistedActiveId(e,t,i){const s=this.normalizeViewId(t,i);return e.some(a=>a.id===s)?s:i}shouldHealWorkspaceActiveViewId(e,t,i){if(!t||!e)return!1;const s=this.normalizeViewId(e.activeViewId,""),a=this.normalizeViewId(i,"");return s.length>0&&s!==a}async saveViewsConfig(e){const t=this.normalizeViewsConfig(e),i=require("js-yaml");if(this.isTeamSurface()){const f=this.resolvePersistedActiveId(t.views,t.activeViewId,"fleet"),g={version:1,activeViewId:f,views:[{id:"fleet",name:"Fleet",scope:"global",scripts:[AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME],system:!0}]},w=this.getGlobalViewsConfigPath();await fs_1.promises.mkdir(path.dirname(w),{recursive:!0}),await fs_1.promises.writeFile(w,i.dump(g,{lineWidth:120,noRefs:!0}),"utf-8");const y=this.getWorkspaceViewsConfigPath();y&&(await fs_1.promises.mkdir(path.dirname(y),{recursive:!0}),await fs_1.promises.writeFile(y,i.dump({version:1,activeViewId:f,views:[]},{lineWidth:120,noRefs:!0}),"utf-8")),this.viewsConfig=t,await this.ensureViewsFilesystemState();return}const s=t.views.filter(u=>u.scope!=="workspace"),a=t.views.filter(u=>u.scope==="workspace"),r="global",n=this.normalizeViewId(t.activeViewId,r),c=t.views.some(u=>u.id===n)?n:r,d=this.getGlobalViewsConfigPath(),o=this.getWorkspaceViewsConfigPath(),[l]=await Promise.all([this.readViewsConfigFile(d),o?this.readViewsConfigFile(o):Promise.resolve(void 0)]),h={version:1,activeViewId:o?this.resolvePersistedActiveId(s,l?.activeViewId??r,r):c,views:s};if(await fs_1.promises.mkdir(path.dirname(d),{recursive:!0}),await fs_1.promises.writeFile(d,i.dump(h,{lineWidth:120,noRefs:!0}),"utf-8"),o){const u={version:1,activeViewId:c,views:a};await fs_1.promises.mkdir(path.dirname(o),{recursive:!0}),await fs_1.promises.writeFile(o,i.dump(u,{lineWidth:120,noRefs:!0}),"utf-8")}this.viewsConfig=t,await this.ensureViewsFilesystemState()}getActiveViewId(){const e=this.isTeamSurface()?"fleet":"global",t=this.normalizeViewId(this.viewsConfig.activeViewId,e);return this.viewsConfig.views.some(i=>i.id===t)?t:e}getActiveView(){const e=this.getActiveViewId();return this.viewsConfig.views.find(t=>t.id===e)??this.createDefaultViewsConfig().views[0]}findViewById(e){const t=this.normalizeViewId(e);return this.viewsConfig.views.find(i=>i.id===t)}async postViewsState(){if(!this.viewsConfig.views.some(i=>i.id==="fleet")){const i={id:"fleet",name:"Fleet",scope:"global",scripts:["agent-fleet-widget.js"],system:!0};this.viewsConfig={...this.viewsConfig,views:[...this.viewsConfig.views,i]},await this.ensureViewOwnedDirectories(i),await this.writeViewMetadata(i),await this.saveViewsConfig(this.viewsConfig)}if(!this.isTeamSurface()&&!this.viewsConfig.views.some(i=>i.id===viewsConfigMerge_1.WORKFLOWS_VIEW_ID)){const i=(0,viewsConfigMerge_1.createDefaultWorkflowsView)();this.viewsConfig={...this.viewsConfig,views:[...this.viewsConfig.views,i]},await this.ensureViewOwnedDirectories(i),await this.ensureViewOwnedScripts(i),await this.writeViewMetadata(i),await this.saveViewsConfig(this.viewsConfig)}const e=this.getActiveViewId(),t=this.viewsConfig.views.map(i=>({id:i.id,name:i.name,scope:i.scope,system:i.system===!0,layout:(0,viewsConfigMerge_1.resolveGalleryLayout)(i),refreshOnNavigate:(0,viewsConfigMerge_1.resolveRefreshOnNavigate)(i),preserveFleetIframe:(0,viewsConfigMerge_1.resolvePreserveFleetIframe)(i)}));postToWebview(this.webviewView?.webview,{type:"viewsLoaded",activeViewId:e,views:t,surfaceMode:this.getSurfaceMode()})}async loadAndPostViewsState(){this.viewsConfig=await this.loadViewsConfig(),await this.postViewsState()}async refreshViewsFromDisk(){await this.loadAndPostViewsState()}async setActiveView(e){const t=this.normalizeViewId(e);return this.viewsConfig.views.some(i=>i.id===t)?(this.viewsConfig.activeViewId===t||(this.viewsConfig={...this.viewsConfig,activeViewId:t},await this.saveViewsConfig(this.viewsConfig)),!0):!1}async openWithView(e){await this.loadViewsConfig();const t=await this.setActiveView(e);this.webviewView?.visible&&(await this.postViewsState(),t&&(this.syncArtifactContext(),await this.refreshAfterViewChange(),await this.refreshChartsForActiveViewAfterSwitch()))}allocateViewId(e,t="view"){const i=this.normalizeViewId(e,t);let s=i,a=2;for(;this.viewsConfig.views.some(r=>r.id===s);)s=`${i}-${a++}`;return s}async createView(e){const t=e.trim();if(!t)return;const i=this.allocateViewId(t,"view"),s=this.getActiveView(),a={id:i,name:t,scope:s.scope,scripts:[],system:!1};return await this.ensureViewOwnedDirectories(a),await this.copyViewFiles(s,a,["assets","images"]),this.viewsConfig={...this.viewsConfig,activeViewId:a.id,views:[...this.viewsConfig.views,a]},await this.saveViewsConfig(this.viewsConfig),a}async renameView(e,t){const i=this.findViewById(e);if(!i||i.system)return!1;const s=t.trim();return s?(this.viewsConfig={...this.viewsConfig,views:this.viewsConfig.views.map(a=>a.id===i.id?{...a,name:s}:a)},await this.saveViewsConfig(this.viewsConfig),!0):!1}async duplicateView(e){const t=this.findViewById(e);if(!t)return;const i=`${t.name} Copy`,s=this.allocateViewId(i,"view"),a=(0,viewsConfigMerge_1.formatDisplayNameForAllocatedViewId)(i,s,(c,d)=>this.normalizeViewId(c,d)),r=await this.resolveScriptBasenamesForDuplicate(t),n={id:s,name:a,scope:t.scope,scripts:r,system:!1};return await this.ensureViewOwnedDirectories(n),await this.copyViewFiles(t,n,["scripts","assets","images","charts","reports","exports"]),this.viewsConfig={...this.viewsConfig,activeViewId:n.id,views:[...this.viewsConfig.views,n]},await this.saveViewsConfig(this.viewsConfig),n}async deleteView(e){const t=this.findViewById(e);if(!t||t.system)return!1;const i=this.getActiveViewId()===t.id?"global":this.getActiveViewId();this.viewsConfig={...this.viewsConfig,activeViewId:i,views:this.viewsConfig.views.filter(r=>r.id!==t.id)};const s=this.artifactManager.resolveViewRoot({scope:t.scope,projectId:this.resolveWorkspaceProjectId(),agentId:this.agentFilterId,viewId:t.id});await fs_1.promises.rm(s,{recursive:!0,force:!0}).catch(()=>{});const a=path.join(this.artifactManager.getAnalyticsRoot(),"panel-state",`default--${t.id}.json`);return await fs_1.promises.rm(a,{force:!0}).catch(()=>{}),await this.saveViewsConfig(this.viewsConfig),!0}async editViewScripts(e){const t=e?this.findViewById(e):this.getActiveView();if(!t)return!1;const i=path.join(this.artifactManager.resolveViewRoot({scope:t.scope,projectId:this.resolveWorkspaceProjectId(),agentId:this.agentFilterId,viewId:t.id}),"scripts");return await fs_1.promises.mkdir(i,{recursive:!0}),await vscode.commands.executeCommand("revealInExplorer",vscode.Uri.file(i)),!0}async refreshAllViews(){const e=this.getActiveViewId();await this.refreshCharts({viewId:e})}getRefreshContext(e){const t=e?.viewId?this.normalizeViewId(e.viewId):this.getActiveViewId(),i=this.viewsConfig.views.find(s=>s.id===t)??this.getActiveView();return{viewId:i.id,viewName:i.name}}postRefreshStatus(e,t){const i=this.getRefreshContext(t);postToWebview(this.webviewView?.webview,{type:e,viewId:i.viewId,viewName:i.viewName})}async getScriptsForView(e){const t=e?.viewId?this.normalizeViewId(e.viewId):this.getActiveViewId(),i=this.viewsConfig.views.find(s=>s.id===t)??this.getActiveView();return await this.ensureViewOwnedScripts(i),this.withArtifactViewContext(i,async()=>{const s=await this.artifactManager.listScripts({disabledScripts:[]});return this.resolveScriptsForView(i,s)})}resolveScriptsForView(e,t){if(!e.scripts.length)return e.system?e.id==="fleet"?{view:e,scripts:t.filter(s=>this.normalizeBasename(s.filename)===AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME)}:{view:e,scripts:t.filter(s=>this.normalizeBasename(s.filename)!==AnalyticsDashboardProvider.FLEET_WIDGET_BASENAME)}:{view:e,scripts:[]};const i=new Set(e.scripts.map(s=>this.normalizeBasename(s)));return{view:e,scripts:t.filter(s=>i.has(this.normalizeBasename(s.filename)))}}async resolveScriptBasenamesForDuplicate(e){if(e.scripts.length>0)return this.dedupeBasenames(e.scripts);const t=await this.withArtifactViewContext(e,async()=>{const i=await this.artifactManager.listScripts({disabledScripts:[]});return this.resolveScriptsForView(e,i)});return this.dedupeBasenames(t.scripts.map(i=>i.filename))}getScopedPanelId(e){const t=this.normalizePanelStateId(e),i=this.getActiveViewId();return`${t}--${i}`}async refreshDashboardData(){this.queryRunner.invalidateCache(),this.fetchDashboardBootstrapData().then(t=>{this.postFilterState(t?.agentIds),this.triggerSummaryCardsUpdate(t?.summary)});const e=await this.fetchDashboardBootstrapData();await this.postFilterState(e?.agentIds),this.triggerSummaryCardsUpdate(e?.summary),await this.updateDataFreshnessHint(e?{hasData:e.hasData,dataHash:e.dataHash}:void 0)}async refreshAfterViewChange(){this.queryRunner.invalidateCache();const e=this.fetchDashboardBootstrapData();await this.loadAndSendArtifacts(),this.scheduleAutoGenerateForCurrentFilter();const t=await e;await this.postFilterState(t?.agentIds),this.triggerSummaryCardsUpdate(t?.summary),await this.updateDataFreshnessHint(t?{hasData:t.hasData,dataHash:t.dataHash}:void 0)}async shouldRunScriptsOnNavigation(){this.syncArtifactContext();const e=await this.artifactManager.listArtifacts();return(0,viewsConfigMerge_1.shouldRunScriptsOnNavigation)(this.getActiveView(),e.map(t=>t.type))}async refreshChartsForActiveViewAfterSwitch(){try{if(!await this.shouldRunScriptsOnNavigation()){this.outputChannel.appendLine("[Dashboard] View switch: load only (chart artifacts present; use Refresh to re-run scripts)");return}await this.refreshCharts({viewId:this.getActiveViewId()})}catch(e){this.outputChannel.appendLine(`[Dashboard] View-switch chart refresh failed: ${e}`)}}getActiveFilters(){return{scope:this.getActiveView().scope,projectId:this.resolveWorkspaceProjectId(),agentId:this.agentFilterId}}async fetchDashboardBootstrapData(){try{return await this.queryRunner.getDashboardBootstrap(this.getActiveFilters())}catch(e){const t=e instanceof Error?e.message:String(e);this.outputChannel.appendLine(`[Dashboard] Could not load dashboard bootstrap data: ${t}`),t.toLowerCase().includes("indexed analytics db")&&this.webviewView?.webview.postMessage({type:"notifyIndexing",message:"Spotlight index unavailable. Re-index traces (Setup Spotlight) and refresh."});return}}triggerSummaryCardsUpdate(e){this.sendSummaryCards(e)}async postFilterState(e){const t=this.getActiveFilters();let i=[];if(e)i=[...e];else try{i=await this.queryRunner.getAgentIds({scope:t.scope,projectId:t.projectId})}catch(s){this.outputChannel.appendLine(`[Dashboard] Could not load agent filters: ${s}`)}this.agentFilterId&&!i.includes(this.agentFilterId)&&(this.agentFilterId=void 0),postToWebview(this.webviewView?.webview,{type:"filtersUpdated",scope:t.scope,projectId:t.projectId??"",agentId:this.agentFilterId??"",agentIds:i,activeViewId:this.getActiveViewId()})}resolveIndexScriptPathForScripts(){const e=path.resolve(__dirname,"../../scripts/analytics/index-traces.py");return(0,fs_1.existsSync)(e)?e:""}buildScriptEnvironment(e,t=[]){this.syncArtifactContext();const i=this.getActiveFilters(),s=i.scope==="workspace"?i.projectId??"":"",a=this.resolveWorkspaceRootPath()??"",r=path.join((0,index_1.getAgentideHome)(),"analytics","traces"),c=this.indexingService()?.getIndexPath().trim()??"",d=c||path.join(r,"analytics.duckdb"),o=c.length>0,l=this.resolveIndexScriptPathForScripts(),p=vscode.workspace.getConfiguration("agentide.analytics"),h=Math.max(64,p.get("duckdb.memoryLimitMb",1024)??1024),u=Math.max(1,p.get("duckdb.threads",2)??2),f=Math.max(5,p.get("duckdb.queryTimeoutSeconds",30)??30),g=Math.max(100,p.get("duckdb.maxResultRows",5e3)??5e3),w=(p.get("duckdb.tempDirectory","")??"").trim();return{...process.env,PATH:(0,toolPath_1.augmentPathForTools)(process.env.PATH??""),AGENTIDE_TRACES_DIR:r,AGENTIDE_CHART_DIR:this.artifactManager.getChartDir(),AGENTIDE_REPORT_DIR:this.artifactManager.getReportDir(),AGENTIDE_EXPORT_DIR:this.artifactManager.getExportDir(),AGENTIDE_IMAGE_DIR:this.artifactManager.getImageDir(),AGENTIDE_ASSET_DIR:this.artifactManager.getAssetDir(),AGENTIDE_VIEW_ROOT:this.artifactManager.getViewRoot(),AGENTIDE_VIEW_ID:this.getActiveViewId(),AGENTIDE_OUTPUT_DIR:e,AGENTIDE_ANALYTICS_SCOPE:i.scope??"global",AGENTIDE_ANALYTICS_PROJECT_ID:s,AGENTIDE_ANALYTICS_AGENT_ID:i.agentId??"",AGENTIDE_WORKSPACE_ROOT:a,AGENTIDE_DISABLED_CHARTS:t.join(","),AGENTIDE_INDEX_DB_PATH:d,AGENTIDE_USE_DUCKDB:o?"true":"false",AGENTIDE_INDEX_SCRIPT_PATH:l,AGENTIDE_DUCKDB_MEMORY_LIMIT_MB:String(h),AGENTIDE_DUCKDB_THREADS:String(u),AGENTIDE_DUCKDB_QUERY_TIMEOUT_SECONDS:String(f),AGENTIDE_DUCKDB_MAX_RESULT_ROWS:String(g),AGENTIDE_DUCKDB_TEMP_DIRECTORY:w}}parseStringArray(e){return Array.isArray(e)?e.filter(t=>typeof t=="string").map(t=>t.trim()).filter(t=>t.length>0):[]}normalizeBasename(e){return path.basename(e.trim()).toLowerCase()}dedupeBasenames(e){const t=new Set,i=[];for(const s of e){const a=s.trim();if(!a)continue;const r=path.basename(a),n=r.toLowerCase();!n||t.has(n)||(t.add(n),i.push(r))}return i}dedupeCaseInsensitive(e){const t=new Set,i=[];for(const s of e){const a=s.trim();if(!a)continue;const r=a.toLowerCase();t.has(r)||(t.add(r),i.push(a))}return i}parseDisabledScriptsByView(e){if(Array.isArray(e)){const i=this.dedupeBasenames(this.parseStringArray(e));return i.length>0?{all:i}:{}}if(!e||typeof e!="object")return{};const t={};for(const[i,s]of Object.entries(e)){const a=this.normalizeViewId(i,"all"),r=this.dedupeBasenames(this.parseStringArray(s));r.length>0&&(t[a]=r)}return t}mergeDisabledScriptsByView(e,t){const i={};for(const[s,a]of Object.entries(t))i[s]=this.dedupeBasenames(a);for(const[s,a]of Object.entries(e))i[s]=this.dedupeBasenames([...i[s]??[],...a]);return i}getDisabledScriptsForView(e,t){const i=this.normalizeViewId(t,"global");return this.dedupeBasenames([...e.scriptsByView.all??[],...e.scriptsByView[i]??[]])}async loadDisabledChartsConfig(){const e=this.getDisabledChartsConfigPath();try{const t=await fs_1.promises.readFile(e,"utf-8"),s=require("js-yaml").load(t),a=s?.disabled&&typeof s.disabled=="object"?s.disabled:{},r=this.mergeDisabledScriptsByView(this.parseDisabledScriptsByView(s?.scripts),this.parseDisabledScriptsByView(a.scripts)),n=!!(s&&(Object.prototype.hasOwnProperty.call(s,"galleryIgnore")||Object.prototype.hasOwnProperty.call(s,"gallery_ignore"))||Object.prototype.hasOwnProperty.call(a,"galleryIgnore")||Object.prototype.hasOwnProperty.call(a,"gallery_ignore")),c=this.dedupeCaseInsensitive([...this.parseStringArray(s?.galleryIgnore),...this.parseStringArray(s?.gallery_ignore),...this.parseStringArray(a.galleryIgnore),...this.parseStringArray(a.gallery_ignore)]);return{scriptsByView:r,charts:this.dedupeBasenames([...this.parseStringArray(s?.charts),...this.parseStringArray(a.charts)]),galleryIgnore:n?c:[...AnalyticsDashboardProvider.DEFAULT_GALLERY_IGNORE]}}catch{return{scriptsByView:{},charts:[],galleryIgnore:[...AnalyticsDashboardProvider.DEFAULT_GALLERY_IGNORE]}}}async postDisabledChartsState(e){const t=e??await this.loadDisabledChartsConfig();this.webviewView?.webview.postMessage({type:"disabledChartsUpdated",charts:t.charts})}async saveDisabledChartsConfig(e){const t=this.getDisabledChartsConfigPath();await fs_1.promises.mkdir(path.dirname(t),{recursive:!0});const i={};for(const[r,n]of Object.entries(e.scriptsByView)){const c=this.normalizeViewId(r,"all"),d=this.dedupeBasenames(n);d.length>0&&(i[c]=d)}const a=require("js-yaml").dump({scripts:i,charts:this.dedupeBasenames(e.charts),galleryIgnore:this.dedupeCaseInsensitive(e.galleryIgnore)},{lineWidth:120,noRefs:!0});await fs_1.promises.writeFile(t,a,"utf-8")}isPathWithinDir(e,t){return(0,pathSafety_1.isPathUnderRoot)(t,e)}async disableChartByFilename(e){const t=path.basename(e.trim());if(!t)return;const i=await this.loadDisabledChartsConfig(),s=new Set(i.charts.map(r=>this.normalizeBasename(r))),a=this.normalizeBasename(t);!a||s.has(a)||(await this.saveDisabledChartsConfig({scriptsByView:i.scriptsByView,charts:[...i.charts,t],galleryIgnore:i.galleryIgnore}),await this.postDisabledChartsState())}normalizePanelStateId(e){return(e??"default").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"_").replace(/^_+|_+$/g,"")||"default"}getPanelStatePath(e){const t=this.getScopedPanelId(e);return path.join(this.artifactManager.getAnalyticsRoot(),"panel-state",`${t}.json`)}async loadPanelState(e){const t=this.getPanelStatePath(e);try{const i=await fs_1.promises.readFile(t,"utf-8");return JSON.parse(i)}catch{return{}}}async savePanelState(e,t){const i=this.getPanelStatePath(e);await fs_1.promises.mkdir(path.dirname(i),{recursive:!0});const s=t&&typeof t=="object"?t:{};await fs_1.promises.writeFile(i,`${JSON.stringify(s,null,2)}
3
+ `,"utf-8")}async postPanelState(e){const t=this.normalizePanelStateId(e),i=await this.loadPanelState(e),s=Math.max(0,vscode.workspace.getConfiguration("agentide.analytics").get("fleetAutoRefreshIntervalSeconds",0));this.webviewView?.webview.postMessage({type:"panelStateLoaded",panelId:t,state:i,fleetAutoRefreshIntervalSeconds:s,surfaceMode:this.getSurfaceMode()})}async updateDataFreshnessHint(e){const t=this.getActiveFilters();if(!(e?.hasData??await this.queryRunner.hasData(t))){this.webviewView?.webview.postMessage({type:"dataFresh"});return}const s=await this.artifactManager.getManifest(),a=e?.dataHash??await this.queryRunner.getDataHash(t);if(!s){this.webviewView?.webview.postMessage({type:"dataFresh"});return}s.dataHash!==a?this.webviewView?.webview.postMessage({type:"dataStale",generatedAt:s.generatedAt}):this.webviewView?.webview.postMessage({type:"dataFresh"})}async onPanelBecameVisible(){await this.loadAndPostViewsState(),this.syncArtifactContext(),await this.postPanelState("default"),await this.seedWorkspaceScripts();const e=this.fetchDashboardBootstrapData();await this.loadAndSendArtifacts(),await this.postPanelState("default"),await this.ensureChartsForCurrentFilterIfNeeded({trigger:"panel-open"}),await this.autoRefreshDefaultCharts();const t=await e;await this.postFilterState(t?.agentIds),this.triggerSummaryCardsUpdate(t?.summary),await this.updateDataFreshnessHint(t?{hasData:t.hasData,dataHash:t.dataHash}:void 0)}getFilterCacheKey(e=this.getActiveFilters()){const t=e.scope==="workspace"?"workspace":"global",i=e.scope==="workspace"?e.projectId??"":"",s=e.agentId??"";return`${t}::${i}::${s}`}pruneAutoGenerationHistory(){if(this.autoGenerationAttemptByFilter.size<=MAX_FILTER_AUTOGEN_HISTORY)return;const e=[...this.autoGenerationAttemptByFilter.entries()].sort((i,s)=>i[1]-s[1]),t=this.autoGenerationAttemptByFilter.size-MAX_FILTER_AUTOGEN_HISTORY;for(let i=0;i<t;i++)this.autoGenerationAttemptByFilter.delete(e[i][0])}scheduleAutoGenerateForCurrentFilter(){this.pendingFilterAutoGenerateTimer&&clearTimeout(this.pendingFilterAutoGenerateTimer);const e=this.getFilterCacheKey();this.pendingFilterAutoGenerateTimer=setTimeout(()=>{this.ensureChartsForCurrentFilterIfNeeded({trigger:"filter-switch",expectedFilterKey:e,respectCooldown:!0})},FILTER_AUTOGEN_DEBOUNCE_MS)}async ensureChartsForCurrentFilterIfNeeded(e){this.syncArtifactContext();const t=this.getActiveFilters(),i=this.getFilterCacheKey(t);if(e?.expectedFilterKey&&e.expectedFilterKey!==i||this.isRefreshInProgress)return;if(e?.respectCooldown){const n=this.autoGenerationAttemptByFilter.get(i)??0;if(Date.now()-n<FILTER_AUTOGEN_COOLDOWN_MS){this.outputChannel.appendLine("[Dashboard] Auto-generation skipped (cooldown active)");return}}(await this.artifactManager.listArtifacts()).some(n=>n.type==="chart")||!(e?.hasDataHint??await this.queryRunner.hasData(t))||(this.autoGenerationAttemptByFilter.set(i,Date.now()),this.pruneAutoGenerationHistory(),this.outputChannel.appendLine("[Dashboard] No cached charts for selected filters; auto-generating"),await this.refreshCharts())}async autoRefreshDefaultCharts(e){if(!vscode.workspace.getConfiguration("agentide.analytics").get("autoGenerateDefaults",!0))return;const s=this.getActiveFilters();if(!(e?.hasDataHint??await this.queryRunner.hasData(s)))return;const r=await this.artifactManager.getManifest(),n=e?.dataHashHint??await this.queryRunner.getDataHash(s);r?r.dataHash!==n?this.webviewView?.webview.postMessage({type:"dataStale",generatedAt:r.generatedAt}):this.webviewView?.webview.postMessage({type:"dataFresh"}):await this.refreshCharts()}async runDefaultChartScript(e){this.syncArtifactContext();const t=e?.preserveExistingContent??!1;let i="finished",s="";this.generationAbortController?.abort(),this.generationAbortController=new AbortController;const a=this.indexingService();a&&a.isIndexing()&&(this.outputChannel.appendLine("[Dashboard] Background DuckDB indexing in progress; using JSONL fallback for charts"),t||this.webviewView?.webview.postMessage({type:"notifyIndexing",message:"DuckDB index is being built in background. Charts will use JSONL for now (slower)."})),t?this.startSmoothRefresh({batchArtifactUpdates:!0}):this.webviewView?.webview.postMessage({type:"generatingStarted"});try{const r=path.join((0,index_1.getAgentideHome)(),"analytics","traces"),n=(await this.artifactManager.ensureArtifactDir()).fsPath,c=await this.loadDisabledChartsConfig(),d=this.buildScriptEnvironment(n,c.charts),o=await this.findChartScript();if(!o)throw new Error("Chart generation script not found");this.outputChannel.appendLine(`[Dashboard] Running default chart script: ${o}`),this.outputChannel.appendLine(`[Dashboard] Traces: ${r}, Output: ${n}`);const l=await this.readPythonDeps();let p;try{const w=await(0,pythonRunner_1.runPythonWithDeps)({scriptPath:o,args:[r,n],deps:l,analyticsRoot:this.artifactManager.getAnalyticsRoot(),requirementsPath:path.join(this.artifactManager.getScriptDir(),"requirements.txt"),execOptions:{timeout:6e4,maxBuffer:5242880,env:d,signal:this.generationAbortController?.signal},log:y=>this.outputChannel.appendLine(y)});p={stdout:w.stdout,stderr:w.stderr}}catch(w){const y=w.stdout,v=w.stderr;if(typeof y=="string"&&y.trim().startsWith("{"))p={stdout:y,stderr:typeof v=="string"?v:""};else throw w}p.stderr&&this.outputChannel.appendLine(`[Dashboard] Chart script stderr: ${p.stderr}`);let h;try{h=JSON.parse(p.stdout)}catch{throw new Error(`Script output not valid JSON: ${p.stdout.slice(0,200)}`)}if(h.status==="no_data"){this.outputChannel.appendLine("[Dashboard] No trace data available"),t?i="noData":this.webviewView?.webview.postMessage({type:"generatingCancelled"});return}if(h.status!=="ok")throw new Error(h.error||"Chart generation failed");const u=await this.queryRunner.getDataHash(this.getActiveFilters()),f=(h.charts??[]).map(w=>({filename:path.basename(w.file),path:path.relative(n,w.file),type:"chart",source:"default",title:w.title,createdAt:new Date().toISOString(),generator:"generate-default-charts.py"})),g={version:1,generatedAt:h.generated_at||new Date().toISOString(),dataHash:u,prompt:"auto-generated",artifacts:f};await this.artifactManager.saveManifest(g),await this.writeFleetExportForCurrentView(),this.outputChannel.appendLine(`[Dashboard] Generated ${f.length} default charts`),await this.loadAndSendArtifacts()}catch(r){if(r instanceof Error&&r.name==="AbortError")t?(i="failed",s="Refresh cancelled"):this.webviewView?.webview.postMessage({type:"generatingCancelled"});else{const n=r instanceof Error?r.message:String(r);this.outputChannel.appendLine(`[Dashboard] Chart generation failed: ${n}`),t?(i="failed",s=n):this.webviewView?.webview.postMessage({type:"generatingFailed",error:n})}}finally{if(t){try{await this.finishSmoothRefresh({notifyFinished:!1})}catch(r){this.outputChannel.appendLine(`[Dashboard] Failed finishing default-refresh state: ${r}`),this.webviewView?.webview.postMessage({type:"refreshFinished"})}i==="finished"?this.webviewView?.webview.postMessage({type:"refreshFinished"}):i==="noData"?this.webviewView?.webview.postMessage({type:"refreshNoData"}):this.webviewView?.webview.postMessage({type:"refreshFailed",error:s||"Refresh failed"})}this.generationAbortController=void 0}}async findChartScript(){const e=this.getBundledAnalyticsScriptCandidates("generate-default-charts.py");for(const t of e)try{return await fs_1.promises.access(t),t}catch{}return null}async findBundledAnalyticsScript(e){const t=this.getBundledAnalyticsScriptCandidates(e);for(const i of t)try{return await fs_1.promises.access(i),i}catch{}return null}getBundledAnalyticsScriptCandidates(e){const t=this.extensionContext.extensionPath,i=[path.resolve(__dirname,"../../scripts","analytics",e),path.resolve(__dirname,"../../../scripts","analytics",e),path.resolve(__dirname,"../../../../extensions","agentide","scripts","analytics",e)],s=[path.join(t,"out","scripts","analytics",e),path.join(t,"scripts","analytics",e),path.join(t,"..","scripts","analytics",e),path.join(t,"..","..","scripts","analytics",e),path.join(t,"..","..","..","extensions","agentide","scripts","analytics",e)];return[...new Set([...i,...s])]}async loadBundledDefaultSeedScriptNames(){const e=[...FALLBACK_DEFAULT_SEED_SCRIPT_NAMES],t=await this.findBundledAnalyticsScript(DEFAULT_SEED_REGISTRY_FILENAME);if(!t)return this.outputChannel.appendLine(`[Dashboard] Missing default seed registry: ${DEFAULT_SEED_REGISTRY_FILENAME}. Using built-in defaults.`),e;try{const i=await fs_1.promises.readFile(t,"utf-8"),s=JSON.parse(i),a=this.parseStringArray(s.seedScripts).map(r=>path.basename(r)).filter(Boolean);return a.length===0&&this.outputChannel.appendLine(`[Dashboard] Seed registry ${t} had no scripts. Using built-in defaults.`),this.dedupeBasenames([...a,...e])}catch(i){return this.outputChannel.appendLine(`[Dashboard] Failed reading seed registry ${t}: ${i}`),e}}getDisabledChartsConfigPath(){return path.join(this.artifactManager.getAnalyticsRoot(),"disabled-charts.yaml")}hasDuplicateShebangMarkers(e){return(e.match(/^#!/gm)?.length??0)>1}hasStaleDuckDbLookup(e,t){if(!new Set(["session-timeline-widget.js","session-comparison-widget.js","wow-model-widget.js"]).has(e))return!1;const s=t.includes("require('./duckdbLocator')"),a=t.includes("function findDuckdb()")&&t.includes("path.join(home, '.agent-hippo', 'bin', 'duckdb')"),r=t.includes("duckdb.exe"),n=t.includes("const home = process.env.HOME || ''");return!s&&(a||n||!r)}async repairCorruptedBundledDefaultScripts(e){const t=this.artifactManager.getScriptDir();let i=0;for(const s of e){const a=path.basename(s);if(!a)continue;const r=path.join(t,a);let n;try{n=await fs_1.promises.readFile(r,"utf-8")}catch{continue}const c=this.hasDuplicateShebangMarkers(n),d=this.hasStaleDuckDbLookup(a,n);if(!c&&!d)continue;const o=await this.findBundledAnalyticsScript(a);if(!o){this.outputChannel.appendLine(`[Dashboard] Could not repair script ${a}: source not found`);continue}if(path.resolve(o)!==path.resolve(r))try{await fs_1.promises.copyFile(o,r),i+=1;const l=c?"duplicate shebang markers":"stale DuckDB lookup";this.outputChannel.appendLine(`[Dashboard] Repaired default script ${a} (${l})`)}catch(l){this.outputChannel.appendLine(`[Dashboard] Failed repairing default script ${a}: ${l}`)}}return i}viewNeedsBundledDefaultScripts(e,t){if(e.scripts.length===0)return e.system===!0&&e.id!=="fleet";const i=new Set(t.map(s=>path.basename(s).toLowerCase()));return this.dedupeBasenames(e.scripts).some(s=>i.has(s.toLowerCase()))}async ensureBundledDefaultScriptsPresent(){const e=await this.loadBundledDefaultSeedScriptNames();if(e.length===0){this.outputChannel.appendLine("[Dashboard] No default scripts listed in seed registry");return}const t=this.getActiveView();if(!this.viewNeedsBundledDefaultScripts(t,e))return;const i=await this.artifactManager.listScripts(),s=new Set(i.map(n=>path.basename(n.filename).toLowerCase())),a=[];for(const n of e){if(s.has(path.basename(n).toLowerCase()))continue;const c=await this.findBundledAnalyticsScript(n);c&&a.push({sourcePath:c,filename:n})}a.length>0?(await this.artifactManager.ensureSeedScripts(a),this.outputChannel.appendLine(`[Dashboard] Seeded ${a.length} missing default script(s)`)):this.outputChannel.appendLine(`[Dashboard] No missing default scripts were resolvable from extension path: ${this.extensionContext.extensionPath}`);const r=await this.repairCorruptedBundledDefaultScripts(e);r>0&&this.outputChannel.appendLine(`[Dashboard] Repaired ${r} default script(s)`)}async seedWorkspaceScripts(){await this.seedRequirements(),await this.seedDisabledChartsConfig(),await this.ensureBundledDefaultScriptsPresent();try{await fs_1.promises.mkdir(this.getWidgetRunLogsDir(),{recursive:!0})}catch(e){this.outputChannel.appendLine(`[Dashboard] Failed creating widget log directory: ${e}`)}await this.seedPanelFiles()}async seedDisabledChartsConfig(){const e=this.getDisabledChartsConfigPath();try{await fs_1.promises.access(e);return}catch{}const t=["# Disable script execution or chart outputs without deleting files.","# scripts: per-view script filenames in analytics/views/<viewId>/scripts to skip running.",'# Use "all" to apply to every view id.',"scripts:"," all: []"," # all:"," # - wow-model-widget.js"," # fleet:"," # - agent-fleet-widget.js","# charts: output filenames in analytics/views/.../charts to disable/hide.","charts:"," # Hotspot analysis is opt-in by default; remove this entry to enable it."," - hotspot-analysis-widget.html","# galleryIgnore: suffixes or exact filenames hidden from gallery cards.","galleryIgnore:",` - ${AnalyticsDashboardProvider.DEFAULT_GALLERY_IGNORE[0]}`,` - ${AnalyticsDashboardProvider.DEFAULT_GALLERY_IGNORE[1]}`,""].join(`
4
+ `);try{await fs_1.promises.writeFile(e,t,"utf-8")}catch(i){this.outputChannel.appendLine(`[Dashboard] Failed seeding disabled-charts.yaml: ${i}`)}}async seedRequirements(){const e=this.artifactManager.getScriptDir();await fs_1.promises.mkdir(e,{recursive:!0});const t=path.join(e,"requirements.txt");try{await fs_1.promises.access(t);return}catch{}try{await fs_1.promises.writeFile(t,["# Python dependencies for analytics scripts.","# Add packages here - they are auto-installed when scripts run.","matplotlib","duckdb",""].join(`
5
+ `),"utf-8")}catch(i){this.outputChannel.appendLine(`[Dashboard] Failed seeding requirements.txt: ${i}`)}}async seedPanelFiles(){const e=this.getPanelDir();await fs_1.promises.mkdir(e,{recursive:!0});const t=this.getWebviewPath(),i=path.join(this.extensionContext.extensionPath,t),s=[{bundled:"styles.css",userFile:"panel.css"},{bundled:"analytics-panel.html",userFile:"panel.html"}];try{await fs_1.promises.access(path.join(e,"panel.js")),this.outputChannel.appendLine(`[Dashboard] Note: ${path.join(e,"panel.js")} is not used \u2014 the bundled panel.js always loads. Edit panel.html / panel.css to customize the dashboard.`)}catch{}for(const{bundled:a,userFile:r}of s){const n=path.join(e,r);try{await fs_1.promises.access(n);continue}catch{}const c=path.join(i,a);try{await fs_1.promises.copyFile(c,n)}catch(d){this.outputChannel.appendLine(`[Dashboard] Failed seeding panel file ${r}: ${d}`)}}await this.patchUserPanelHtmlOnDisk()}getPanelDir(){return this.artifactManager.getPanelDir()}async refreshCharts(e){if(e?.viewId&&!await this.setActiveView(e.viewId)){this.outputChannel.appendLine(`[Dashboard] Ignoring refresh for unknown view: ${e.viewId}`);return}if(this.syncArtifactContext(),this.isRefreshInProgress){this.hasPendingRefreshRequest=!0,this.outputChannel.appendLine("[Dashboard] Refresh already running; queued one follow-up refresh");return}const t=this.getRefreshContext(e);this.isRefreshInProgress=!0;let i=!1,s,a="ok",r="";try{try{s=await this.startWidgetRunLog()}catch(h){this.outputChannel.appendLine(`[Dashboard] Failed to initialize widget run log: ${h}`)}await this.ensureBundledDefaultScriptsPresent();const n=await this.loadDisabledChartsConfig(),c=await this.getScriptsForView(e),d=new Set(this.getDisabledScriptsForView(n,c.view.id).map(h=>this.normalizeBasename(h))),o=c.scripts.filter(h=>!d.has(this.normalizeBasename(h.filename)));if(o.length===0){this.outputChannel.appendLine(`[Dashboard] No scripts assigned to view "${c.view.name}"`),await this.finishSmoothRefresh({notifyFinished:!1,refresh:e}),postToWebview(this.webviewView?.webview,{type:"refreshNoData",viewId:t.viewId});return}this.startSmoothRefresh({batchArtifactUpdates:!1,refresh:e});let l=null,p={totalScripts:0,failedScripts:0};try{const h=o.some(g=>this.normalizeBasename(g.filename)==="agent-fleet-widget.js");if(h){this.queryRunner.invalidateCache(),this.indexingService()?.scheduleIndexing(!0,{debounceMs:500});const g=this.getActiveView();await this.ensureViewOwnedDirectories(g),await this.writeFleetExportForCurrentView()}this.outputChannel.appendLine(`[Dashboard] Refreshing view "${c.view.name}" via ${o.length} script(s)`),p=await this.runUserScripts(o,n.charts,s),p.failedScripts>0&&a==="ok"&&(a="failed",r=`${p.failedScripts}/${p.totalScripts} widget scripts failed`);const u=await this.queryRunner.getDataHash(this.getActiveFilters()),f=await this.artifactManager.getManifest()??{version:1,generatedAt:new Date().toISOString(),dataHash:u,prompt:"",artifacts:[]};f.generatedAt=new Date().toISOString(),f.dataHash=u,await this.artifactManager.saveManifest(f),this.shouldUpdateFleetExportAfterRefresh(h)&&await this.writeFleetExportForCurrentView(),await this.enqueueArtifactRefresh()}catch(h){const u=h instanceof Error?h.message:String(h);this.outputChannel.appendLine(`[Dashboard] Refresh failed: ${u}`),l=u,a=u.toLowerCase().includes("cancel")?"cancelled":"failed",r=u}finally{try{await this.finishSmoothRefresh({notifyFinished:!0,refresh:e})}catch(h){this.outputChannel.appendLine(`[Dashboard] Failed finishing refresh state: ${h}`),this.postRefreshStatus("refreshFinished",e)}l!==null&&postToWebview(this.webviewView?.webview,{type:"refreshFailed",viewId:t.viewId,error:l})}}catch(n){const c=n instanceof Error?n.message:String(n);this.outputChannel.appendLine(`[Dashboard] Refresh failed: ${c}`),postToWebview(this.webviewView?.webview,{type:"refreshFailed",viewId:t.viewId,error:c}),this.postRefreshStatus("refreshFinished",e),a=c.toLowerCase().includes("cancel")?"cancelled":"failed",r=c}finally{try{await this.finishWidgetRunLog(s,a,r)}catch(n){this.outputChannel.appendLine(`[Dashboard] Failed finalizing widget run log: ${n}`)}i=this.hasPendingRefreshRequest,this.hasPendingRefreshRequest=!1,this.isRefreshInProgress=!1}i&&(this.outputChannel.appendLine("[Dashboard] Running queued follow-up refresh"),await this.refreshCharts({viewId:this.getActiveViewId()}))}async refreshWidgetScriptsOnly(e){const t=Array.from(new Set(e.map(s=>this.normalizeBasename(s)).filter(s=>!!s)));if(t.length===0)return;const i={viewId:this.getActiveViewId(),viewName:this.getActiveView().name};this.startSmoothRefresh({batchArtifactUpdates:!0,refresh:i});try{this.syncArtifactContext(),await this.ensureBundledDefaultScriptsPresent();const s=new Set(t),a=s.has("agent-fleet-widget.js")&&this.getActiveViewId()==="fleet";if(a){const o=this.getActiveView();await this.ensureViewOwnedDirectories(o),await this.writeFleetExportForCurrentView()}const r=await this.getScriptsForView({viewId:this.getActiveViewId()}),n=await this.loadDisabledChartsConfig(),c=new Set(this.getDisabledScriptsForView(n,r.view.id).map(o=>this.normalizeBasename(o))),d=r.scripts.filter(o=>s.has(this.normalizeBasename(o.filename))&&!c.has(this.normalizeBasename(o.filename)));if(d.length===0){await this.enqueueArtifactRefresh(),await this.postViewExportSnapshot({respondWhenMissing:!0});return}await this.runUserScripts(d,n.charts),a&&(this.fleetRefreshVersion+=1),await this.enqueueArtifactRefresh(),await this.postViewExportSnapshot({respondWhenMissing:!0})}finally{await this.finishSmoothRefresh({notifyFinished:!0,refresh:i})}}isDuckDbHeavyScript(e){return this.duckDbHeavyScriptNames.has(path.basename(e.filename).toLowerCase())}getWidgetRunLogsDir(){return path.join(this.artifactManager.getAnalyticsRoot(),"logs","widgets")}createWidgetRunId(){const t=new Date().toISOString().replace(/[:.]/g,"-"),i=Math.random().toString(36).slice(2,8);return`refresh-${t}-${i}`}async appendWidgetRunLogLine(e,t){const i=`${JSON.stringify(t)}
6
+ `;await fs_1.promises.appendFile(e,i,"utf-8")}shouldUpdateFleetExportAfterRefresh(e){return this.getActiveViewId()==="fleet"||e}async writeFleetExportForCurrentView(){const e=this.getActiveFilters(),t=path.join(this.artifactManager.getExportDir(),AnalyticsDashboardProvider.FLEET_EXPORT_FILENAME),i=d=>Array.isArray(d)?d.map(o=>o&&typeof o=="object"?o:null).filter(o=>!!o).slice().sort((o,l)=>{const p=(0,fleetTypes_1.rankFleetStatus)(o.status)-(0,fleetTypes_1.rankFleetStatus)(l.status);if(p!==0)return p;const h=Date.parse(String(o.startedAt??"")),u=Date.parse(String(l.startedAt??""));return Number.isFinite(h)&&Number.isFinite(u)&&h!==u?u-h:String(o.sessionId??"").localeCompare(String(l.sessionId??""))}):[],s=d=>({scope:d.scope??"global",projectId:d.projectId??"",agentId:d.agentId??"",totals:d.totals??{},sessions:i(d.sessions)}),a=async()=>{try{const d=await fs_1.promises.readFile(t,"utf-8"),o=JSON.parse(d);return JSON.stringify(s(o))}catch{return null}},r=await a();try{const d=await(0,fleetOrchestrator_1.runFleetRefresh)({scope:e.scope??"global",projectId:e.projectId,agentId:e.agentId},{queryRunner:this.queryRunner,exportPath:t,onError:(o,l)=>{this.outputChannel.appendLine(`[Dashboard] Fleet source "${o}" failed: ${String(l)}`)}});d.success||this.outputChannel.appendLine(`[Dashboard] Fleet refresh completed with source errors (failed: ${d.failedSources.join(", ")||"none"}).`)}catch(d){this.outputChannel.appendLine(`[Dashboard] Fleet refresh failed: ${d}`);return}const n=await a();(r===null||n!==r)&&this.getActiveViewId()==="fleet"&&(this.fleetRefreshVersion+=1,await this.refreshFleetWidgetFromCurrentExport(),this.isRefreshInProgress||await this.enqueueArtifactRefresh())}async handleFleetCardTitleUpdate(e,t,i){const s=String(t??"").trim();if(!s)return;const a=String(i??"").trim(),r=Array.from(new Set([path.join((0,index_1.getAgentideHome)(),"analytics","fleet-title-updates.log"),path.join(this.artifactManager.getAnalyticsRoot(),"fleet-title-updates.log")])),n=async(c,d)=>{const o=`${new Date().toISOString()} sessionId=${s} cardTitle=${JSON.stringify(a)} ${c}`,l=d!==void 0?` error=${String(d)}`:"";await Promise.all(r.map(async p=>{await fs_1.promises.mkdir(path.dirname(p),{recursive:!0}),await fs_1.promises.appendFile(p,`${o}${l}
7
+ `,"utf-8").catch(()=>{})}))};await n("requested");try{await this.queryRunner.updateFleetCardTitle(e,s,a),await this.writeFleetExportForCurrentView(),await n("persisted"),this.outputChannel.appendLine(`[Dashboard] Fleet title edit persisted for session=${s}`)}catch(c){await n("failed",c),this.outputChannel.appendLine(`[Dashboard] Fleet title edit failed for session=${s}: ${c}`)}}async refreshFleetWidgetFromCurrentExport(){const e=this.getActiveViewId();if(e!=="fleet")return;const t=this.getActiveView();if(!t)return;this.syncArtifactContext(),await this.ensureBundledDefaultScriptsPresent(),await this.ensureViewOwnedDirectories(t);const i=await this.getScriptsForView({viewId:e}),s=await this.loadDisabledChartsConfig(),a=new Set(this.getDisabledScriptsForView(s,i.view.id).map(n=>this.normalizeBasename(n))),r=i.scripts.filter(n=>{const c=this.normalizeBasename(n.filename);return c==="agent-fleet-widget.js"&&!a.has(c)});await this.runUserScripts(r,s.charts)}buildFleetDisconnectPrompt(e,t,i){const s=e.trim(),a=(t??"").trim()||"unknown",r=(i??"").trim();return["Disconnect this Agent Anywhere channel session.",`Session ID: ${s}`,`Channel Type: ${a}`,r?`Channel Target: ${r}`:"","If direct disconnect tooling is unavailable, provide the exact command/workflow to disconnect safely."].filter(Boolean).join(`
8
+ `)}async handleFleetDisconnectRequest(e,t,i){const s=e.trim();!s||await vscode.window.showWarningMessage(`Disconnect channel for session ${s}?`,{modal:!0},"Disconnect")!=="Disconnect"||await this.openVsCodeChat({prompt:this.buildFleetDisconnectPrompt(s,t,i)})}buildLocalChatSessionResource(e){return`vscode-chat-session://local/${Buffer.from(e,"utf-8").toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}`}async resolveFleetSessionOpenPreferences(e,t,i,s,a){const r=(0,fleetRowDefaults_1.buildFleetSessionRow)("local",{sessionId:e,agentId:s,status:"in_progress",startedAt:new Date().toISOString(),endedAt:"",taskTitle:String(a??"").trim()||e,model:String(t??"").trim()||"unknown",engine:String(i??"").trim(),channelType:"none",channelTarget:"",turns:0,totalTokens:0});if(!a?.trim())try{const n=await vscode.commands.executeCommand("agentide.resolveFleetSessionTitles",[e]),c=typeof n?.[e]=="string"?n[e].trim():"";c&&(r.taskTitle=c,r.cardTitle=r.cardTitle??c)}catch{}return(0,fleetDisplayUtils_1.enrichFleetRowMetadata)(r),{model:r.model,engine:r.engine,modeName:(0,fleetDisplayUtils_1.fleetEngineIdToChatModeName)(r.engine)}}async openSpotlightForSession(e,t,i,s,a,r){let n=e.trim();if(!n)return;if(n.startsWith("schedule:")){const u=String(i??"").trim();u&&(n=u)}const c=this.buildLocalChatSessionResource(n),d=await this.resolveFleetSessionOpenPreferences(n,s,a,r),o=d.model,l=d.engine,p=String(r??"").trim()||"default",h=["Show the session investigation for this agent session.",`Session ID: ${n}`,t?`Channel: ${t}`:"",i?`Target: ${i}`:"","Include: the live chat transcript, a chronological feed of the agent's actions/logs, and the exact prompt or payload that initiated the task."].filter(Boolean).join(`
9
+ `);try{await vscode.commands.executeCommand("workbench.action.chat.open",{sessionResource:c,mode:d.modeName??"agent"});for(let g=0;g<5;g+=1)if(await new Promise(v=>setTimeout(v,g===0?150:100)),((await vscode.commands.executeCommand("agentide.getChatState"))?.sessionId?.trim()??"")===n){await this.applyFleetSessionChatPreferences(p,l,o);return}await this.applyFleetSessionChatPreferences(p,l,o);const f=(await vscode.commands.executeCommand("agentide.getChatState"))?.sessionId?.trim()??"";if(f===n)return;this.outputChannel.appendLine(`[Dashboard] Could not open session ${n} directly (active=${f||"unknown"}); falling back to investigation prompt`),await this.openVsCodeChat({prompt:h})}catch{await this.openVsCodeChat({prompt:h})}}async applyFleetSessionChatPreferences(e,t,i){if(!(!t&&!i))try{await vscode.commands.executeCommand("agentide.applyPackPreferences",{packId:e,preferredEngine:t||void 0,preferredModel:i||void 0,silent:!0})}catch(s){this.outputChannel.appendLine(`[Dashboard] Could not restore fleet session engine/model: ${s}`)}}async startWidgetRunLog(){const e=this.getWidgetRunLogsDir();await fs_1.promises.mkdir(e,{recursive:!0}),await this.trimOldWidgetRunLogs();const t=this.createWidgetRunId(),i=path.join(e,`${t}.jsonl`),s=new Date().toISOString(),a=this.getActiveFilters();return await this.appendWidgetRunLogLine(i,{type:"refresh_started",runId:t,startedAt:s,scope:a.scope,projectId:a.projectId??"",agentId:a.agentId??""}),this.outputChannel.appendLine(`[Dashboard] Widget refresh log: ${i}`),{runId:t,logPath:i,startedAt:s}}async trimOldWidgetRunLogs(){const e=this.getWidgetRunLogsDir();let t=[];try{const s=await fs_1.promises.readdir(e,{withFileTypes:!0});t=await Promise.all(s.filter(a=>a.isFile()&&a.name.endsWith(".jsonl")).map(async a=>{const r=path.join(e,a.name),n=await fs_1.promises.stat(r);return{name:a.name,fullPath:r,mtimeMs:n.mtimeMs}}))}catch{return}if(t.length<=WIDGET_RUN_LOG_RETENTION_COUNT)return;t.sort((s,a)=>a.mtimeMs-s.mtimeMs);const i=t.slice(WIDGET_RUN_LOG_RETENTION_COUNT);await Promise.all(i.map(s=>fs_1.promises.unlink(s.fullPath).catch(()=>{})))}async finishWidgetRunLog(e,t,i){e&&await this.appendWidgetRunLogLine(e.logPath,{type:"refresh_finished",runId:e.runId,status:t,endedAt:new Date().toISOString(),error:i??""})}async acquireDuckDbScriptSlot(e){if(this.isDuckDbHeavyScript(e)){for(;this.activeDuckDbScriptCount>=1;)await new Promise(t=>this.duckDbScriptWaiters.push(t));this.activeDuckDbScriptCount+=1,this.outputChannel.appendLine(`[Dashboard] DuckDB gate acquired: ${e.filename}`)}}releaseDuckDbScriptSlot(e){if(!this.isDuckDbHeavyScript(e))return;this.activeDuckDbScriptCount=Math.max(0,this.activeDuckDbScriptCount-1),this.outputChannel.appendLine(`[Dashboard] DuckDB gate released: ${e.filename}`),this.duckDbScriptWaiters.shift()?.()}async runUserScripts(e,t,i){this.syncArtifactContext();const s=(await this.artifactManager.ensureArtifactDir()).fsPath;if(e.length===0)return this.outputChannel.appendLine("[Dashboard] All scripts disabled; nothing to run"),{totalScripts:0,failedScripts:0};e.some(o=>o.filename.toLowerCase().endsWith(".py"))&&await this.seedRequirements();const a=this.buildScriptEnvironment(s,t),r=Math.max(1,Math.min(USER_SCRIPT_MAX_CONCURRENCY,e.length)),n=[];let c=0;const d=async()=>{for(;;){const o=c++;if(o>=e.length)return;const l=e[o],p=Date.now();let h=!1,u=0,f="ok",g="",w="",y="";try{const v=Date.now();await this.acquireDuckDbScriptSlot(l),u=Date.now()-v,h=!0;const b=await this.executeUserScript(l,a);w=b.stdout,y=b.stderr}catch(v){f="failed",g=v instanceof Error?v.message:String(v),n.push({script:l,error:v})}finally{if(h&&this.releaseDuckDbScriptSlot(l),i)try{const v=new Date().toISOString(),b=Date.now()-p,C=this.getActiveFilters();await this.appendWidgetRunLogLine(i.logPath,{type:"script_run",runId:i.runId,script:l.filename,language:l.language,status:f,error:g,stdout:w,stderr:y,durationMs:b,duckDbGateWaitMs:u,finishedAt:v,scope:C.scope,projectId:C.projectId??"",agentId:C.agentId??""})}catch(v){this.outputChannel.appendLine(`[Dashboard] Failed writing script run log: ${v}`)}await this.enqueueArtifactRefresh()}}};if(await Promise.all(Array.from({length:r},()=>d())),n.length===e.length)throw new Error("All refresh scripts failed. Check output logs for details.");if(n.length>0){this.outputChannel.appendLine(`[Dashboard] Refresh completed with ${n.length} script failure(s)`);for(const o of n){const l=o.error instanceof Error?o.error.message:String(o.error);this.outputChannel.appendLine(`[Dashboard] Script failed: ${o.script.filename}: ${l}`)}}return{totalScripts:e.length,failedScripts:n.length}}isJavascriptEsmScopeError(e){const t=typeof e.stderr=="string"?e.stderr:"",s=[e instanceof Error?e.message:String(e),t].join(`
10
+ `).toLowerCase();return s.includes("require is not defined in es module scope")||s.includes("module is not defined in es module scope")||s.includes("exports is not defined in es module scope")}async runJavascriptWithCommonJsRetry(e,t){try{return await execFileAsync("node",[e],t)}catch(i){if(path.extname(e).toLowerCase()!==".js"||!this.isJavascriptEsmScopeError(i))throw i;const a=path.parse(e);this.outputChannel.appendLine(`[Dashboard] Retrying JavaScript script as CommonJS: ${a.base}`);const r=await fs_1.promises.mkdtemp(path.join(os.tmpdir(),`agentide-cjs-run-${a.name}-`)),n=path.join(r,"scripts"),c=path.join(n,a.base);try{return await fs_1.promises.cp(a.dir,n,{recursive:!0}),await fs_1.promises.writeFile(path.join(n,"package.json"),JSON.stringify({type:"commonjs"}),"utf-8"),await execFileAsync("node",[c],t)}finally{await fs_1.promises.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}}async executeUserScript(e,t){const i=e.uri.fsPath;this.outputChannel.appendLine(`[Dashboard] Running user script: ${e.filename}`);const s={timeout:6e4,maxBuffer:5*1024*1024,env:t};let a;switch(e.language){case"python":{const r=await this.readPythonDeps(),n=await(0,pythonRunner_1.runPythonWithDeps)({scriptPath:i,args:[],deps:r,analyticsRoot:this.artifactManager.getAnalyticsRoot(),requirementsPath:path.join(this.artifactManager.getScriptDir(),"requirements.txt"),execOptions:s,log:c=>this.outputChannel.appendLine(c)});a={stdout:n.stdout,stderr:n.stderr}}break;case"javascript":a=await this.runJavascriptWithCommonJsRetry(i,s);break;case"shell":a=await execFileAsync("bash",[i],s);break;default:throw new Error(`Unsupported script language: ${e.language}`)}return a.stderr&&this.outputChannel.appendLine(`[Dashboard] ${e.filename} stderr: ${a.stderr.slice(0,500)}`),a.stdout&&this.outputChannel.appendLine(`[Dashboard] ${e.filename} output: ${a.stdout.slice(0,300)}`),a}async readPythonDeps(){const e=path.join(this.artifactManager.getScriptDir(),"requirements.txt");try{return(await fs_1.promises.readFile(e,"utf-8")).split(`
11
+ `).map(i=>i.replace(/#.*$/,"").trim()).filter(i=>i.length>0)}catch{return[]}}async sendSummaryCards(e){try{const t=e??await this.queryRunner.getCostSummary(this.getActiveFilters()),i=[{label:"Total Cost",value:`$${t.totalCost.toFixed(2)}`,icon:"\u{1F4B0}"},{label:"Requests",value:t.totalRequests.toLocaleString(),icon:"\u{1F4CA}"},{label:"Avg Latency",value:`${Math.round(t.avgDurationMs)}ms`,icon:"\u26A1"},{label:"Top Model",value:this.formatModelName(t.topModel),icon:"\u{1F916}"}];this.webviewView?.webview.postMessage({type:"summaryCards",cards:i})}catch(t){this.outputChannel.appendLine(`[Dashboard] Could not load summary: ${t}`)}}formatModelName(e){return!e||e==="unknown"?"-":e.replace("claude-","").replace("gpt-","").replace("-20","").slice(0,12)}filterFleetGalleryArtifactsForActiveView(e){const t=this.getActiveViewId();return t!=="global"&&t!=="workspace"?e:e.filter(i=>{const s=i.filename.trim().toLowerCase();return s!=="agent-fleet.html"&&s!=="agent-fleet.csv"})}async loadAndSendArtifacts(){this.syncArtifactContext();const[e,t]=await Promise.all([this.artifactManager.listArtifacts(),this.loadDisabledChartsConfig()]),i=this.filterFleetGalleryArtifactsForActiveView(e);this.sendArtifactsToWebview(i,t.galleryIgnore),await this.postDisabledChartsState(t);const s=await this.artifactManager.getManifest();s&&this.webviewView?.webview.postMessage({type:"manifestUpdated",manifest:s});try{const a=new Set(e.filter(r=>r?.uri).map(r=>path.resolve(r.uri.fsPath)));for(const[r,n]of this.htmlPreviewPanels.entries())a.has(r)&&this.setPreviewPanelHtml(n,r).catch(c=>{this.outputChannel.appendLine(`[Dashboard] Failed refreshing preview for ${r}: ${c}`)})}catch(a){this.outputChannel.appendLine(`[Dashboard] Error refreshing open preview panels: ${a}`)}}getViewExportSnapshotPath(e){const t=(e??this.getActiveViewId()).trim();if(!t)return;const i=this.artifactManager.getExportDir(),s=t===this.getActiveViewId()?this.getActiveView():this.viewsConfig.views.find(a=>a.id===t);return t==="fleet"||s&&(0,viewsConfigMerge_1.viewRunsFleetWidget)(s)?path.join(i,AnalyticsDashboardProvider.FLEET_EXPORT_FILENAME):path.join(i,`${t}.json`)}resolveFleetExportReadPath(){const e=this.artifactManager.getExportDir(),t=path.join(e,AnalyticsDashboardProvider.FLEET_EXPORT_FILENAME);if((0,fs_1.existsSync)(t))return t;const i=path.join(e,AnalyticsDashboardProvider.LEGACY_FLEET_EXPORT_FILENAME);return(0,fs_1.existsSync)(i)?i:t}activeViewPushesExportSnapshot(){return(0,viewsConfigMerge_1.shouldPreserveFleetIframe)(this.getActiveView())}async postViewExportSnapshot(e){if(!this.webviewView||!(this.activeViewPushesExportSnapshot()||e?.respondWhenMissing===!0))return;const i=this.getViewExportSnapshotPath();if(!i)return;const s=i.endsWith(AnalyticsDashboardProvider.FLEET_EXPORT_FILENAME)?this.resolveFleetExportReadPath():i;if(!(!e?.respondWhenMissing&&!(0,fs_1.existsSync)(s)))try{const a=await fs_1.promises.readFile(s,"utf-8"),r=JSON.parse(a),n=(0,viewsConfigMerge_1.buildViewExportSnapshotMessages)(r);for(const c of n)this.webviewView.webview.postMessage({type:viewsConfigMerge_1.VIEW_EXPORT_SNAPSHOT_MESSAGE,payload:c.payload,meta:c.meta})}catch(a){const r=a&&typeof a=="object"&&"code"in a&&a.code==="ENOENT";if(!e?.respondWhenMissing&&r)return;this.webviewView.webview.postMessage({type:viewsConfigMerge_1.VIEW_EXPORT_SNAPSHOT_MESSAGE,payload:{generatedAt:"",totals:{}},meta:{ok:r,sessionCount:0,error:a instanceof Error?a.message:String(a),cliActive:!1,daemonHint:!1}})}}sendArtifactsToWebview(e,t){if(!this.webviewView)return;const i=e.map(a=>({webviewUri:this.webviewView.webview.asWebviewUri(a.uri).toString()+`?v=${a.modifiedAt.getTime()}`,localPath:a.uri.fsPath,filename:a.filename,size:a.size,modifiedAt:a.modifiedAt.toISOString(),type:a.type,format:a.format,generator:a.generator})),s=this.getActiveViewId();this.webviewView.webview.postMessage({type:"artifactsUpdated",artifacts:i,activeViewId:s,fleetRefreshVersion:s==="fleet"?this.fleetRefreshVersion:void 0,galleryIgnorePatterns:t}),this.postViewExportSnapshot()}async postArtifactContent(e,t){const i=e?.trim();if(!i||!this.webviewView)return;const s=path.resolve(i);if(!this.isPathWithinAllowedAnalyticsRoots(s)){this.webviewView.webview.postMessage({type:"artifactContent",path:i,format:t??"html",error:"Access denied for preview path."});return}try{const a=await fs_1.promises.stat(s);if(!a.isFile())throw new Error("Preview target is not a file.");if(a.size>2*1024*1024)throw new Error("Preview file is too large (>2MB).");const r=await fs_1.promises.readFile(s,"utf-8");this.webviewView.webview.postMessage({type:"artifactContent",path:i,format:t??"html",content:r})}catch(a){this.outputChannel.appendLine(`[Dashboard] Failed loading artifact preview for ${s}: ${a}`),this.webviewView.webview.postMessage({type:"artifactContent",path:i,format:t??"html",error:a instanceof Error?a.message:String(a)})}}async openHtmlArtifactPreview(e){const t=e?.trim();if(!t)return;const i=path.resolve(t);let s=this.htmlPreviewPanels.get(i);s?s.reveal(vscode.ViewColumn.Active):(s=vscode.window.createWebviewPanel("agentide.analyticsHtmlPreview",`Preview: ${path.basename(i)}`,vscode.ViewColumn.Active,{enableScripts:!0,retainContextWhenHidden:!0,localResourceRoots:[vscode.Uri.file(path.dirname(i))]}),this.htmlPreviewPanels.set(i,s),s.onDidDispose(()=>{this.htmlPreviewPanels.delete(i)}),s.webview.onDidReceiveMessage(this.handleMessage.bind(this))),await this.setPreviewPanelHtml(s,i)}async setPreviewPanelHtml(e,t){const i=getNonce();try{const a=(await fs_1.promises.readFile(t,"utf-8")).replace(/<script(?![^>]*\bnonce=)([^>]*)>/gi,`<script nonce="${i}"$1>`),r=`<meta http-equiv="Content-Security-Policy" content="
3123
12
  default-src 'none';
3124
- style-src ${panel.webview.cspSource} 'unsafe-inline';
3125
- script-src 'nonce-${nonce}' 'unsafe-inline';
3126
- img-src ${panel.webview.cspSource} data: https: http:;
3127
- font-src ${panel.webview.cspSource};
3128
- ">`;
3129
- // Bridge: when widget runs in preview (no iframe), it posts to window.parent (=== window).
3130
- // This script forwards those messages to the extension so card click → openSpotlightForSession works.
3131
- const previewBridge = `<script nonce="${nonce}">(function(){try{var api=typeof acquireVsCodeApi==="function"?acquireVsCodeApi():null;if(!api)return;window.addEventListener("message",function(e){var d=e.data;if(!d||typeof d!=="object")return;if(d.type==="openSpotlightForSession"&&typeof d.sessionId==="string")api.postMessage({type:"openSpotlightForSession",sessionId:d.sessionId,channelType:d.channelType||"",channelTarget:d.channelTarget||"",model:d.model||"",engine:d.engine||"",agentId:d.agentId||""});if(d.type==="refreshView")api.postMessage({type:"refreshView"});if(d.type==="refreshWidget"){var msg={type:"refreshWidget"};if(typeof d.script==="string")msg.script=d.script;if(Array.isArray(d.scripts))msg.scripts=d.scripts;api.postMessage(msg);}if(d.type==="refreshFleetWidget")api.postMessage({type:"refreshFleetWidget"});if(d.type==="disconnectFleetChannel"&&typeof d.sessionId==="string")api.postMessage({type:"disconnectFleetChannel",sessionId:d.sessionId,channelType:d.channelType||"",channelTarget:d.channelTarget||""});if(d.type==="copySessionId"&&typeof d.sessionId==="string")api.postMessage({type:"copySessionId",sessionId:d.sessionId});if(d.type==="requestClipboardText"&&typeof d.requestId==="string")api.postMessage({type:"requestClipboardText",requestId:d.requestId});if(d.type==="writeClipboardText"&&typeof d.text==="string")api.postMessage({type:"writeClipboardText",text:d.text});});}catch(err){}})();</script>`;
3132
- let html = /<head[^>]*>/i.test(withNonce)
3133
- ? withNonce.replace(/<head([^>]*)>/i, `<head$1>\n\t${csp}`)
3134
- : `<!DOCTYPE html><html><head>${csp}<meta charset="UTF-8"></head><body>${withNonce}</body></html>`;
3135
- html = html.replace(/<body(\s[^>]*)?>/i, (_m, bodyAttrs) => `<body${bodyAttrs ?? ''}>${previewBridge}`);
3136
- panel.title = `Preview: ${path.basename(normalizedTarget)}`;
3137
- panel.webview.html = html;
3138
- }
3139
- catch (err) {
3140
- this.outputChannel.appendLine(`[Dashboard] Failed setting html preview for ${normalizedTarget}: ${err}`);
3141
- panel.webview.html = `<!DOCTYPE html>
13
+ style-src ${e.webview.cspSource} 'unsafe-inline';
14
+ script-src 'nonce-${i}' 'unsafe-inline';
15
+ img-src ${e.webview.cspSource} data: https: http:;
16
+ font-src ${e.webview.cspSource};
17
+ ">`,n=`<script nonce="${i}">(function(){try{var api=typeof acquireVsCodeApi==="function"?acquireVsCodeApi():null;if(!api)return;window.addEventListener("message",function(e){var d=e.data;if(!d||typeof d!=="object")return;if(d.type==="openSpotlightForSession"&&typeof d.sessionId==="string")api.postMessage({type:"openSpotlightForSession",sessionId:d.sessionId,channelType:d.channelType||"",channelTarget:d.channelTarget||"",model:d.model||"",engine:d.engine||"",agentId:d.agentId||""});if(d.type==="refreshView")api.postMessage({type:"refreshView"});if(d.type==="refreshWidget"){var msg={type:"refreshWidget"};if(typeof d.script==="string")msg.script=d.script;if(Array.isArray(d.scripts))msg.scripts=d.scripts;api.postMessage(msg);}if(d.type==="refreshFleetWidget")api.postMessage({type:"refreshFleetWidget"});if(d.type==="disconnectFleetChannel"&&typeof d.sessionId==="string")api.postMessage({type:"disconnectFleetChannel",sessionId:d.sessionId,channelType:d.channelType||"",channelTarget:d.channelTarget||""});if(d.type==="copySessionId"&&typeof d.sessionId==="string")api.postMessage({type:"copySessionId",sessionId:d.sessionId});if(d.type==="requestClipboardText"&&typeof d.requestId==="string")api.postMessage({type:"requestClipboardText",requestId:d.requestId});if(d.type==="writeClipboardText"&&typeof d.text==="string")api.postMessage({type:"writeClipboardText",text:d.text});});}catch(err){}})();</script>`;let c=/<head[^>]*>/i.test(a)?a.replace(/<head([^>]*)>/i,`<head$1>
18
+ ${r}`):`<!DOCTYPE html><html><head>${r}<meta charset="UTF-8"></head><body>${a}</body></html>`;c=c.replace(/<body(\s[^>]*)?>/i,(d,o)=>`<body${o??""}>${n}`),e.title=`Preview: ${path.basename(t)}`,e.webview.html=c}catch(s){this.outputChannel.appendLine(`[Dashboard] Failed setting html preview for ${t}: ${s}`),e.webview.html=`<!DOCTYPE html>
3142
19
  <html>
3143
20
  <body style="font-family: sans-serif; padding: 16px;">
3144
21
  <h3>Unable to preview HTML artifact</h3>
3145
- <p>${err instanceof Error ? err.message : String(err)}</p>
22
+ <p>${s instanceof Error?s.message:String(s)}</p>
3146
23
  </body>
3147
- </html>`;
3148
- }
3149
- }
3150
- enqueueArtifactRefresh() {
3151
- if (this.isBatchingArtifactUpdates) {
3152
- this.pendingArtifactRefresh = true;
3153
- return this.artifactRefreshQueue;
3154
- }
3155
- this.artifactRefreshQueue = this.artifactRefreshQueue
3156
- .then(() => this.loadAndSendArtifacts())
3157
- .catch(err => {
3158
- this.outputChannel.appendLine(`[Dashboard] Incremental artifact refresh failed: ${err}`);
3159
- });
3160
- return this.artifactRefreshQueue;
3161
- }
3162
- startSmoothRefresh(options) {
3163
- this.isBatchingArtifactUpdates = options?.batchArtifactUpdates ?? true;
3164
- this.pendingArtifactRefresh = false;
3165
- postToWebview(this.webviewView?.webview, { type: 'dataFresh' });
3166
- this.postRefreshStatus('refreshStarted', options?.refresh);
3167
- }
3168
- async finishSmoothRefresh(options) {
3169
- const notifyFinished = options?.notifyFinished ?? true;
3170
- this.isBatchingArtifactUpdates = false;
3171
- if (this.pendingArtifactRefresh) {
3172
- this.pendingArtifactRefresh = false;
3173
- await this.loadAndSendArtifacts();
3174
- }
3175
- if (notifyFinished) {
3176
- this.postRefreshStatus('refreshFinished', options?.refresh);
3177
- }
3178
- }
3179
- async resizeSpotlightSidebar(direction) {
3180
- const sizeCommand = direction === 'expand'
3181
- ? 'workbench.action.increaseViewSize'
3182
- : 'workbench.action.decreaseViewSize';
3183
- const widthCommand = direction === 'expand'
3184
- ? 'workbench.action.increaseViewWidth'
3185
- : 'workbench.action.decreaseViewWidth';
3186
- const delayForFocus = async (ms = 50) => new Promise(resolve => setTimeout(resolve, ms));
3187
- try {
3188
- await vscode.commands.executeCommand('agentide.analyticsView.focus');
3189
- await delayForFocus();
3190
- await vscode.commands.executeCommand(sizeCommand);
3191
- return;
3192
- }
3193
- catch (err) {
3194
- this.outputChannel.appendLine(`[Dashboard] Spotlight resize primary path failed (${direction}): ${err}`);
3195
- }
3196
- try {
3197
- await vscode.commands.executeCommand('agentide.analyticsView.focus');
3198
- await delayForFocus();
3199
- await vscode.commands.executeCommand(widthCommand);
3200
- return;
3201
- }
3202
- catch (err) {
3203
- this.outputChannel.appendLine(`[Dashboard] Spotlight resize width fallback failed (${direction}): ${err}`);
3204
- }
3205
- await vscode.commands.executeCommand('workbench.action.focusSideBar');
3206
- await delayForFocus();
3207
- await vscode.commands.executeCommand(sizeCommand);
3208
- }
3209
- // -------------------------------------------------------------------------
3210
- // Message Handling
3211
- // -------------------------------------------------------------------------
3212
- async handleMessage(message) {
3213
- switch (message.type) {
3214
- case 'requestArtifacts':
3215
- await this.loadAndSendArtifacts();
3216
- break;
3217
- case 'requestSummary':
3218
- await this.sendSummaryCards();
3219
- break;
3220
- case 'requestFilters':
3221
- await this.postFilterState();
3222
- break;
3223
- case 'requestViews':
3224
- // Re-read views.yaml so external edits appear without requiring window reload.
3225
- await this.loadAndPostViewsState();
3226
- await this.postPanelState('default');
3227
- break;
3228
- case 'setActiveView': {
3229
- const switched = await this.setActiveView(message.viewId);
3230
- // Always update the webview with the correct view, even if the switch failed.
3231
- // This prevents the dropdown and provider state from being out of sync.
3232
- await this.postViewsState();
3233
- if (!switched) {
3234
- break;
3235
- }
3236
- this.syncArtifactContext();
3237
- await this.postPanelState('default');
3238
- await this.refreshAfterViewChange();
3239
- await this.refreshChartsForActiveViewAfterSwitch();
3240
- break;
3241
- }
3242
- case 'createView': {
3243
- const created = await this.runViewConfigMutation(() => this.createView(message.name));
3244
- if (!created) {
3245
- break;
3246
- }
3247
- await this.postViewsState();
3248
- await this.postPanelState('default');
3249
- await this.refreshAfterViewChange();
3250
- await this.refreshChartsForActiveViewAfterSwitch();
3251
- break;
3252
- }
3253
- case 'duplicateView': {
3254
- const duplicated = await this.runViewConfigMutation(() => this.duplicateView(message.viewId));
3255
- if (!duplicated) {
3256
- break;
3257
- }
3258
- await this.setActiveView(duplicated.id);
3259
- await this.postViewsState();
3260
- await this.postPanelState('default');
3261
- await this.refreshAfterViewChange();
3262
- await this.refreshChartsForActiveViewAfterSwitch();
3263
- break;
3264
- }
3265
- case 'renameView': {
3266
- const renamed = await this.renameView(message.viewId, message.name);
3267
- if (!renamed) {
3268
- break;
3269
- }
3270
- await this.postViewsState();
3271
- break;
3272
- }
3273
- case 'deleteView': {
3274
- const removed = await this.runViewConfigMutation(() => this.deleteView(message.viewId));
3275
- if (!removed) {
3276
- break;
3277
- }
3278
- await this.postViewsState();
3279
- await this.postPanelState('default');
3280
- await this.refreshAfterViewChange();
3281
- await this.refreshChartsForActiveViewAfterSwitch();
3282
- break;
3283
- }
3284
- case 'requestRenameView': {
3285
- const viewId = message.viewId ?? this.getActiveViewId();
3286
- const view = viewId ? this.findViewById(viewId) : undefined;
3287
- if (!view || view.system) {
3288
- break;
3289
- }
3290
- const name = await vscode.window.showInputBox({
3291
- title: 'Rename view',
3292
- value: view.name,
3293
- prompt: 'Enter the new view name',
3294
- validateInput: (value) => {
3295
- const t = value?.trim();
3296
- if (!t) {
3297
- return 'Name cannot be empty';
3298
- }
3299
- return null;
3300
- },
3301
- });
3302
- if (name !== undefined && name.trim()) {
3303
- const renamed = await this.renameView(view.id, name.trim());
3304
- if (renamed) {
3305
- await this.postViewsState();
3306
- }
3307
- }
3308
- break;
3309
- }
3310
- case 'requestDeleteView': {
3311
- const viewId = message.viewId ?? this.getActiveViewId();
3312
- const view = viewId ? this.findViewById(viewId) : undefined;
3313
- if (!view || view.system) {
3314
- break;
3315
- }
3316
- const action = await vscode.window.showWarningMessage(`Delete view "${view.name}"?`, { modal: true }, 'Delete', 'Cancel');
3317
- if (action === 'Delete') {
3318
- const removed = await this.deleteView(view.id);
3319
- if (removed) {
3320
- await this.postViewsState();
3321
- await this.postPanelState('default');
3322
- await this.refreshAfterViewChange();
3323
- await this.refreshChartsForActiveViewAfterSwitch();
3324
- }
3325
- }
3326
- break;
3327
- }
3328
- case 'editViewScripts':
3329
- await this.editViewScripts(message.viewId);
3330
- break;
3331
- case 'refreshAllViews':
3332
- await this.refreshAllViews();
3333
- break;
3334
- case 'refreshView':
3335
- await this.refreshDashboardData();
3336
- await this.refreshCharts({ viewId: this.getActiveViewId() });
3337
- break;
3338
- case 'refreshWidget': {
3339
- const normalizedScripts = [
3340
- typeof message.script === 'string' ? message.script : '',
3341
- ...(Array.isArray(message.scripts) ? message.scripts.filter((s) => typeof s === 'string') : []),
3342
- ]
3343
- .map(s => s.trim())
3344
- .filter(Boolean);
3345
- await this.refreshWidgetScriptsOnly(normalizedScripts);
3346
- break;
3347
- }
3348
- case 'refreshFleetWidget':
3349
- await this.refreshWidgetScriptsOnly(['agent-fleet-widget.js']);
3350
- break;
3351
- case 'shareView': {
3352
- const viewId = message.viewId?.trim() ?? '';
3353
- const view = viewId ? this.findViewById(viewId) : undefined;
3354
- if (!viewId || !view || view.system) {
3355
- void vscode.window.showInformationMessage('Built-in Spotlight views cannot be shared. Duplicate the view or create a custom view first.');
3356
- break;
3357
- }
3358
- const bundle = message.bundle === 'scripts-only' ? 'scripts-only' : 'full';
3359
- await vscode.commands.executeCommand('agentide.spotlight.publish', viewId, { bundle });
3360
- break;
3361
- }
3362
- case 'installView':
3363
- await vscode.commands.executeCommand('agentide.store.find', { typeFilter: 'view' });
3364
- break;
3365
- case 'expandSidebar': {
3366
- await this.resizeSpotlightSidebar('expand');
3367
- break;
3368
- }
3369
- case 'shrinkSidebar': {
3370
- await this.resizeSpotlightSidebar('shrink');
3371
- break;
3372
- }
3373
- case 'requestPanelState':
3374
- await this.postPanelState(message.panelId);
3375
- break;
3376
- case 'savePanelState':
3377
- await this.savePanelState(message.panelId, message.state);
3378
- break;
3379
- case 'requestArtifactContent':
3380
- await this.postArtifactContent(message.path, message.format);
3381
- break;
3382
- case 'requestViewExportSnapshot':
3383
- await this.postViewExportSnapshot({ respondWhenMissing: true });
3384
- break;
3385
- case 'openChat':
3386
- await this.openVsCodeChat();
3387
- break;
3388
- case 'openChatWithPrompt':
3389
- await this.openVsCodeChat({ prompt: message.prompt });
3390
- break;
3391
- case 'copySessionId': {
3392
- const sessionId = message.sessionId?.trim() ?? '';
3393
- if (!sessionId) {
3394
- break;
3395
- }
3396
- await vscode.env.clipboard.writeText(sessionId);
3397
- this.webviewView?.webview.postMessage({
3398
- type: 'sessionCopied',
3399
- sessionId,
3400
- });
3401
- break;
3402
- }
3403
- case 'requestClipboardText': {
3404
- const requestId = message.requestId?.trim() ?? '';
3405
- if (!requestId) {
3406
- break;
3407
- }
3408
- const text = await vscode.env.clipboard.readText();
3409
- const responsePayload = {
3410
- type: 'clipboardText',
3411
- requestId,
3412
- text,
3413
- };
3414
- this.webviewView?.webview.postMessage(responsePayload);
3415
- for (const panel of this.htmlPreviewPanels.values()) {
3416
- void panel.webview.postMessage(responsePayload);
3417
- }
3418
- break;
3419
- }
3420
- case 'writeClipboardText': {
3421
- const text = typeof message.text === 'string' ? message.text : '';
3422
- await vscode.env.clipboard.writeText(text);
3423
- break;
3424
- }
3425
- case 'updateFleetCardTitle':
3426
- await this.handleFleetCardTitleUpdate(message.sourceId, message.sessionId, message.cardTitle);
3427
- break;
3428
- case 'disconnectFleetChannel':
3429
- await this.handleFleetDisconnectRequest(message.sessionId, message.channelType, message.channelTarget);
3430
- break;
3431
- case 'openSpotlightForSession':
3432
- await this.openSpotlightForSession(message.sessionId, message.channelType, message.channelTarget, message.model, message.engine, message.agentId);
3433
- break;
3434
- case 'openArtifact': {
3435
- const targetPath = message.path?.trim() ?? '';
3436
- if (!targetPath) {
3437
- break;
3438
- }
3439
- const normalizedTarget = path.resolve(targetPath);
3440
- if (normalizedTarget.toLowerCase().endsWith('.html')) {
3441
- await this.openHtmlArtifactPreview(normalizedTarget);
3442
- break;
3443
- }
3444
- await vscode.commands.executeCommand('vscode.open', vscode.Uri.file(normalizedTarget));
3445
- break;
3446
- }
3447
- case 'deleteArtifact':
3448
- try {
3449
- const artifactPath = message.path.trim();
3450
- if (artifactPath &&
3451
- this.isPathWithinDir(artifactPath, this.artifactManager.getChartDir())) {
3452
- await this.disableChartByFilename(path.basename(artifactPath));
3453
- }
3454
- await vscode.workspace.fs.delete(vscode.Uri.file(message.path));
3455
- this.webviewView?.webview.postMessage({
3456
- type: 'artifactDeleted',
3457
- path: message.path,
3458
- });
3459
- }
3460
- catch (err) {
3461
- this.outputChannel.appendLine(`[Dashboard] Failed to delete artifact: ${err}`);
3462
- }
3463
- break;
3464
- case 'cancelGeneration':
3465
- this.generationAbortController?.abort();
3466
- break;
3467
- case 'retryGeneration':
3468
- await this.refreshCharts();
3469
- break;
3470
- case 'refreshDefaults':
3471
- case 'refreshCharts':
3472
- await this.refreshDashboardData();
3473
- await this.refreshCharts({ viewId: this.getActiveViewId() });
3474
- break;
3475
- case 'updateFilters':
3476
- this.agentFilterId = message.agentId?.trim() || undefined;
3477
- await this.refreshAfterViewChange();
3478
- break;
3479
- case 'resetChartsHome':
3480
- await this.resetChartsAndScripts('home');
3481
- break;
3482
- case 'resetChartsWorkspace':
3483
- await this.resetChartsAndScripts('workspace');
3484
- break;
3485
- case 'reenableAllCharts':
3486
- await this.reenableAllDisabledCharts();
3487
- break;
3488
- }
3489
- }
3490
- // -------------------------------------------------------------------------
3491
- // Reset & Re-enable Operations
3492
- // -------------------------------------------------------------------------
3493
- /**
3494
- * Reset charts and scripts for either the home analytics folder or the
3495
- * current workspace. This deletes generated charts, user scripts, manifest,
3496
- * panel state, and disabled-charts config, then regenerates defaults.
3497
- */
3498
- async resetChartsAndScripts(target) {
3499
- const analyticsRoot = this.artifactManager.getAnalyticsRoot();
3500
- this.outputChannel.appendLine(`[Dashboard] Reset requested for: ${target}`);
3501
- try {
3502
- // Determine which directories to clear
3503
- const dirsToDelete = [];
3504
- if (target === 'home') {
3505
- // Home = global analytics root (under ~/.agent-hippo/analytics)
3506
- // Clear: panel/, views/, panel-state/, disabled-charts.yaml.
3507
- dirsToDelete.push(path.join(analyticsRoot, 'panel'), path.join(analyticsRoot, 'views'), path.join(analyticsRoot, 'panel-state'));
3508
- }
3509
- else {
3510
- // Workspace reset = current view root only (views/<viewId>/)
3511
- const viewRoot = this.getViewRoot();
3512
- dirsToDelete.push(path.join(viewRoot, 'charts'), path.join(viewRoot, 'reports'), path.join(viewRoot, 'exports'));
3513
- // Also delete the manifest for this view
3514
- const manifestPath = path.join(viewRoot, 'manifest.json');
3515
- try {
3516
- await fs_1.promises.unlink(manifestPath);
3517
- }
3518
- catch { /* ok if missing */ }
3519
- }
3520
- // Delete directories recursively
3521
- for (const dir of dirsToDelete) {
3522
- try {
3523
- await fs_1.promises.rm(dir, { recursive: true, force: true });
3524
- this.outputChannel.appendLine(`[Dashboard] Deleted: ${dir}`);
3525
- }
3526
- catch (err) {
3527
- this.outputChannel.appendLine(`[Dashboard] Could not delete ${dir}: ${err}`);
3528
- }
3529
- }
3530
- // For home reset, also delete the disabled-charts.yaml config
3531
- if (target === 'home') {
3532
- const configPath = this.getDisabledChartsConfigPath();
3533
- try {
3534
- await fs_1.promises.unlink(configPath);
3535
- }
3536
- catch { /* ok if missing */ }
3537
- }
3538
- // Re-seed workspace scripts and regenerate defaults
3539
- await this.seedWorkspaceScripts();
3540
- await this.artifactManager.ensureArtifactDir();
3541
- // Refresh the webview
3542
- this.queryRunner.invalidateCache();
3543
- const bootstrapPromise = this.fetchDashboardBootstrapData();
3544
- await this.loadAndSendArtifacts();
3545
- const bootstrap = await bootstrapPromise;
3546
- await this.postFilterState(bootstrap?.agentIds);
3547
- this.triggerSummaryCardsUpdate(bootstrap?.summary);
3548
- // Run the full script set so all widgets are generated after reset
3549
- await this.refreshCharts();
3550
- vscode.window.showInformationMessage(target === 'home'
3551
- ? 'Analytics reset complete (home folder)'
3552
- : 'Analytics reset complete (workspace)');
3553
- }
3554
- catch (err) {
3555
- this.outputChannel.appendLine(`[Dashboard] Reset failed: ${err}`);
3556
- vscode.window.showErrorMessage(`Reset failed: ${err instanceof Error ? err.message : String(err)}`);
3557
- }
3558
- }
3559
- /**
3560
- * Get the current view root directory path (e.g. .../analytics/views/fleet).
3561
- */
3562
- getViewRoot() {
3563
- return this.artifactManager.resolveViewRoot({
3564
- scope: this.getActiveView()?.scope ?? 'global',
3565
- projectId: this.resolveWorkspaceProjectId(),
3566
- agentId: this.agentFilterId,
3567
- viewId: this.getActiveViewId(),
3568
- });
3569
- }
3570
- /**
3571
- * Re-enable all disabled charts/scripts by clearing those lists in disabled-charts.yaml.
3572
- * Also clears any hidden state in the panel-state.
3573
- */
3574
- async reenableAllDisabledCharts() {
3575
- this.outputChannel.appendLine('[Dashboard] Re-enabling all disabled charts');
3576
- try {
3577
- // Clear disabled script/chart blocks while preserving galleryIgnore patterns.
3578
- const disabledConfig = await this.loadDisabledChartsConfig();
3579
- await this.saveDisabledChartsConfig({
3580
- scriptsByView: {},
3581
- charts: [],
3582
- galleryIgnore: disabledConfig.galleryIgnore,
3583
- });
3584
- await this.postDisabledChartsState({
3585
- ...disabledConfig,
3586
- scriptsByView: {},
3587
- charts: [],
3588
- });
3589
- // Also clear hidden state in panel state for the default panel
3590
- const statePath = this.getPanelStatePath('default');
3591
- try {
3592
- const raw = await fs_1.promises.readFile(statePath, 'utf-8');
3593
- const parsed = JSON.parse(raw);
3594
- if (parsed.charts && typeof parsed.charts === 'object') {
3595
- // Set all hidden flags to false
3596
- let changed = false;
3597
- for (const key of Object.keys(parsed.charts)) {
3598
- if (parsed.charts[key]?.hidden === true) {
3599
- parsed.charts[key].hidden = false;
3600
- changed = true;
3601
- }
3602
- }
3603
- if (changed) {
3604
- await fs_1.promises.writeFile(statePath, `${JSON.stringify(parsed, null, 2)}\n`, 'utf-8');
3605
- await this.postPanelState('default');
3606
- }
3607
- }
3608
- }
3609
- catch { /* ok if missing or malformed */ }
3610
- // Refresh the gallery
3611
- await this.loadAndSendArtifacts();
3612
- vscode.window.showInformationMessage('All hidden charts have been re-enabled');
3613
- }
3614
- catch (err) {
3615
- this.outputChannel.appendLine(`[Dashboard] Re-enable charts failed: ${err}`);
3616
- vscode.window.showErrorMessage(`Failed to re-enable charts: ${err instanceof Error ? err.message : String(err)}`);
3617
- }
3618
- }
3619
- // -------------------------------------------------------------------------
3620
- // VS Code Chat Integration
3621
- // -------------------------------------------------------------------------
3622
- buildAnalyticsPrimer(userPrompt) {
3623
- return `Use spotlight skills as needed. ${userPrompt?.trim() || ''}`;
3624
- }
3625
- async openVsCodeChat(options) {
3626
- this.syncArtifactContext();
3627
- // Ensure artifact directories exist before opening chat
3628
- await this.artifactManager.ensureArtifactDir();
3629
- // Set environment variables so agent knows where to save charts and scripts
3630
- process.env.AGENTIDE_CHART_DIR = this.artifactManager.getChartDir();
3631
- process.env.AGENTIDE_REPORT_DIR = this.artifactManager.getReportDir();
3632
- process.env.AGENTIDE_EXPORT_DIR = this.artifactManager.getExportDir();
3633
- process.env.AGENTIDE_IMAGE_DIR = this.artifactManager.getImageDir();
3634
- process.env.AGENTIDE_ASSET_DIR = this.artifactManager.getAssetDir();
3635
- process.env.AGENTIDE_VIEW_ROOT = this.artifactManager.getViewRoot();
3636
- process.env.AGENTIDE_VIEW_ID = this.getActiveViewId();
3637
- process.env.AGENTIDE_SCRIPT_DIR = this.artifactManager.getScriptDir();
3638
- process.env.AGENTIDE_TRACES_DIR = path.join((0, index_1.getAgentideHome)(), 'analytics', 'traces');
3639
- const filters = this.getActiveFilters();
3640
- process.env.AGENTIDE_ANALYTICS_SCOPE = filters.scope ?? 'global';
3641
- process.env.AGENTIDE_ANALYTICS_PROJECT_ID = filters.scope === 'workspace'
3642
- ? (filters.projectId ?? '')
3643
- : '';
3644
- process.env.AGENTIDE_ANALYTICS_AGENT_ID = filters.agentId ?? '';
3645
- // Always open a dedicated session for analytics flows.
3646
- await vscode.commands.executeCommand('workbench.action.chat.newChat', {
3647
- agentMode: true,
3648
- });
3649
- const fullPrompt = this.buildAnalyticsPrimer(options?.prompt);
3650
- const sendImmediately = options?.sendImmediately ?? false;
3651
- // Open chat without participant mention and prefill by default.
3652
- // NOTE: the open command does not currently expose a direct title field,
3653
- // so we lead with "Agent Analytics" in the prompt and use a fresh session.
3654
- await vscode.commands.executeCommand('workbench.action.chat.open', {
3655
- mode: 'agent',
3656
- query: fullPrompt,
3657
- isPartialQuery: !sendImmediately,
3658
- });
3659
- }
3660
- // -------------------------------------------------------------------------
3661
- // HTML Content
3662
- // -------------------------------------------------------------------------
3663
- getWebviewPath() {
3664
- // Prefer out/ (used after full compile); fall back to src/ when only tsc watch was run so panel.js and bridge load
3665
- const outPath = path.join(this.extensionContext.extensionPath, 'out', 'analytics', 'dashboard', 'webview', 'panel.js');
3666
- return (0, fs_1.existsSync)(outPath) ? 'out/analytics/dashboard/webview' : 'src/analytics/dashboard/webview';
3667
- }
3668
- /** Share toolbar control; injected when user's panel.html lacks it (e.g. old seeded copy). */
3669
- getShareViewButtonFragment() {
3670
- return `
24
+ </html>`}}enqueueArtifactRefresh(){return this.isBatchingArtifactUpdates?(this.pendingArtifactRefresh=!0,this.artifactRefreshQueue):(this.artifactRefreshQueue=this.artifactRefreshQueue.then(()=>this.loadAndSendArtifacts()).catch(e=>{this.outputChannel.appendLine(`[Dashboard] Incremental artifact refresh failed: ${e}`)}),this.artifactRefreshQueue)}startSmoothRefresh(e){this.isBatchingArtifactUpdates=e?.batchArtifactUpdates??!0,this.pendingArtifactRefresh=!1,postToWebview(this.webviewView?.webview,{type:"dataFresh"}),this.postRefreshStatus("refreshStarted",e?.refresh)}async finishSmoothRefresh(e){const t=e?.notifyFinished??!0;this.isBatchingArtifactUpdates=!1,this.pendingArtifactRefresh&&(this.pendingArtifactRefresh=!1,await this.loadAndSendArtifacts()),t&&this.postRefreshStatus("refreshFinished",e?.refresh)}async resizeSpotlightSidebar(e){const t=e==="expand"?"workbench.action.increaseViewSize":"workbench.action.decreaseViewSize",i=e==="expand"?"workbench.action.increaseViewWidth":"workbench.action.decreaseViewWidth",s=async(a=50)=>new Promise(r=>setTimeout(r,a));try{await vscode.commands.executeCommand("agentide.analyticsView.focus"),await s(),await vscode.commands.executeCommand(t);return}catch(a){this.outputChannel.appendLine(`[Dashboard] Spotlight resize primary path failed (${e}): ${a}`)}try{await vscode.commands.executeCommand("agentide.analyticsView.focus"),await s(),await vscode.commands.executeCommand(i);return}catch(a){this.outputChannel.appendLine(`[Dashboard] Spotlight resize width fallback failed (${e}): ${a}`)}await vscode.commands.executeCommand("workbench.action.focusSideBar"),await s(),await vscode.commands.executeCommand(t)}async handleMessage(e){switch(e.type){case"requestArtifacts":await this.loadAndSendArtifacts();break;case"requestSummary":await this.sendSummaryCards();break;case"requestFilters":await this.postFilterState();break;case"requestViews":await this.loadAndPostViewsState(),await this.postPanelState("default");break;case"setActiveView":{const t=await this.setActiveView(e.viewId);if(await this.postViewsState(),!t)break;this.syncArtifactContext(),await this.postPanelState("default"),await this.refreshAfterViewChange(),await this.refreshChartsForActiveViewAfterSwitch();break}case"createView":{if(!await this.runViewConfigMutation(()=>this.createView(e.name)))break;await this.postViewsState(),await this.postPanelState("default"),await this.refreshAfterViewChange(),await this.refreshChartsForActiveViewAfterSwitch();break}case"duplicateView":{const t=await this.runViewConfigMutation(()=>this.duplicateView(e.viewId));if(!t)break;await this.setActiveView(t.id),await this.postViewsState(),await this.postPanelState("default"),await this.refreshAfterViewChange(),await this.refreshChartsForActiveViewAfterSwitch();break}case"renameView":{if(!await this.renameView(e.viewId,e.name))break;await this.postViewsState();break}case"deleteView":{if(!await this.runViewConfigMutation(()=>this.deleteView(e.viewId)))break;await this.postViewsState(),await this.postPanelState("default"),await this.refreshAfterViewChange(),await this.refreshChartsForActiveViewAfterSwitch();break}case"requestRenameView":{const t=e.viewId??this.getActiveViewId(),i=t?this.findViewById(t):void 0;if(!i||i.system)break;const s=await vscode.window.showInputBox({title:"Rename view",value:i.name,prompt:"Enter the new view name",validateInput:a=>a?.trim()?null:"Name cannot be empty"});s!==void 0&&s.trim()&&await this.renameView(i.id,s.trim())&&await this.postViewsState();break}case"requestDeleteView":{const t=e.viewId??this.getActiveViewId(),i=t?this.findViewById(t):void 0;if(!i||i.system)break;await vscode.window.showWarningMessage(`Delete view "${i.name}"?`,{modal:!0},"Delete","Cancel")==="Delete"&&await this.deleteView(i.id)&&(await this.postViewsState(),await this.postPanelState("default"),await this.refreshAfterViewChange(),await this.refreshChartsForActiveViewAfterSwitch());break}case"editViewScripts":await this.editViewScripts(e.viewId);break;case"refreshAllViews":await this.refreshAllViews();break;case"refreshView":await this.refreshDashboardData(),await this.refreshCharts({viewId:this.getActiveViewId()});break;case"refreshWidget":{const t=[typeof e.script=="string"?e.script:"",...Array.isArray(e.scripts)?e.scripts.filter(i=>typeof i=="string"):[]].map(i=>i.trim()).filter(Boolean);await this.refreshWidgetScriptsOnly(t);break}case"refreshFleetWidget":await this.refreshWidgetScriptsOnly(["agent-fleet-widget.js"]);break;case"shareView":{const t=e.viewId?.trim()??"",i=t?this.findViewById(t):void 0;if(!t||!i||i.system){vscode.window.showInformationMessage("Built-in Spotlight views cannot be shared. Duplicate the view or create a custom view first.");break}const s=e.bundle==="scripts-only"?"scripts-only":"full";await vscode.commands.executeCommand("agentide.spotlight.publish",t,{bundle:s});break}case"installView":await vscode.commands.executeCommand("agentide.store.find",{typeFilter:"view"});break;case"expandSidebar":{await this.resizeSpotlightSidebar("expand");break}case"shrinkSidebar":{await this.resizeSpotlightSidebar("shrink");break}case"requestPanelState":await this.postPanelState(e.panelId);break;case"savePanelState":await this.savePanelState(e.panelId,e.state);break;case"requestArtifactContent":await this.postArtifactContent(e.path,e.format);break;case"requestViewExportSnapshot":await this.postViewExportSnapshot({respondWhenMissing:!0});break;case"openChat":await this.openVsCodeChat();break;case"openChatWithPrompt":await this.openVsCodeChat({prompt:e.prompt});break;case"copySessionId":{const t=e.sessionId?.trim()??"";if(!t)break;await vscode.env.clipboard.writeText(t),this.webviewView?.webview.postMessage({type:"sessionCopied",sessionId:t});break}case"requestClipboardText":{const t=e.requestId?.trim()??"";if(!t)break;const i=await vscode.env.clipboard.readText(),s={type:"clipboardText",requestId:t,text:i};this.webviewView?.webview.postMessage(s);for(const a of this.htmlPreviewPanels.values())a.webview.postMessage(s);break}case"writeClipboardText":{const t=typeof e.text=="string"?e.text:"";await vscode.env.clipboard.writeText(t);break}case"updateFleetCardTitle":await this.handleFleetCardTitleUpdate(e.sourceId,e.sessionId,e.cardTitle);break;case"disconnectFleetChannel":await this.handleFleetDisconnectRequest(e.sessionId,e.channelType,e.channelTarget);break;case"openSpotlightForSession":await this.openSpotlightForSession(e.sessionId,e.channelType,e.channelTarget,e.model,e.engine,e.agentId);break;case"openArtifact":{const t=e.path?.trim()??"";if(!t)break;const i=path.resolve(t);if(i.toLowerCase().endsWith(".html")){await this.openHtmlArtifactPreview(i);break}await vscode.commands.executeCommand("vscode.open",vscode.Uri.file(i));break}case"deleteArtifact":try{const t=e.path.trim();t&&this.isPathWithinDir(t,this.artifactManager.getChartDir())&&await this.disableChartByFilename(path.basename(t)),await vscode.workspace.fs.delete(vscode.Uri.file(e.path)),this.webviewView?.webview.postMessage({type:"artifactDeleted",path:e.path})}catch(t){this.outputChannel.appendLine(`[Dashboard] Failed to delete artifact: ${t}`)}break;case"cancelGeneration":this.generationAbortController?.abort();break;case"retryGeneration":await this.refreshCharts();break;case"refreshDefaults":case"refreshCharts":await this.refreshDashboardData(),await this.refreshCharts({viewId:this.getActiveViewId()});break;case"updateFilters":this.agentFilterId=e.agentId?.trim()||void 0,await this.refreshAfterViewChange();break;case"resetChartsHome":await this.resetChartsAndScripts("home");break;case"resetChartsWorkspace":await this.resetChartsAndScripts("workspace");break;case"reenableAllCharts":await this.reenableAllDisabledCharts();break}}async resetChartsAndScripts(e){const t=this.artifactManager.getAnalyticsRoot();this.outputChannel.appendLine(`[Dashboard] Reset requested for: ${e}`);try{const i=[];if(e==="home")i.push(path.join(t,"panel"),path.join(t,"views"),path.join(t,"panel-state"));else{const r=this.getViewRoot();i.push(path.join(r,"charts"),path.join(r,"reports"),path.join(r,"exports"));const n=path.join(r,"manifest.json");try{await fs_1.promises.unlink(n)}catch{}}for(const r of i)try{await fs_1.promises.rm(r,{recursive:!0,force:!0}),this.outputChannel.appendLine(`[Dashboard] Deleted: ${r}`)}catch(n){this.outputChannel.appendLine(`[Dashboard] Could not delete ${r}: ${n}`)}if(e==="home"){const r=this.getDisabledChartsConfigPath();try{await fs_1.promises.unlink(r)}catch{}}await this.seedWorkspaceScripts(),await this.artifactManager.ensureArtifactDir(),this.queryRunner.invalidateCache();const s=this.fetchDashboardBootstrapData();await this.loadAndSendArtifacts();const a=await s;await this.postFilterState(a?.agentIds),this.triggerSummaryCardsUpdate(a?.summary),await this.refreshCharts(),vscode.window.showInformationMessage(e==="home"?"Analytics reset complete (home folder)":"Analytics reset complete (workspace)")}catch(i){this.outputChannel.appendLine(`[Dashboard] Reset failed: ${i}`),vscode.window.showErrorMessage(`Reset failed: ${i instanceof Error?i.message:String(i)}`)}}getViewRoot(){return this.artifactManager.resolveViewRoot({scope:this.getActiveView()?.scope??"global",projectId:this.resolveWorkspaceProjectId(),agentId:this.agentFilterId,viewId:this.getActiveViewId()})}async reenableAllDisabledCharts(){this.outputChannel.appendLine("[Dashboard] Re-enabling all disabled charts");try{const e=await this.loadDisabledChartsConfig();await this.saveDisabledChartsConfig({scriptsByView:{},charts:[],galleryIgnore:e.galleryIgnore}),await this.postDisabledChartsState({...e,scriptsByView:{},charts:[]});const t=this.getPanelStatePath("default");try{const i=await fs_1.promises.readFile(t,"utf-8"),s=JSON.parse(i);if(s.charts&&typeof s.charts=="object"){let a=!1;for(const r of Object.keys(s.charts))s.charts[r]?.hidden===!0&&(s.charts[r].hidden=!1,a=!0);a&&(await fs_1.promises.writeFile(t,`${JSON.stringify(s,null,2)}
25
+ `,"utf-8"),await this.postPanelState("default"))}}catch{}await this.loadAndSendArtifacts(),vscode.window.showInformationMessage("All hidden charts have been re-enabled")}catch(e){this.outputChannel.appendLine(`[Dashboard] Re-enable charts failed: ${e}`),vscode.window.showErrorMessage(`Failed to re-enable charts: ${e instanceof Error?e.message:String(e)}`)}}buildAnalyticsPrimer(e){return`Use spotlight skills as needed. ${e?.trim()||""}`}async openVsCodeChat(e){this.syncArtifactContext(),await this.artifactManager.ensureArtifactDir(),process.env.AGENTIDE_CHART_DIR=this.artifactManager.getChartDir(),process.env.AGENTIDE_REPORT_DIR=this.artifactManager.getReportDir(),process.env.AGENTIDE_EXPORT_DIR=this.artifactManager.getExportDir(),process.env.AGENTIDE_IMAGE_DIR=this.artifactManager.getImageDir(),process.env.AGENTIDE_ASSET_DIR=this.artifactManager.getAssetDir(),process.env.AGENTIDE_VIEW_ROOT=this.artifactManager.getViewRoot(),process.env.AGENTIDE_VIEW_ID=this.getActiveViewId(),process.env.AGENTIDE_SCRIPT_DIR=this.artifactManager.getScriptDir(),process.env.AGENTIDE_TRACES_DIR=path.join((0,index_1.getAgentideHome)(),"analytics","traces");const t=this.getActiveFilters();process.env.AGENTIDE_ANALYTICS_SCOPE=t.scope??"global",process.env.AGENTIDE_ANALYTICS_PROJECT_ID=t.scope==="workspace"?t.projectId??"":"",process.env.AGENTIDE_ANALYTICS_AGENT_ID=t.agentId??"",await vscode.commands.executeCommand("workbench.action.chat.newChat",{agentMode:!0});const i=this.buildAnalyticsPrimer(e?.prompt),s=e?.sendImmediately??!1;await vscode.commands.executeCommand("workbench.action.chat.open",{mode:"agent",query:i,isPartialQuery:!s})}getWebviewPath(){const e=path.join(this.extensionContext.extensionPath,"out","analytics","dashboard","webview","panel.js");return(0,fs_1.existsSync)(e)?"out/analytics/dashboard/webview":"src/analytics/dashboard/webview"}getShareViewButtonFragment(){return`
3671
26
  <button id="share-view-btn" class="toolbar-btn icon-only" title="Publish current view to store (full bundle)" aria-label="Publish current view to store (full bundle)">
3672
27
  <span class="btn-icon" aria-hidden="true">&#8593;</span>
3673
- </button>`;
3674
- }
3675
- /** Install toolbar control; injected when user's panel.html lacks it (e.g. old seeded copy). */
3676
- getInstallViewButtonFragment() {
3677
- return `
28
+ </button>`}getInstallViewButtonFragment(){return`
3678
29
  <button id="install-view-btn" class="toolbar-btn icon-only" title="Install Spotlight view from store" aria-label="Install Spotlight view from store">
3679
30
  <span class="btn-icon" aria-hidden="true">&#8595;</span>
3680
- </button>`;
3681
- }
3682
- getShareScriptsOnlyMenuFragment() {
3683
- return `
31
+ </button>`}getShareScriptsOnlyMenuFragment(){return`
3684
32
  <button id="share-scripts-only-menu-item" class="dropdown-item" role="menuitem" data-action="share-scripts-only">
3685
33
  <span class="dropdown-icon">&#8593;</span>
3686
34
  Share scripts only to store
3687
- </button>`;
3688
- }
3689
- patchPanelHtmlForShareButton(panelHtml) {
3690
- let html = panelHtml;
3691
- if (!html.includes('id="share-scripts-only-menu-item')) {
3692
- const editScriptsMatch = html.match(/<button class="dropdown-item"[^>]*data-action="edit-view-scripts"[^>]*>[\s\S]*?<\/button>/);
3693
- if (editScriptsMatch && editScriptsMatch.index !== undefined) {
3694
- const insertAt = editScriptsMatch.index + editScriptsMatch[0].length;
3695
- html = `${html.slice(0, insertAt)}${this.getShareScriptsOnlyMenuFragment()}${html.slice(insertAt)}`;
3696
- }
3697
- }
3698
- if (!html.includes('id="share-view-btn"')) {
3699
- const refreshMatch = html.match(/<button[^>]*id="refresh-charts-btn"[^>]*>[\s\S]*?<\/button>/);
3700
- if (refreshMatch && refreshMatch.index !== undefined) {
3701
- const insertAt = refreshMatch.index + refreshMatch[0].length;
3702
- html = `${html.slice(0, insertAt)}${this.getShareViewButtonFragment()}${html.slice(insertAt)}`;
3703
- }
3704
- }
3705
- if (!html.includes('id="install-view-btn"')) {
3706
- const shareMatch = html.match(/<button[^>]*id="share-view-btn"[^>]*>[\s\S]*?<\/button>/);
3707
- if (shareMatch && shareMatch.index !== undefined) {
3708
- const insertAt = shareMatch.index + shareMatch[0].length;
3709
- html = `${html.slice(0, insertAt)}${this.getInstallViewButtonFragment()}${html.slice(insertAt)}`;
3710
- }
3711
- }
3712
- // Normalize share icon and keep share to the right of refresh.
3713
- html = html.replace(/(<button[^>]*id="share-view-btn"[^>]*>)\s*<span class="btn-icon"[^>]*>[\s\S]*?<\/span>/, '$1<span class="btn-icon" aria-hidden="true">&#8593;</span>');
3714
- html = html.replace(/(<button[^>]*id="install-view-btn"[^>]*>)\s*<span class="btn-icon"[^>]*>[\s\S]*?<\/span>/, '$1<span class="btn-icon" aria-hidden="true">&#8595;</span>');
3715
- const shareBeforeRefresh = /<button[^>]*id="share-view-btn"[^>]*>[\s\S]*?<\/button>\s*<button[^>]*id="refresh-charts-btn"/.test(html);
3716
- if (shareBeforeRefresh) {
3717
- html = html.replace(/(<button[^>]*id="share-view-btn"[^>]*>[\s\S]*?<\/button>)\s*(<button[^>]*id="refresh-charts-btn"[^>]*>[\s\S]*?<\/button>)/, '$2$1');
3718
- }
3719
- return html;
3720
- }
3721
- async patchUserPanelHtmlOnDisk() {
3722
- const userPanelHtml = path.join(this.getPanelDir(), 'panel.html');
3723
- if (!(0, fs_1.existsSync)(userPanelHtml)) {
3724
- return;
3725
- }
3726
- try {
3727
- const current = (0, fs_1.readFileSync)(userPanelHtml, 'utf-8');
3728
- const patched = this.patchPanelHtmlForShareButton(current);
3729
- if (patched !== current) {
3730
- await fs_1.promises.writeFile(userPanelHtml, patched, 'utf-8');
3731
- }
3732
- }
3733
- catch (err) {
3734
- this.outputChannel.appendLine(`[Dashboard] Failed patching panel.html for share button: ${err}`);
3735
- }
3736
- }
3737
- /** Modal HTML for "New view name"; injected when user's panel.html lacks it (e.g. old seeded copy). */
3738
- getNewViewModalFragment() {
3739
- return `
35
+ </button>`}patchPanelHtmlForShareButton(e){let t=e;if(!t.includes('id="share-scripts-only-menu-item')){const s=t.match(/<button class="dropdown-item"[^>]*data-action="edit-view-scripts"[^>]*>[\s\S]*?<\/button>/);if(s&&s.index!==void 0){const a=s.index+s[0].length;t=`${t.slice(0,a)}${this.getShareScriptsOnlyMenuFragment()}${t.slice(a)}`}}if(!t.includes('id="share-view-btn"')){const s=t.match(/<button[^>]*id="refresh-charts-btn"[^>]*>[\s\S]*?<\/button>/);if(s&&s.index!==void 0){const a=s.index+s[0].length;t=`${t.slice(0,a)}${this.getShareViewButtonFragment()}${t.slice(a)}`}}if(!t.includes('id="install-view-btn"')){const s=t.match(/<button[^>]*id="share-view-btn"[^>]*>[\s\S]*?<\/button>/);if(s&&s.index!==void 0){const a=s.index+s[0].length;t=`${t.slice(0,a)}${this.getInstallViewButtonFragment()}${t.slice(a)}`}}return t=t.replace(/(<button[^>]*id="share-view-btn"[^>]*>)\s*<span class="btn-icon"[^>]*>[\s\S]*?<\/span>/,'$1<span class="btn-icon" aria-hidden="true">&#8593;</span>'),t=t.replace(/(<button[^>]*id="install-view-btn"[^>]*>)\s*<span class="btn-icon"[^>]*>[\s\S]*?<\/span>/,'$1<span class="btn-icon" aria-hidden="true">&#8595;</span>'),/<button[^>]*id="share-view-btn"[^>]*>[\s\S]*?<\/button>\s*<button[^>]*id="refresh-charts-btn"/.test(t)&&(t=t.replace(/(<button[^>]*id="share-view-btn"[^>]*>[\s\S]*?<\/button>)\s*(<button[^>]*id="refresh-charts-btn"[^>]*>[\s\S]*?<\/button>)/,"$2$1")),t}async patchUserPanelHtmlOnDisk(){const e=path.join(this.getPanelDir(),"panel.html");if((0,fs_1.existsSync)(e))try{const t=(0,fs_1.readFileSync)(e,"utf-8"),i=this.patchPanelHtmlForShareButton(t);i!==t&&await fs_1.promises.writeFile(e,i,"utf-8")}catch(t){this.outputChannel.appendLine(`[Dashboard] Failed patching panel.html for share button: ${t}`)}}getNewViewModalFragment(){return`
3740
36
  <!-- New view name modal (injected so Add view works without window.prompt) -->
3741
37
  <div id="new-view-modal" class="modal-overlay" role="dialog" aria-labelledby="new-view-modal-title" aria-modal="true" aria-hidden="true" style="display: none;">
3742
38
  <div class="modal-content">
@@ -3747,72 +43,29 @@ class AnalyticsDashboardProvider {
3747
43
  <button type="button" id="new-view-modal-ok" class="modal-btn modal-btn-primary">OK</button>
3748
44
  </div>
3749
45
  </div>
3750
- </div>`;
3751
- }
3752
- getHtmlContent(webview) {
3753
- const nonce = getNonce();
3754
- const webviewBasePath = this.getWebviewPath();
3755
- // Runtime bridge - always loaded from the extension bundle
3756
- const bridgeUri = webview.asWebviewUri(vscode.Uri.joinPath(this.extensionContext.extensionUri, webviewBasePath, 'runtime-bridge.js'));
3757
- // Panel files - prefer user panel dir for HTML/CSS; always use bundled panel.js so refresh/expand/shrink and message handling are up to date
3758
- const panelDir = this.getPanelDir();
3759
- const userPanelCss = path.join(panelDir, 'panel.css');
3760
- const userPanelHtml = path.join(panelDir, 'panel.html');
3761
- const panelJsUri = webview.asWebviewUri(vscode.Uri.joinPath(this.extensionContext.extensionUri, webviewBasePath, 'panel.js'));
3762
- const panelCssUri = (0, fs_1.existsSync)(userPanelCss)
3763
- ? webview.asWebviewUri(vscode.Uri.file(userPanelCss))
3764
- : webview.asWebviewUri(vscode.Uri.joinPath(this.extensionContext.extensionUri, webviewBasePath, 'styles.css'));
3765
- // Read panel HTML body: user override → bundled default → empty
3766
- let panelHtml = '';
3767
- if ((0, fs_1.existsSync)(userPanelHtml)) {
3768
- try {
3769
- panelHtml = (0, fs_1.readFileSync)(userPanelHtml, 'utf-8');
3770
- }
3771
- catch { /* fall through */ }
3772
- }
3773
- if (!panelHtml) {
3774
- const bundledHtmlPath = path.join(this.extensionContext.extensionPath, webviewBasePath, 'analytics-panel.html');
3775
- try {
3776
- panelHtml = (0, fs_1.readFileSync)(bundledHtmlPath, 'utf-8');
3777
- }
3778
- catch { /* panel.js will build DOM itself */ }
3779
- }
3780
- panelHtml = this.patchPanelHtmlForShareButton(panelHtml);
3781
- // Ensure "Add view" modal is present (user may have old seeded panel.html without it)
3782
- const newViewModalFragment = this.getNewViewModalFragment();
3783
- if (newViewModalFragment && !panelHtml.includes('id="new-view-modal"')) {
3784
- // Append before closing </div> of #app so modal is inside #app
3785
- const appClose = panelHtml.lastIndexOf('</div>');
3786
- if (appClose !== -1) {
3787
- panelHtml = panelHtml.slice(0, appClose) + newViewModalFragment + '\n\t' + panelHtml.slice(appClose);
3788
- }
3789
- else {
3790
- panelHtml = panelHtml + newViewModalFragment;
3791
- }
3792
- }
3793
- const bodyClass = this.isTeamSurface() ? ' class="spotlight-surface-team"' : '';
3794
- return `<!DOCTYPE html>
46
+ </div>`}getHtmlContent(e){const t=getNonce(),i=this.getWebviewPath(),s=e.asWebviewUri(vscode.Uri.joinPath(this.extensionContext.extensionUri,i,"runtime-bridge.js")),a=this.getPanelDir(),r=path.join(a,"panel.css"),n=path.join(a,"panel.html"),c=e.asWebviewUri(vscode.Uri.joinPath(this.extensionContext.extensionUri,i,"panel.js")),d=(0,fs_1.existsSync)(r)?e.asWebviewUri(vscode.Uri.file(r)):e.asWebviewUri(vscode.Uri.joinPath(this.extensionContext.extensionUri,i,"styles.css"));let o="";if((0,fs_1.existsSync)(n))try{o=(0,fs_1.readFileSync)(n,"utf-8")}catch{}if(!o){const h=path.join(this.extensionContext.extensionPath,i,"analytics-panel.html");try{o=(0,fs_1.readFileSync)(h,"utf-8")}catch{}}o=this.patchPanelHtmlForShareButton(o);const l=this.getNewViewModalFragment();if(l&&!o.includes('id="new-view-modal"')){const h=o.lastIndexOf("</div>");h!==-1?o=o.slice(0,h)+l+`
47
+ `+o.slice(h):o=o+l}const p=this.isTeamSurface()?' class="spotlight-surface-team"':"";return`<!DOCTYPE html>
3795
48
  <html lang="en">
3796
49
  <head>
3797
50
  <meta charset="UTF-8">
3798
51
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
3799
52
  <meta http-equiv="Content-Security-Policy" content="
3800
53
  default-src 'none';
3801
- style-src ${webview.cspSource} 'unsafe-inline';
3802
- script-src 'nonce-${nonce}';
3803
- img-src ${webview.cspSource} data:;
3804
- frame-src ${webview.cspSource};
3805
- font-src ${webview.cspSource};
54
+ style-src ${e.cspSource} 'unsafe-inline';
55
+ script-src 'nonce-${t}';
56
+ img-src ${e.cspSource} data:;
57
+ frame-src ${e.cspSource};
58
+ font-src ${e.cspSource};
3806
59
  ">
3807
- <link href="${panelCssUri}" rel="stylesheet">
60
+ <link href="${d}" rel="stylesheet">
3808
61
  <title>Spotlight Dashboard</title>
3809
- <style nonce="${nonce}">.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.4);display:flex;align-items:center;justify-content:center;z-index:1000}.modal-content{background:var(--vscode-editor-background);border:1px solid var(--vscode-panel-border);border-radius:8px;padding:16px;min-width:280px;box-shadow:0 4px 24px rgba(0,0,0,0.25)}.modal-title{font-size:1em;font-weight:600;margin:0 0 12px 0;color:var(--vscode-foreground)}.modal-input{width:100%;padding:8px 10px;margin-bottom:14px;font-family:var(--vscode-font-family);font-size:var(--vscode-font-size);color:var(--vscode-input-foreground);background:var(--vscode-input-background);border:1px solid var(--vscode-input-border);border-radius:4px;box-sizing:border-box}.modal-input:focus{outline:none;border-color:var(--vscode-focusBorder)}.modal-actions{display:flex;justify-content:flex-end;gap:8px}.modal-btn{padding:6px 14px;font-size:0.9em;border-radius:4px;cursor:pointer;border:none}.modal-btn-primary{background:var(--vscode-button-background);color:var(--vscode-button-foreground)}.modal-btn-primary:hover{background:var(--vscode-button-hoverBackground)}.modal-btn-secondary{background:var(--vscode-button-secondaryBackground);color:var(--vscode-button-secondaryForeground)}.modal-btn-secondary:hover{background:var(--vscode-button-secondaryHoverBackground)}</style>
62
+ <style nonce="${t}">.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.4);display:flex;align-items:center;justify-content:center;z-index:1000}.modal-content{background:var(--vscode-editor-background);border:1px solid var(--vscode-panel-border);border-radius:8px;padding:16px;min-width:280px;box-shadow:0 4px 24px rgba(0,0,0,0.25)}.modal-title{font-size:1em;font-weight:600;margin:0 0 12px 0;color:var(--vscode-foreground)}.modal-input{width:100%;padding:8px 10px;margin-bottom:14px;font-family:var(--vscode-font-family);font-size:var(--vscode-font-size);color:var(--vscode-input-foreground);background:var(--vscode-input-background);border:1px solid var(--vscode-input-border);border-radius:4px;box-sizing:border-box}.modal-input:focus{outline:none;border-color:var(--vscode-focusBorder)}.modal-actions{display:flex;justify-content:flex-end;gap:8px}.modal-btn{padding:6px 14px;font-size:0.9em;border-radius:4px;cursor:pointer;border:none}.modal-btn-primary{background:var(--vscode-button-background);color:var(--vscode-button-foreground)}.modal-btn-primary:hover{background:var(--vscode-button-hoverBackground)}.modal-btn-secondary{background:var(--vscode-button-secondaryBackground);color:var(--vscode-button-secondaryForeground)}.modal-btn-secondary:hover{background:var(--vscode-button-secondaryHoverBackground)}</style>
3810
63
  </head>
3811
- <body${bodyClass}>
3812
- ${panelHtml}
3813
- <script nonce="${nonce}" src="${bridgeUri}"></script>
3814
- <script nonce="${nonce}" src="${panelJsUri}"></script>
3815
- <script nonce="${nonce}">
64
+ <body${p}>
65
+ ${o}
66
+ <script nonce="${t}" src="${s}"></script>
67
+ <script nonce="${t}" src="${c}"></script>
68
+ <script nonce="${t}">
3816
69
  (function(){
3817
70
  var api = typeof window.panelApi !== 'undefined' ? window.panelApi : null;
3818
71
  if (!api) return;
@@ -3861,36 +114,4 @@ class AnalyticsDashboardProvider {
3861
114
  })();
3862
115
  </script>
3863
116
  </body>
3864
- </html>`;
3865
- }
3866
- // -------------------------------------------------------------------------
3867
- // Disposal
3868
- // -------------------------------------------------------------------------
3869
- dispose() {
3870
- this.artifactWatcher?.dispose();
3871
- this.viewsConfigWatcher?.dispose();
3872
- if (this.viewsConfigDebounceTimeout) {
3873
- clearTimeout(this.viewsConfigDebounceTimeout);
3874
- }
3875
- for (const panel of this.htmlPreviewPanels.values()) {
3876
- panel.dispose();
3877
- }
3878
- this.htmlPreviewPanels.clear();
3879
- if (this.pendingFilterAutoGenerateTimer) {
3880
- clearTimeout(this.pendingFilterAutoGenerateTimer);
3881
- }
3882
- this.disposables.forEach(d => d.dispose());
3883
- }
3884
- }
3885
- exports.AnalyticsDashboardProvider = AnalyticsDashboardProvider;
3886
- // -----------------------------------------------------------------------------
3887
- // Utilities
3888
- // -----------------------------------------------------------------------------
3889
- function getNonce() {
3890
- let text = '';
3891
- const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
3892
- for (let i = 0; i < 32; i++) {
3893
- text += possible.charAt(Math.floor(Math.random() * possible.length));
3894
- }
3895
- return text;
3896
- }
117
+ </html>`}dispose(){this.artifactWatcher?.dispose(),this.viewsConfigWatcher?.dispose(),this.viewsConfigDebounceTimeout&&clearTimeout(this.viewsConfigDebounceTimeout);for(const e of this.htmlPreviewPanels.values())e.dispose();this.htmlPreviewPanels.clear(),this.pendingFilterAutoGenerateTimer&&clearTimeout(this.pendingFilterAutoGenerateTimer),this.disposables.forEach(e=>e.dispose())}}exports.AnalyticsDashboardProvider=AnalyticsDashboardProvider;function getNonce(){let m="";const e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";for(let t=0;t<32;t++)m+=e.charAt(Math.floor(Math.random()*e.length));return m}