@agenthippo-ai/agenthippo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (480) hide show
  1. package/LICENSE +21 -0
  2. package/bin/agenthippo.js +2 -0
  3. package/out/agentpack/github.js +547 -0
  4. package/out/agentpack/index.js +66 -0
  5. package/out/agentpack/install.js +297 -0
  6. package/out/agentpack/manager.js +441 -0
  7. package/out/agentpack/manifest.js +135 -0
  8. package/out/agentpack/paths.js +475 -0
  9. package/out/agentpack/publish.js +248 -0
  10. package/out/agentpack/stores.js +554 -0
  11. package/out/agentpack/types.js +5 -0
  12. package/out/analytics/dashboard/artifactManager.js +439 -0
  13. package/out/analytics/dashboard/fleetDisplayUtils.js +159 -0
  14. package/out/analytics/dashboard/fleetOrchestrator.js +142 -0
  15. package/out/analytics/dashboard/fleetRegistry.js +79 -0
  16. package/out/analytics/dashboard/fleetRowDefaults.js +87 -0
  17. package/out/analytics/dashboard/fleetScriptAdapter.js +146 -0
  18. package/out/analytics/dashboard/fleetStatusUtils.js +30 -0
  19. package/out/analytics/dashboard/fleetTitleUtils.js +68 -0
  20. package/out/analytics/dashboard/fleetTypes.js +29 -0
  21. package/out/analytics/dashboard/index.js +17 -0
  22. package/out/analytics/dashboard/indexingService.js +422 -0
  23. package/out/analytics/dashboard/localFleetAdapter.js +222 -0
  24. package/out/analytics/dashboard/performanceMonitor.js +76 -0
  25. package/out/analytics/dashboard/processManager.js +209 -0
  26. package/out/analytics/dashboard/provider.js +3885 -0
  27. package/out/analytics/dashboard/queryRunner.js +994 -0
  28. package/out/analytics/dashboard/refreshPolicy.js +16 -0
  29. package/out/analytics/dashboard/scheduledFleetFingerprint.js +22 -0
  30. package/out/analytics/dashboard/scheduledFleetStorage.js +241 -0
  31. package/out/analytics/dashboard/scheduledFleetUtils.js +22 -0
  32. package/out/analytics/dashboard/sessionEventFleetStats.js +118 -0
  33. package/out/analytics/dashboard/sessionJsonlFleet.js +204 -0
  34. package/out/analytics/dashboard/teamViewDiscovery.js +106 -0
  35. package/out/analytics/dashboard/viewsConfigMerge.js +221 -0
  36. package/out/analytics/dashboard/webview/analytics-panel.html +155 -0
  37. package/out/analytics/dashboard/webview/codicon.ttf +0 -0
  38. package/out/analytics/dashboard/webview/index.html +78 -0
  39. package/out/analytics/dashboard/webview/panel.js +2295 -0
  40. package/out/analytics/dashboard/webview/runtime-bridge.js +82 -0
  41. package/out/analytics/dashboard/webview/styles.css +1009 -0
  42. package/out/analytics/lifecycle.js +43 -0
  43. package/out/analytics/service.js +1279 -0
  44. package/out/analytics/sessionEvents.js +231 -0
  45. package/out/auth/bootstrapApply.js +72 -0
  46. package/out/auth/brokerClient.js +126 -0
  47. package/out/auth/callbackUri.js +59 -0
  48. package/out/auth/callbackUriParse.js +59 -0
  49. package/out/auth/githubStoreGate.js +70 -0
  50. package/out/auth/loopbackAuthServer.js +165 -0
  51. package/out/auth/pkce.js +9 -0
  52. package/out/auth/provider.js +239 -0
  53. package/out/auth/sessionStore.js +193 -0
  54. package/out/auth/signInFlow.js +182 -0
  55. package/out/auth/types.js +12 -0
  56. package/out/auth-broker/config.js +62 -0
  57. package/out/auth-broker/jwt.js +105 -0
  58. package/out/auth-broker/pkce.js +57 -0
  59. package/out/auth-broker/server.js +539 -0
  60. package/out/auth-broker/store.js +273 -0
  61. package/out/base-prompts/gateway/openclaw-starter.json5 +144 -0
  62. package/out/base-prompts/gateway/slack-socket-mode-minimal.manifest.json +44 -0
  63. package/out/base-prompts/mcp-servers/.mcp.json +13 -0
  64. package/out/base-prompts/mcp-servers/langfuse-analytics/README.md +142 -0
  65. package/out/base-prompts/mcp-servers/langfuse-analytics/mcp.json +13 -0
  66. package/out/base-prompts/mcp-servers/langfuse-analytics/package-lock.json +1192 -0
  67. package/out/base-prompts/mcp-servers/langfuse-analytics/package.json +23 -0
  68. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/run-with-env.sh +12 -0
  69. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/sdk-test.js +102 -0
  70. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/simple-test.js +137 -0
  71. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-client.js +118 -0
  72. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-server.js +209 -0
  73. package/out/base-prompts/mcp-servers/langfuse-analytics/src/client.ts +431 -0
  74. package/out/base-prompts/mcp-servers/langfuse-analytics/src/index.ts +270 -0
  75. package/out/base-prompts/mcp-servers/langfuse-analytics/tsconfig.json +18 -0
  76. package/out/base-prompts/rules/AGENTS.md +204 -0
  77. package/out/base-prompts/skills/agent-browser/SKILL.md +102 -0
  78. package/out/base-prompts/skills/agent-builder/SKILL.md +73 -0
  79. package/out/base-prompts/skills/agent-builder/references/agent-pack.md +63 -0
  80. package/out/base-prompts/skills/agent-builder/references/crewai.md +66 -0
  81. package/out/base-prompts/skills/agent-builder/references/langchain.md +60 -0
  82. package/out/base-prompts/skills/agent-builder/references/raw-sdk.md +73 -0
  83. package/out/base-prompts/skills/agent-engine-creator/SKILL.md +586 -0
  84. package/out/base-prompts/skills/agent-engine-installer/SKILL.md +106 -0
  85. package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.mjs +259 -0
  86. package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.ps1 +189 -0
  87. package/out/base-prompts/skills/agenthippo-cli/SKILL.md +69 -0
  88. package/out/base-prompts/skills/agenthippo-cli/scripts/install.sh +63 -0
  89. package/out/base-prompts/skills/agentpack-creator/SKILL.md +375 -0
  90. package/out/base-prompts/skills/agentpack-creator/references/agent-templates.md +261 -0
  91. package/out/base-prompts/skills/agentpack-creator/references/agents-md-guide.md +195 -0
  92. package/out/base-prompts/skills/agentpack-creator/scripts/init_pack.py +391 -0
  93. package/out/base-prompts/skills/agentpack-creator/scripts/init_pack_test.py +294 -0
  94. package/out/base-prompts/skills/agentpack-testbench/SKILL.md +135 -0
  95. package/out/base-prompts/skills/algorithmic-art/LICENSE.txt +202 -0
  96. package/out/base-prompts/skills/algorithmic-art/SKILL.md +405 -0
  97. package/out/base-prompts/skills/algorithmic-art/templates/generator_template.js +223 -0
  98. package/out/base-prompts/skills/algorithmic-art/templates/viewer.html +599 -0
  99. package/out/base-prompts/skills/debug-agent/SKILL.md +83 -0
  100. package/out/base-prompts/skills/desktop-automation/SKILL.md +166 -0
  101. package/out/base-prompts/skills/desktop-automation/references/agenthippo-ui-recipes.md +441 -0
  102. package/out/base-prompts/skills/desktop-automation/scripts/windows-desktop-uia.psm1 +280 -0
  103. package/out/base-prompts/skills/docx/LICENSE.txt +30 -0
  104. package/out/base-prompts/skills/docx/SKILL.md +197 -0
  105. package/out/base-prompts/skills/docx/docx-js.md +350 -0
  106. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  107. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  108. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  109. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  110. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  111. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  112. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  113. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  114. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  115. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  116. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  117. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  118. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  119. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  120. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  121. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  122. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  123. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  124. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  125. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  126. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  127. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  128. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  129. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  130. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  131. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  132. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  133. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  134. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  135. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  136. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  137. package/out/base-prompts/skills/docx/ooxml/schemas/mce/mc.xsd +75 -0
  138. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  139. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  140. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  141. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  142. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  143. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  144. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  145. package/out/base-prompts/skills/docx/ooxml/scripts/pack.py +159 -0
  146. package/out/base-prompts/skills/docx/ooxml/scripts/unpack.py +29 -0
  147. package/out/base-prompts/skills/docx/ooxml/scripts/validate.py +69 -0
  148. package/out/base-prompts/skills/docx/ooxml/scripts/validation/__init__.py +15 -0
  149. package/out/base-prompts/skills/docx/ooxml/scripts/validation/base.py +951 -0
  150. package/out/base-prompts/skills/docx/ooxml/scripts/validation/docx.py +274 -0
  151. package/out/base-prompts/skills/docx/ooxml/scripts/validation/pptx.py +315 -0
  152. package/out/base-prompts/skills/docx/ooxml/scripts/validation/redlining.py +279 -0
  153. package/out/base-prompts/skills/docx/ooxml.md +610 -0
  154. package/out/base-prompts/skills/docx/scripts/__init__.py +1 -0
  155. package/out/base-prompts/skills/docx/scripts/document.py +1276 -0
  156. package/out/base-prompts/skills/docx/scripts/templates/comments.xml +3 -0
  157. package/out/base-prompts/skills/docx/scripts/templates/commentsExtended.xml +3 -0
  158. package/out/base-prompts/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
  159. package/out/base-prompts/skills/docx/scripts/templates/commentsIds.xml +3 -0
  160. package/out/base-prompts/skills/docx/scripts/templates/people.xml +3 -0
  161. package/out/base-prompts/skills/docx/scripts/utilities.py +374 -0
  162. package/out/base-prompts/skills/gateway-outbound/SKILL.md +136 -0
  163. package/out/base-prompts/skills/mcp-builder/LICENSE.txt +202 -0
  164. package/out/base-prompts/skills/mcp-builder/SKILL.md +328 -0
  165. package/out/base-prompts/skills/mcp-builder/reference/evaluation.md +602 -0
  166. package/out/base-prompts/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
  167. package/out/base-prompts/skills/mcp-builder/reference/node_mcp_server.md +970 -0
  168. package/out/base-prompts/skills/mcp-builder/reference/python_mcp_server.md +719 -0
  169. package/out/base-prompts/skills/mcp-builder/scripts/connections.py +151 -0
  170. package/out/base-prompts/skills/mcp-builder/scripts/evaluation.py +373 -0
  171. package/out/base-prompts/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  172. package/out/base-prompts/skills/mcp-builder/scripts/requirements.txt +2 -0
  173. package/out/base-prompts/skills/openclaw/SKILL.md +406 -0
  174. package/out/base-prompts/skills/openclaw/scripts/run-smoke.sh +61 -0
  175. package/out/base-prompts/skills/pdf/LICENSE.txt +30 -0
  176. package/out/base-prompts/skills/pdf/SKILL.md +294 -0
  177. package/out/base-prompts/skills/pdf/forms.md +205 -0
  178. package/out/base-prompts/skills/pdf/reference.md +612 -0
  179. package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes.py +70 -0
  180. package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes_test.py +226 -0
  181. package/out/base-prompts/skills/pdf/scripts/check_fillable_fields.py +12 -0
  182. package/out/base-prompts/skills/pdf/scripts/convert_pdf_to_images.py +35 -0
  183. package/out/base-prompts/skills/pdf/scripts/create_validation_image.py +41 -0
  184. package/out/base-prompts/skills/pdf/scripts/extract_form_field_info.py +152 -0
  185. package/out/base-prompts/skills/pdf/scripts/fill_fillable_fields.py +114 -0
  186. package/out/base-prompts/skills/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
  187. package/out/base-prompts/skills/pptx/LICENSE.txt +30 -0
  188. package/out/base-prompts/skills/pptx/SKILL.md +484 -0
  189. package/out/base-prompts/skills/pptx/html2pptx.md +625 -0
  190. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  191. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  192. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  193. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  194. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  195. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  196. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  197. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  198. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  199. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  200. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  201. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  202. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  203. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  204. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  205. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  206. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  207. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  208. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  209. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  210. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  211. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  212. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  213. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  214. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  215. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  216. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  217. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  218. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  219. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  220. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  221. package/out/base-prompts/skills/pptx/ooxml/schemas/mce/mc.xsd +75 -0
  222. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  223. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  224. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  225. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  226. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  227. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  228. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  229. package/out/base-prompts/skills/pptx/ooxml/scripts/pack.py +159 -0
  230. package/out/base-prompts/skills/pptx/ooxml/scripts/unpack.py +29 -0
  231. package/out/base-prompts/skills/pptx/ooxml/scripts/validate.py +69 -0
  232. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/__init__.py +15 -0
  233. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/base.py +951 -0
  234. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/docx.py +274 -0
  235. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/pptx.py +315 -0
  236. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/redlining.py +279 -0
  237. package/out/base-prompts/skills/pptx/ooxml.md +427 -0
  238. package/out/base-prompts/skills/pptx/scripts/html2pptx.js +984 -0
  239. package/out/base-prompts/skills/pptx/scripts/inventory.py +1020 -0
  240. package/out/base-prompts/skills/pptx/scripts/rearrange.py +231 -0
  241. package/out/base-prompts/skills/pptx/scripts/replace.py +385 -0
  242. package/out/base-prompts/skills/pptx/scripts/thumbnail.py +450 -0
  243. package/out/base-prompts/skills/prompt-lab/SKILL.md +149 -0
  244. package/out/base-prompts/skills/prompt-lab/scripts/query-analytics.sh +2551 -0
  245. package/out/base-prompts/skills/skill-creator/LICENSE.txt +202 -0
  246. package/out/base-prompts/skills/skill-creator/SKILL.md +371 -0
  247. package/out/base-prompts/skills/skill-creator/references/output-patterns.md +82 -0
  248. package/out/base-prompts/skills/skill-creator/references/workflows.md +28 -0
  249. package/out/base-prompts/skills/skill-creator/scripts/init_skill.py +303 -0
  250. package/out/base-prompts/skills/skill-creator/scripts/package_skill.py +110 -0
  251. package/out/base-prompts/skills/skill-creator/scripts/quick_validate.py +95 -0
  252. package/out/base-prompts/skills/skill-installer/SKILL.md +251 -0
  253. package/out/base-prompts/skills/skill-installer/references/store-api.md +219 -0
  254. package/out/base-prompts/skills/spotlight-dashboard/SKILL.md +83 -0
  255. package/out/base-prompts/skills/spotlight-insights/SKILL.md +101 -0
  256. package/out/base-prompts/skills/spotlight-optimize/SKILL.md +93 -0
  257. package/out/base-prompts/skills/spotlight-optimize/scripts/query-analytics.sh +2551 -0
  258. package/out/base-prompts/skills/spotlight-query/SKILL.md +314 -0
  259. package/out/base-prompts/skills/spotlight-query/references/otel-schema.md +163 -0
  260. package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.sh +2551 -0
  261. package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.test.sh +744 -0
  262. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/sessions/sess-test-1.jsonl +14 -0
  263. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/.duckdb-index.json +7 -0
  264. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/sample.jsonl +5 -0
  265. package/out/base-prompts/skills/spotlight-report/SKILL.md +86 -0
  266. package/out/base-prompts/skills/spotlight-visualize/SKILL.md +92 -0
  267. package/out/base-prompts/skills/spotlight-visualize/scripts/query-analytics.sh +2551 -0
  268. package/out/base-prompts/skills/xlsx/LICENSE.txt +30 -0
  269. package/out/base-prompts/skills/xlsx/SKILL.md +289 -0
  270. package/out/base-prompts/skills/xlsx/recalc.py +178 -0
  271. package/out/branding.js +23 -0
  272. package/out/cli/commands/anywhere.js +66 -0
  273. package/out/cli/commands/ask.js +243 -0
  274. package/out/cli/commands/auth-broker.js +24 -0
  275. package/out/cli/commands/auth.js +219 -0
  276. package/out/cli/commands/chat.js +354 -0
  277. package/out/cli/commands/deploy.js +223 -0
  278. package/out/cli/commands/gateway.js +160 -0
  279. package/out/cli/commands/pack.js +220 -0
  280. package/out/cli/commands/probe-custom-engine.js +82 -0
  281. package/out/cli/commands/serve.js +583 -0
  282. package/out/cli/commands/store.js +919 -0
  283. package/out/cli/credential-context.js +113 -0
  284. package/out/cli/deploy-init.js +2087 -0
  285. package/out/cli/deps.js +238 -0
  286. package/out/cli/index.js +390 -0
  287. package/out/cli/pack-loader.js +376 -0
  288. package/out/cli/run-custom-engine-runner.js +318 -0
  289. package/out/cli/runtime.js +409 -0
  290. package/out/cli/serve-hardening.js +325 -0
  291. package/out/cli/state-sync.js +135 -0
  292. package/out/cli/utils.js +220 -0
  293. package/out/cli/vscode-shim.js +86 -0
  294. package/out/engines/base.js +159 -0
  295. package/out/engines/claude.js +1215 -0
  296. package/out/engines/codex.js +759 -0
  297. package/out/engines/custom.js +632 -0
  298. package/out/engines/customEngineIpc.js +95 -0
  299. package/out/engines/customEngineRunnerEmbed.js +107 -0
  300. package/out/engines/customRegistry.js +274 -0
  301. package/out/engines/embeddedCustomEngineRunner.generated.js +9 -0
  302. package/out/engines/engineApiKey.js +138 -0
  303. package/out/engines/factory.js +45 -0
  304. package/out/engines/install.js +372 -0
  305. package/out/engines/persistentEngineWorker.js +320 -0
  306. package/out/engines/publish.js +341 -0
  307. package/out/engines/types.js +17 -0
  308. package/out/examples/run-headless-repl.js +291 -0
  309. package/out/examples/run-headless.js +187 -0
  310. package/out/extension.js +4472 -0
  311. package/out/gateway/bridge.js +805 -0
  312. package/out/gateway/cli-agent-id.js +36 -0
  313. package/out/gateway/client.js +303 -0
  314. package/out/gateway/client.test.js +395 -0
  315. package/out/gateway/gateway-scope-errors.js +14 -0
  316. package/out/gateway/gatewayBackup.js +76 -0
  317. package/out/gateway/gatewayRouteIndex.js +371 -0
  318. package/out/gateway/gatewaySessionStore.js +243 -0
  319. package/out/gateway/index.js +31 -0
  320. package/out/gateway/lifecycle.js +3056 -0
  321. package/out/gateway/openclaw-channel-routing.js +317 -0
  322. package/out/gateway/outboundHandles.js +373 -0
  323. package/out/gateway/outboundPaths.js +72 -0
  324. package/out/gateway/outboundSend.js +254 -0
  325. package/out/gateway/protocol.js +48 -0
  326. package/out/gateway/resetMappings.js +52 -0
  327. package/out/gateway/service.js +1244 -0
  328. package/out/gateway/sessionRouting.js +77 -0
  329. package/out/headless/index.js +505 -0
  330. package/out/headless/sync.js +473 -0
  331. package/out/mcp/publish.js +143 -0
  332. package/out/mcp.js +597 -0
  333. package/out/permissions.js +108 -0
  334. package/out/permissions.mapping.yaml +68 -0
  335. package/out/registry/index.js +332 -0
  336. package/out/remote/handleRemoteChat.js +122 -0
  337. package/out/remote/remotePackStatusBar.js +103 -0
  338. package/out/remote/serveClient.js +216 -0
  339. package/out/remoteSkills.js +191 -0
  340. package/out/runtime/index.js +32 -0
  341. package/out/runtime/interfaces/config-provider.js +32 -0
  342. package/out/runtime/interfaces/edit-tracker.js +22 -0
  343. package/out/runtime/interfaces/index.js +29 -0
  344. package/out/runtime/interfaces/logger.js +40 -0
  345. package/out/runtime/interfaces/permission-handler.js +66 -0
  346. package/out/runtime/interfaces/session-store.js +342 -0
  347. package/out/runtime/vscode/index.js +20 -0
  348. package/out/runtime/vscode/vscode-config-provider.js +77 -0
  349. package/out/runtime/vscode/vscode-edit-tracker.js +120 -0
  350. package/out/runtime/vscode/vscode-logger.js +30 -0
  351. package/out/runtime/vscode/vscode-permission-handler.js +72 -0
  352. package/out/runtime/vscode/vscode-session-store.js +22 -0
  353. package/out/scripts/agentide-litellm.ps1 +501 -0
  354. package/out/scripts/agentide-litellm.sh +700 -0
  355. package/out/scripts/agentide_litellm_hooks_v1.py +604 -0
  356. package/out/scripts/analytics/agent-fleet-widget.js +954 -0
  357. package/out/scripts/analytics/agentide-litellm-otel.config.yaml +160 -0
  358. package/out/scripts/analytics/default-seed-scripts.json +12 -0
  359. package/out/scripts/analytics/duckdbLocator.js +29 -0
  360. package/out/scripts/analytics/fleet-local-adapter.js +246 -0
  361. package/out/scripts/analytics/fleet-workflow.sh +263 -0
  362. package/out/scripts/analytics/generate-default-charts.py +685 -0
  363. package/out/scripts/analytics/hotspot-analysis-widget.js +909 -0
  364. package/out/scripts/analytics/index-traces.py +1394 -0
  365. package/out/scripts/analytics/install-analytics.sh +286 -0
  366. package/out/scripts/analytics/live-fleet-4-collector.cjs +315 -0
  367. package/out/scripts/analytics/live-fleet-4-daemon.mjs +134 -0
  368. package/out/scripts/analytics/live-fleet-4-widget.cjs +433 -0
  369. package/out/scripts/analytics/otel-collector.yaml +55 -0
  370. package/out/scripts/analytics/query-analytics.ps1 +396 -0
  371. package/out/scripts/analytics/query-analytics.sh +2551 -0
  372. package/out/scripts/analytics/session-comparison-widget.js +1468 -0
  373. package/out/scripts/analytics/session-timeline-widget.js +2137 -0
  374. package/out/scripts/analytics/setup-analytics.ps1 +432 -0
  375. package/out/scripts/analytics/setup-analytics.sh +854 -0
  376. package/out/scripts/analytics/start-analytics.ps1 +169 -0
  377. package/out/scripts/analytics/start-analytics.sh +323 -0
  378. package/out/scripts/analytics/token-cost-flamegraph-widget.js +954 -0
  379. package/out/scripts/analytics/token-cost-trend-widget.js +1062 -0
  380. package/out/scripts/analytics/workflows-collector.cjs +315 -0
  381. package/out/scripts/analytics/workflows-daemon.mjs +186 -0
  382. package/out/scripts/analytics/workflows-widget.cjs +433 -0
  383. package/out/scripts/analytics/wow-model-widget.js +191 -0
  384. package/out/scripts/gateway/Dockerfile.serve +45 -0
  385. package/out/scripts/gateway/docker-serve.sh +248 -0
  386. package/out/scripts/gateway/setup-gateway-server.ps1 +953 -0
  387. package/out/scripts/gateway/setup-gateway-server.sh +935 -0
  388. package/out/scripts/gateway/setup-gateway.ps1 +1813 -0
  389. package/out/scripts/gateway/setup-gateway.sh +1541 -0
  390. package/out/scripts/setup-cli.ps1 +86 -0
  391. package/out/scripts/setup-cli.sh +117 -0
  392. package/out/scripts/tools-manifest.json +83 -0
  393. package/out/scripts/vendor-tools.mjs +234 -0
  394. package/out/session.js +272 -0
  395. package/out/shared/agentEngines.js +170 -0
  396. package/out/shared/index.js +109 -0
  397. package/out/shared/models.js +235 -0
  398. package/out/shared/providers.js +202 -0
  399. package/out/skills/publish.js +178 -0
  400. package/out/spotlight/install.js +339 -0
  401. package/out/spotlight/publish.js +544 -0
  402. package/out/spotlight/yaml.js +31 -0
  403. package/out/store/github/setup.js +235 -0
  404. package/out/store/github/templates/build-index.ts +339 -0
  405. package/out/store/github/templates/build-index.yml +97 -0
  406. package/out/store/github/templates/package.json +11 -0
  407. package/out/store/index.js +755 -0
  408. package/out/store/oramaIndex.js +884 -0
  409. package/out/store/shared.js +241 -0
  410. package/out/sync.js +1827 -0
  411. package/out/terminalLogger.js +358 -0
  412. package/out/test/agentEnvFile.test.js +97 -0
  413. package/out/test/agentYaml.test.js +373 -0
  414. package/out/test/agentpack.test.js +639 -0
  415. package/out/test/analytics-lifecycle.test.js +174 -0
  416. package/out/test/analytics.test.js +524 -0
  417. package/out/test/auth-bootstrapApply.test.js +102 -0
  418. package/out/test/auth-broker.test.js +434 -0
  419. package/out/test/auth-callback.test.js +62 -0
  420. package/out/test/branding.test.js +229 -0
  421. package/out/test/cli-agent-id.test.js +55 -0
  422. package/out/test/credential-context.test.js +105 -0
  423. package/out/test/customEngineInstall.test.js +100 -0
  424. package/out/test/customRegistry.test.js +122 -0
  425. package/out/test/deploy-init.test.js +532 -0
  426. package/out/test/deps-auto-install.test.js +81 -0
  427. package/out/test/engine-integration.test.js +297 -0
  428. package/out/test/enginePaths.test.js +195 -0
  429. package/out/test/enginePublish.test.js +82 -0
  430. package/out/test/fleetDisplayUtils.test.js +96 -0
  431. package/out/test/gateway-client.test.js +381 -0
  432. package/out/test/gateway-lifecycle.test.js +608 -0
  433. package/out/test/gateway-outbound.test.js +531 -0
  434. package/out/test/gateway-reset-mappings.test.js +221 -0
  435. package/out/test/gateway-route-index.test.js +235 -0
  436. package/out/test/gateway-session-routing.test.js +47 -0
  437. package/out/test/gateway-session-store.test.js +216 -0
  438. package/out/test/github.test.js +232 -0
  439. package/out/test/headless-sync.test.js +279 -0
  440. package/out/test/helpers/manifest-fixtures.js +27 -0
  441. package/out/test/mcp-obo.test.js +82 -0
  442. package/out/test/mcp.test.js +307 -0
  443. package/out/test/openclaw-channel-routing.test.js +191 -0
  444. package/out/test/pathSafety.test.js +63 -0
  445. package/out/test/permissions.test.js +175 -0
  446. package/out/test/persistent-engine-worker.test.js +220 -0
  447. package/out/test/refreshPolicy.test.js +60 -0
  448. package/out/test/registry.test.js +351 -0
  449. package/out/test/runtime.test.js +349 -0
  450. package/out/test/scheduledFleetStorage.test.js +72 -0
  451. package/out/test/serve-hardening.test.js +192 -0
  452. package/out/test/serveClient.test.js +173 -0
  453. package/out/test/sessionJsonlFleet.test.js +78 -0
  454. package/out/test/shared.test.js +173 -0
  455. package/out/test/skills-mcp.test.js +299 -0
  456. package/out/test/spotlightPublish.test.js +299 -0
  457. package/out/test/spotlightYaml.test.js +58 -0
  458. package/out/test/state-sync-peer.test.js +74 -0
  459. package/out/test/store.test.js +331 -0
  460. package/out/test/stores.test.js +504 -0
  461. package/out/test/sync-switch.test.js +251 -0
  462. package/out/test/teamViewDiscovery.test.js +90 -0
  463. package/out/test/toolPath.test.js +120 -0
  464. package/out/test/utils.test.js +308 -0
  465. package/out/test/viewsConfigMerge.test.js +180 -0
  466. package/out/test/windows-command-line.test.js +75 -0
  467. package/out/utils/agentEnvFile.js +131 -0
  468. package/out/utils/binary.js +175 -0
  469. package/out/utils/index.js +37 -0
  470. package/out/utils/network.js +74 -0
  471. package/out/utils/pathSafety.js +60 -0
  472. package/out/utils/paths.js +116 -0
  473. package/out/utils/pid.js +180 -0
  474. package/out/utils/pythonRunner.js +258 -0
  475. package/out/utils/resolveNodeBinary.js +168 -0
  476. package/out/utils/toolPath.js +83 -0
  477. package/out/utils/windowsCommandLine.js +29 -0
  478. package/out/utils.js +915 -0
  479. package/out/workspaceFolders.js +59 -0
  480. package/package.json +41 -0
@@ -0,0 +1,2295 @@
1
+ /* -----------------------------------------------------------------------------
2
+ * Copyright (c) AgentHippo.ai. All rights reserved.
3
+ * -----------------------------------------------------------------------------
4
+ * Spotlight Dashboard - Default Panel Module
5
+ * Handles artifact display, rendering, and interactions.
6
+ * Uses panelApi (provided by runtime-bridge.js) instead of raw VS Code API.
7
+ *
8
+ * This file is seeded to .agent-hippo/analytics/panel/panel.js on first use.
9
+ * Users can edit the seeded copy to customize the dashboard UI.
10
+ * ----------------------------------------------------------------------------- */
11
+
12
+ // @ts-check
13
+ /// <reference lib="dom" />
14
+
15
+ // @ts-ignore - provided by runtime-bridge.js
16
+ const panelApi = window.panelApi;
17
+
18
+ // Seed/version marker for deciding whether a user's locally seeded panel.js
19
+ // is compatible with the extension runtime (e.g. message forwarding fixes).
20
+ const AGENTIDE_PANEL_JS_VERSION = '2026.05.30';
21
+ try { window.__AGENTIDE_PANEL_JS_VERSION__ = AGENTIDE_PANEL_JS_VERSION; } catch { /* no-op */ }
22
+
23
+ // -----------------------------------------------------------------------------
24
+ // State
25
+ // -----------------------------------------------------------------------------
26
+
27
+ const PANEL_ID = 'default';
28
+
29
+ /** Host ↔ panel / iframe ↔ panel: view-owned export JSON live updates. */
30
+ const VIEW_EXPORT_SNAPSHOT = 'viewExportSnapshot';
31
+ const REQUEST_VIEW_EXPORT_SNAPSHOT = 'requestViewExportSnapshot';
32
+ /** @deprecated Renamed; widgets may still emit this until updated. */
33
+ const LEGACY_REQUEST_LIVE_FLEET_SNAPSHOT = 'requestLiveFleetSnapshot';
34
+ const LEGACY_VIEW_EXPORT_SNAPSHOT = 'liveFleetSnapshot';
35
+ const ADD_VIEW_OPTION_VALUE = '__add_view__';
36
+
37
+ /**
38
+ * @typedef {{ order: number, hidden: boolean, height?: number, expanded?: boolean }} PanelChartState
39
+ * @typedef {{ version: 1, charts: Record<string, PanelChartState>, summaryHidden?: boolean }} PanelState
40
+ */
41
+
42
+ /** @returns {PanelState} */
43
+ function createDefaultPanelState() {
44
+ return { version: 1, charts: {}, summaryHidden: false };
45
+ }
46
+
47
+ /** @type {{ artifacts: object[], galleryIgnorePatterns: string[], summaryCards: object[] | null, manifest: object | null, isRefreshing: boolean, scope: 'global'|'workspace', projectId: string, agentId: string, agentIds: string[], views: Array<{ id: string, name: string, scope: 'global'|'workspace', system: boolean, layout?: 'fleet-dashboard'|'gallery', refreshOnNavigate?: 'always'|'when-empty', preserveFleetIframe?: boolean }>, activeViewId: string, disabledCharts: Set<string>, panelState: PanelState, panelStateLoaded: boolean, lastArtifactCatalogSignature: string, fleetAutoRefreshIntervalSeconds: number, fleetRefreshVersion: number }} */
48
+ const state = {
49
+ artifacts: [],
50
+ galleryIgnorePatterns: [],
51
+ summaryCards: null,
52
+ manifest: null,
53
+ isRefreshing: false,
54
+ scope: 'global',
55
+ projectId: '',
56
+ agentId: '',
57
+ agentIds: [],
58
+ views: [],
59
+ activeViewId: 'global',
60
+ disabledCharts: new Set(),
61
+ panelState: createDefaultPanelState(),
62
+ panelStateLoaded: false,
63
+ lastArtifactCatalogSignature: '',
64
+ fleetAutoRefreshIntervalSeconds: 30,
65
+ fleetRefreshVersion: 0,
66
+ surfaceMode: 'spotlight',
67
+ };
68
+
69
+ function applySurfaceMode(mode) {
70
+ const team = mode === 'team';
71
+ state.surfaceMode = team ? 'team' : 'spotlight';
72
+ document.body.classList.toggle('spotlight-surface-team', team);
73
+ applyTeamViewSelectVisibility();
74
+ }
75
+
76
+ /**
77
+ * Fleet view without preserveFleetIframe: host bumps fleetRefreshVersion and this panel reloads agent-fleet.html.
78
+ * preserveFleetIframe views receive viewExportSnapshot postMessages instead.
79
+ * Session/export JSON shape: extensions/agentide/src/analytics/dashboard/FLEET_DATA_CONTRACT.md
80
+ */
81
+ /** Timer for Fleet view auto-refresh; cleared when view changes or interval is 0. */
82
+ let fleetAutoRefreshTimerId = null;
83
+
84
+ /**
85
+ * Only auto-refresh Fleet when the user is at the top of the widget.
86
+ * We check both the outer card scroll container and the embedded iframe
87
+ * document scroll position because either can own scrolling depending on
88
+ * current layout/height.
89
+ * @returns {boolean}
90
+ */
91
+ function isFleetWidgetAtTop() {
92
+ const fleetCard = /** @type {HTMLElement | null} */ (document.querySelector('.fleet-dashboard-full.artifact-card'));
93
+ if (!fleetCard) {
94
+ return true;
95
+ }
96
+
97
+ const contentEl = /** @type {HTMLElement | null} */ (fleetCard.querySelector('.artifact-content'));
98
+ if (contentEl && contentEl.scrollTop > 4) {
99
+ return false;
100
+ }
101
+
102
+ const iframe = /** @type {HTMLIFrameElement | null} */ (fleetCard.querySelector('.html-preview iframe'));
103
+ if (!iframe) {
104
+ return true;
105
+ }
106
+
107
+ try {
108
+ const iframeDoc = iframe.contentDocument;
109
+ const bodyTop = iframeDoc?.body?.scrollTop ?? 0;
110
+ const docTop = iframeDoc?.documentElement?.scrollTop ?? 0;
111
+ const winTop = iframe.contentWindow?.scrollY ?? 0;
112
+ return bodyTop <= 4 && docTop <= 4 && winTop <= 4;
113
+ } catch {
114
+ // If iframe scroll is unavailable, avoid blocking auto-refresh.
115
+ return true;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Start or stop Fleet auto-refresh based on current view and configured interval.
121
+ * Call after activeViewId or fleetAutoRefreshIntervalSeconds changes.
122
+ */
123
+ /** @returns {{ id: string, name: string, scope: 'global'|'workspace', system: boolean, layout?: string, refreshOnNavigate?: string, preserveFleetIframe?: boolean } | undefined} */
124
+ function getActiveViewConfig() {
125
+ return state.views.find(view => view.id === state.activeViewId);
126
+ }
127
+
128
+ function activeViewUsesFleetDashboardLayout() {
129
+ return getActiveViewConfig()?.layout === 'fleet-dashboard';
130
+ }
131
+
132
+ function activeViewPreservesFleetIframe() {
133
+ return getActiveViewConfig()?.preserveFleetIframe === true;
134
+ }
135
+
136
+ /**
137
+ * Iframe-only snapshot views — no gallery Open/Delete footer.
138
+ * Config-driven via `preserveFleetIframe` (covers Workflows and any fleet-dashboard view).
139
+ * The id pattern is a back-compat fallback for legacy live-fleet views whose view.yaml
140
+ * predates the flag and derives `preserveFleetIframe === false` host-side.
141
+ */
142
+ function activeViewIsLiveFleetSnapshot() {
143
+ if (activeViewPreservesFleetIframe()) {
144
+ return true;
145
+ }
146
+ return /^live-fleet-\d+$/.test(String(state.activeViewId || ''));
147
+ }
148
+
149
+ function applyFleetAutoRefresh() {
150
+ if (fleetAutoRefreshTimerId !== null) {
151
+ clearInterval(fleetAutoRefreshTimerId);
152
+ fleetAutoRefreshTimerId = null;
153
+ }
154
+ const intervalSec = state.fleetAutoRefreshIntervalSeconds;
155
+ const isLiveFleetView = state.activeViewId === 'fleet' || activeViewPreservesFleetIframe();
156
+ if (isLiveFleetView && typeof intervalSec === 'number' && intervalSec > 0) {
157
+ const ms = intervalSec * 1000;
158
+ fleetAutoRefreshTimerId = setInterval(() => {
159
+ if (!isFleetWidgetAtTop()) {
160
+ return;
161
+ }
162
+ if (activeViewPreservesFleetIframe()) {
163
+ panelApi.postMessage({ type: REQUEST_VIEW_EXPORT_SNAPSHOT });
164
+ return;
165
+ }
166
+ panelApi.postMessage({ type: 'refreshView' });
167
+ }, ms);
168
+ }
169
+ }
170
+
171
+ function applyTeamViewSelectVisibility() {
172
+ const viewSelect = /** @type {HTMLSelectElement | null} */ (document.getElementById('view-select'));
173
+ if (!viewSelect) {
174
+ return;
175
+ }
176
+ if (state.surfaceMode === 'team') {
177
+ if (state.views.length > 1) {
178
+ viewSelect.style.setProperty('display', 'inline-block', 'important');
179
+ } else {
180
+ viewSelect.style.setProperty('display', 'none', 'important');
181
+ }
182
+ } else {
183
+ viewSelect.style.removeProperty('display');
184
+ }
185
+ }
186
+
187
+ function applyViewOptions() {
188
+ const viewSelect = /** @type {HTMLSelectElement | null} */ (document.getElementById('view-select'));
189
+ if (!viewSelect) {
190
+ return;
191
+ }
192
+ const previous = state.activeViewId || viewSelect.value || '';
193
+ viewSelect.innerHTML = '';
194
+
195
+ if (state.surfaceMode !== 'team') {
196
+ const addViewOption = document.createElement('option');
197
+ addViewOption.value = ADD_VIEW_OPTION_VALUE;
198
+ addViewOption.textContent = '+ Add view…';
199
+ viewSelect.appendChild(addViewOption);
200
+ }
201
+
202
+ for (const view of state.views) {
203
+ const option = document.createElement('option');
204
+ option.value = view.id;
205
+ option.textContent = view.name;
206
+ viewSelect.appendChild(option);
207
+ }
208
+ const next = state.views.some((view) => view.id === state.activeViewId)
209
+ ? state.activeViewId
210
+ : previous;
211
+ if (next && state.views.some((view) => view.id === next)) {
212
+ viewSelect.value = next;
213
+ state.activeViewId = next;
214
+ }
215
+ applyTeamViewSelectVisibility();
216
+ applyShareButtonState();
217
+ }
218
+
219
+ const SHARE_ICON_HTML = '<span class="btn-icon" aria-hidden="true">&#8593;</span>';
220
+ const INSTALL_ICON_HTML = '<span class="btn-icon" aria-hidden="true">&#8595;</span>';
221
+
222
+ function onInstallViewClick() {
223
+ panelApi.postMessage({ type: 'installView' });
224
+ }
225
+
226
+ function onShareViewClick() {
227
+ const activeView = getActiveView();
228
+ // Always forward to the extension host so built-in views get the explanatory toast.
229
+ panelApi.postMessage({
230
+ type: 'shareView',
231
+ viewId: activeView?.id ?? '',
232
+ bundle: 'full',
233
+ });
234
+ }
235
+
236
+ function onShareScriptsOnlyClick() {
237
+ const activeView = getActiveView();
238
+ panelApi.postMessage({
239
+ type: 'shareView',
240
+ viewId: activeView?.id ?? '',
241
+ bundle: 'scripts-only',
242
+ });
243
+ }
244
+
245
+ /**
246
+ * Insert share toolbar control when user's panel.html predates the share button.
247
+ */
248
+ function ensureShareToolbarButton() {
249
+ let shareBtn = document.getElementById('share-view-btn');
250
+ if (!shareBtn) {
251
+ const refreshBtn = document.getElementById('refresh-charts-btn');
252
+ if (!refreshBtn?.parentElement) {
253
+ return;
254
+ }
255
+ let iconGroup = refreshBtn.closest('.toolbar-icon-group');
256
+ if (!iconGroup) {
257
+ iconGroup = document.createElement('div');
258
+ iconGroup.className = 'toolbar-icon-group';
259
+ refreshBtn.parentElement.insertBefore(iconGroup, refreshBtn);
260
+ iconGroup.appendChild(refreshBtn);
261
+ }
262
+ shareBtn = document.createElement('button');
263
+ shareBtn.id = 'share-view-btn';
264
+ shareBtn.className = 'toolbar-btn icon-only';
265
+ shareBtn.title = 'Publish current view to store (full bundle)';
266
+ shareBtn.setAttribute('aria-label', shareBtn.title);
267
+ shareBtn.innerHTML = SHARE_ICON_HTML;
268
+ iconGroup.appendChild(shareBtn);
269
+ } else {
270
+ const refreshBtn = document.getElementById('refresh-charts-btn');
271
+ const icon = shareBtn.querySelector('.btn-icon');
272
+ if (icon && icon.textContent !== '↑') {
273
+ icon.outerHTML = SHARE_ICON_HTML;
274
+ }
275
+ if (refreshBtn && shareBtn.compareDocumentPosition(refreshBtn) & Node.DOCUMENT_POSITION_FOLLOWING) {
276
+ refreshBtn.insertAdjacentElement('afterend', shareBtn);
277
+ }
278
+ }
279
+ const boundShareBtn = document.getElementById('share-view-btn');
280
+ if (boundShareBtn && !boundShareBtn.dataset.boundShareClick) {
281
+ boundShareBtn.dataset.boundShareClick = '1';
282
+ boundShareBtn.addEventListener('click', onShareViewClick);
283
+ }
284
+ applyShareButtonState();
285
+ ensureInstallToolbarButton();
286
+ }
287
+
288
+ /**
289
+ * Insert install toolbar control when user's panel.html predates the install button.
290
+ */
291
+ function ensureInstallToolbarButton() {
292
+ let installBtn = document.getElementById('install-view-btn');
293
+ if (!installBtn) {
294
+ const shareBtn = document.getElementById('share-view-btn');
295
+ if (!shareBtn?.parentElement) {
296
+ return;
297
+ }
298
+ let iconGroup = shareBtn.closest('.toolbar-icon-group');
299
+ if (!iconGroup) {
300
+ iconGroup = document.createElement('div');
301
+ iconGroup.className = 'toolbar-icon-group';
302
+ shareBtn.parentElement.insertBefore(iconGroup, shareBtn);
303
+ iconGroup.appendChild(shareBtn);
304
+ }
305
+ installBtn = document.createElement('button');
306
+ installBtn.id = 'install-view-btn';
307
+ installBtn.className = 'toolbar-btn icon-only';
308
+ installBtn.title = 'Install Spotlight view from store';
309
+ installBtn.setAttribute('aria-label', installBtn.title);
310
+ installBtn.innerHTML = INSTALL_ICON_HTML;
311
+ shareBtn.insertAdjacentElement('afterend', installBtn);
312
+ } else {
313
+ const shareBtn = document.getElementById('share-view-btn');
314
+ const icon = installBtn.querySelector('.btn-icon');
315
+ if (icon && icon.textContent !== '↓') {
316
+ icon.outerHTML = INSTALL_ICON_HTML;
317
+ }
318
+ if (shareBtn && installBtn.compareDocumentPosition(shareBtn) & Node.DOCUMENT_POSITION_FOLLOWING) {
319
+ shareBtn.insertAdjacentElement('afterend', installBtn);
320
+ }
321
+ }
322
+ const boundInstallBtn = document.getElementById('install-view-btn');
323
+ if (boundInstallBtn && !boundInstallBtn.dataset.boundInstallClick) {
324
+ boundInstallBtn.dataset.boundInstallClick = '1';
325
+ boundInstallBtn.addEventListener('click', onInstallViewClick);
326
+ }
327
+ }
328
+
329
+ function applyShareButtonState() {
330
+ const shareBtn = document.getElementById('share-view-btn');
331
+ if (!shareBtn) {
332
+ return;
333
+ }
334
+ const activeView = getActiveView();
335
+ const canShare = Boolean(activeView && !activeView.system);
336
+ shareBtn.classList.toggle('share-unavailable', !canShare);
337
+ shareBtn.title = canShare
338
+ ? 'Publish current view to store (full bundle)'
339
+ : 'Built-in views cannot be shared (duplicate or create a custom view)';
340
+ shareBtn.setAttribute('aria-label', shareBtn.title);
341
+
342
+ const scriptsOnlyItem = document.getElementById('share-scripts-only-menu-item');
343
+ if (scriptsOnlyItem) {
344
+ // Keep clickable so the host can show the same built-in-view explanation.
345
+ scriptsOnlyItem.disabled = false;
346
+ scriptsOnlyItem.classList.toggle('share-unavailable', !canShare);
347
+ scriptsOnlyItem.title = canShare
348
+ ? 'Publish widget scripts only (no charts or exports)'
349
+ : 'Built-in views cannot be shared (duplicate or create a custom view)';
350
+ }
351
+ }
352
+
353
+ function getActiveView() {
354
+ return state.views.find((view) => view.id === state.activeViewId);
355
+ }
356
+
357
+ /**
358
+ * @param {string} nextViewId
359
+ */
360
+ function switchToView(nextViewId) {
361
+ if (!nextViewId || nextViewId === state.activeViewId) {
362
+ return;
363
+ }
364
+ state.activeViewId = nextViewId;
365
+ state.panelStateLoaded = false;
366
+ state.panelState = createDefaultPanelState();
367
+ const viewSelect = /** @type {HTMLSelectElement | null} */ (document.getElementById('view-select'));
368
+ if (viewSelect && state.views.some((view) => view.id === nextViewId)) {
369
+ viewSelect.value = nextViewId;
370
+ }
371
+ applyFleetAutoRefresh();
372
+ applyShareButtonState();
373
+ panelApi.postMessage({ type: 'setActiveView', viewId: nextViewId });
374
+ panelApi.postMessage({ type: 'requestPanelState', panelId: PANEL_ID });
375
+ }
376
+
377
+ /**
378
+ * Show the "New view name" modal. Used instead of prompt() because
379
+ * window.prompt() is not available in VS Code webviews.
380
+ * @param {() => void} [onResetSelect] - Optional callback to reset view dropdown (e.g. when opened from "+ Add view…").
381
+ */
382
+ function showNewViewModal(onResetSelect) {
383
+ const overlay = document.getElementById('new-view-modal');
384
+ const input = document.getElementById('new-view-name-input');
385
+ const okBtn = document.getElementById('new-view-modal-ok');
386
+ const cancelBtn = document.getElementById('new-view-modal-cancel');
387
+ if (!overlay || !input || !okBtn || !cancelBtn) {
388
+ return;
389
+ }
390
+ if (typeof onResetSelect === 'function') {
391
+ onResetSelect();
392
+ }
393
+ input.value = '';
394
+ overlay.style.display = 'flex';
395
+ overlay.setAttribute('aria-hidden', 'false');
396
+ input.focus();
397
+
398
+ function hide() {
399
+ overlay.style.display = 'none';
400
+ overlay.setAttribute('aria-hidden', 'true');
401
+ overlay.removeEventListener('click', handleOverlayClick);
402
+ document.removeEventListener('keydown', handleKeydown);
403
+ }
404
+
405
+ function submit() {
406
+ const trimmed = input.value.trim();
407
+ hide();
408
+ if (trimmed) {
409
+ panelApi.postMessage({ type: 'createView', name: trimmed });
410
+ }
411
+ }
412
+
413
+ function handleOverlayClick(e) {
414
+ if (e.target === overlay) {
415
+ hide();
416
+ }
417
+ }
418
+
419
+ function handleKeydown(e) {
420
+ if (e.key === 'Escape') {
421
+ hide();
422
+ e.preventDefault();
423
+ } else if (e.key === 'Enter') {
424
+ submit();
425
+ e.preventDefault();
426
+ }
427
+ }
428
+
429
+ overlay.addEventListener('click', handleOverlayClick);
430
+ document.addEventListener('keydown', handleKeydown);
431
+
432
+ okBtn.onclick = () => submit();
433
+ cancelBtn.onclick = () => hide();
434
+ }
435
+
436
+ function promptCreateView() {
437
+ showNewViewModal();
438
+ }
439
+
440
+ function initViews() {
441
+ const viewSelect = /** @type {HTMLSelectElement | null} */ (document.getElementById('view-select'));
442
+ viewSelect?.addEventListener('change', () => {
443
+ const nextViewId = viewSelect.value || '';
444
+ if (!nextViewId) {
445
+ return;
446
+ }
447
+ if (nextViewId === ADD_VIEW_OPTION_VALUE) {
448
+ showNewViewModal(() => {
449
+ viewSelect.value = state.activeViewId || '';
450
+ });
451
+ return;
452
+ }
453
+ if (nextViewId === state.activeViewId) {
454
+ return;
455
+ }
456
+ switchToView(nextViewId);
457
+ });
458
+ }
459
+
460
+ function refreshCurrentView() {
461
+ const btn = document.getElementById('refresh-charts-btn');
462
+ if (btn && !btn.classList.contains('refreshing')) {
463
+ setRefreshButtonState(true);
464
+ panelApi.postMessage({ type: 'refreshView' });
465
+ }
466
+ }
467
+
468
+ // -----------------------------------------------------------------------------
469
+ // Utility Functions
470
+ // -----------------------------------------------------------------------------
471
+
472
+ /**
473
+ * Format a date as relative time (e.g., "2 min ago")
474
+ * @param {Date} date
475
+ * @returns {string}
476
+ */
477
+ function formatTimeAgo(date) {
478
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
479
+ if (seconds < 60) {
480
+ return 'just now';
481
+ }
482
+ if (seconds < 3600) {
483
+ return `${Math.floor(seconds / 60)} min ago`;
484
+ }
485
+ if (seconds < 86400) {
486
+ return `${Math.floor(seconds / 3600)}h ago`;
487
+ }
488
+ return `${Math.floor(seconds / 86400)}d ago`;
489
+ }
490
+
491
+ /**
492
+ * Format file size in human-readable form
493
+ * @param {number} bytes
494
+ * @returns {string}
495
+ */
496
+ function formatSize(bytes) {
497
+ if (bytes < 1024) {
498
+ return `${bytes} B`;
499
+ }
500
+ if (bytes < 1024 * 1024) {
501
+ return `${(bytes / 1024).toFixed(1)} KB`;
502
+ }
503
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
504
+ }
505
+
506
+ /**
507
+ * Escape HTML special characters
508
+ * @param {string} text
509
+ * @returns {string}
510
+ */
511
+ function escapeHtml(text) {
512
+ const div = document.createElement('div');
513
+ div.textContent = text;
514
+ return div.innerHTML;
515
+ }
516
+
517
+ /**
518
+ * Simple markdown rendering
519
+ * @param {string} text
520
+ * @returns {string}
521
+ */
522
+ function renderMarkdown(text) {
523
+ return text
524
+ .replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code class="language-$1">$2</code></pre>')
525
+ .replace(/`([^`]+)`/g, '<code>$1</code>')
526
+ .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
527
+ .replace(/\*(.+?)\*/g, '<em>$1</em>')
528
+ .replace(/^### (.+)$/gm, '<h3>$1</h3>')
529
+ .replace(/^## (.+)$/gm, '<h2>$1</h2>')
530
+ .replace(/^# (.+)$/gm, '<h1>$1</h1>')
531
+ .replace(/\n/g, '<br>');
532
+ }
533
+
534
+ /**
535
+ * @param {string} value
536
+ * @returns {string}
537
+ */
538
+ function normalizeFilename(value) {
539
+ return String(value || '').trim().toLowerCase();
540
+ }
541
+
542
+ /**
543
+ * @param {unknown} raw
544
+ * @returns {PanelState}
545
+ */
546
+ function normalizePanelState(raw) {
547
+ const parsed = raw && typeof raw === 'object' ? /** @type {Record<string, unknown>} */ (raw) : {};
548
+ const rawCharts = parsed.charts && typeof parsed.charts === 'object'
549
+ ? /** @type {Record<string, unknown>} */ (parsed.charts)
550
+ : {};
551
+ /** @type {Record<string, PanelChartState>} */
552
+ const charts = {};
553
+ let nextOrder = 1;
554
+ for (const [key, value] of Object.entries(rawCharts)) {
555
+ const filenameKey = normalizeFilename(key);
556
+ if (!filenameKey) {
557
+ continue;
558
+ }
559
+ const entry = value && typeof value === 'object' ? /** @type {Record<string, unknown>} */ (value) : {};
560
+ const parsedOrder = Number(entry.order);
561
+ const order = Number.isFinite(parsedOrder) && parsedOrder > 0 ? parsedOrder : nextOrder;
562
+ const hidden = entry.hidden === true;
563
+ const height = Number(entry.height);
564
+ const expanded = entry.expanded === true;
565
+ charts[filenameKey] = {
566
+ order,
567
+ hidden,
568
+ height: Number.isFinite(height) && height >= 120 ? height : undefined,
569
+ expanded,
570
+ };
571
+ nextOrder = Math.max(nextOrder, order + 1);
572
+ }
573
+ const summaryHidden = parsed.summaryHidden === true;
574
+ return { version: 1, charts, summaryHidden };
575
+ }
576
+
577
+ /**
578
+ * @returns {number}
579
+ */
580
+ function getNextChartOrder() {
581
+ const entries = Object.values(state.panelState.charts);
582
+ if (entries.length === 0) {
583
+ return 1;
584
+ }
585
+ return entries.reduce((max, item) => Math.max(max, item.order), 0) + 1;
586
+ }
587
+
588
+ /**
589
+ * Ensure every discovered chart has a panel-state entry.
590
+ * @param {Array<object>} artifacts
591
+ * @returns {boolean}
592
+ */
593
+ function ensurePanelStateForArtifacts(artifacts) {
594
+ let changed = false;
595
+ let nextOrder = getNextChartOrder();
596
+ for (const artifact of artifacts) {
597
+ if (artifact.type !== 'chart' && artifact.type !== 'image') {
598
+ continue;
599
+ }
600
+ const key = normalizeFilename(String(artifact.filename || ''));
601
+ if (!key) {
602
+ continue;
603
+ }
604
+ if (!state.panelState.charts[key]) {
605
+ state.panelState.charts[key] = { order: nextOrder++, hidden: false, height: undefined, expanded: false };
606
+ changed = true;
607
+ }
608
+ }
609
+ return changed;
610
+ }
611
+
612
+ /**
613
+ * @param {string} filename
614
+ * @returns {PanelChartState}
615
+ */
616
+ function getChartPanelState(filename) {
617
+ const key = normalizeFilename(filename);
618
+ if (!state.panelState.charts[key]) {
619
+ state.panelState.charts[key] = { order: getNextChartOrder(), hidden: false, height: undefined, expanded: false };
620
+ }
621
+ return state.panelState.charts[key];
622
+ }
623
+
624
+ function setChartHeight(filename, heightPx) {
625
+ const chartState = getChartPanelState(filename);
626
+ chartState.height = heightPx >= 120 && heightPx <= 2000 ? heightPx : undefined;
627
+ savePanelState();
628
+ }
629
+
630
+ function setChartExpanded(filename, expanded) {
631
+ const chartState = getChartPanelState(filename);
632
+ chartState.expanded = expanded === true;
633
+ savePanelState();
634
+ }
635
+
636
+ /**
637
+ * Auto-fit Fleet iframe content on first load when no explicit panel height
638
+ * has been saved yet. This prevents an oversized empty area at the bottom
639
+ * while preserving click-to-expand behavior.
640
+ * @param {HTMLElement} card
641
+ */
642
+ function autoFitFleetCardHeight(card) {
643
+ const filename = card?.dataset?.filename || '';
644
+ if (!filename) {
645
+ return;
646
+ }
647
+ const chartState = getChartPanelState(filename);
648
+ if (typeof chartState.height === 'number' && Number.isFinite(chartState.height)) {
649
+ return;
650
+ }
651
+ const content = /** @type {HTMLElement | null} */ (card.querySelector('.artifact-content'));
652
+ const iframe = /** @type {HTMLIFrameElement | null} */ (card.querySelector('.html-preview iframe'));
653
+ if (!content || !iframe) {
654
+ return;
655
+ }
656
+ let docHeight = 0;
657
+ try {
658
+ const doc = iframe.contentDocument;
659
+ const body = doc?.body;
660
+ const html = doc?.documentElement;
661
+ docHeight = Math.max(
662
+ body?.scrollHeight || 0,
663
+ body?.offsetHeight || 0,
664
+ html?.scrollHeight || 0,
665
+ html?.offsetHeight || 0,
666
+ html?.clientHeight || 0,
667
+ );
668
+ } catch {
669
+ return;
670
+ }
671
+ if (!Number.isFinite(docHeight) || docHeight <= 0) {
672
+ return;
673
+ }
674
+ const computed = window.getComputedStyle(content);
675
+ const paddingTop = Number.parseFloat(computed.paddingTop || '0') || 0;
676
+ const paddingBottom = Number.parseFloat(computed.paddingBottom || '0') || 0;
677
+ const targetHeight = Math.round(Math.max(320, Math.min(2000, docHeight + paddingTop + paddingBottom)));
678
+ content.style.height = `${targetHeight}px`;
679
+ content.style.minHeight = `${targetHeight}px`;
680
+ content.style.overflow = 'hidden';
681
+ syncEmbeddedPreviewSize(content);
682
+ setChartHeight(filename, targetHeight);
683
+ }
684
+
685
+ /**
686
+ * Keep embedded HTML previews sized to the resized card and trigger a resize
687
+ * event inside iframe widgets so responsive layouts can relayout.
688
+ * @param {HTMLElement} content
689
+ */
690
+ function syncEmbeddedPreviewSize(content) {
691
+ const preview = content.querySelector('.html-preview');
692
+ if (preview instanceof HTMLElement) {
693
+ preview.style.height = '100%';
694
+ preview.style.minHeight = '0';
695
+ }
696
+ const iframe = content.querySelector('.html-preview iframe');
697
+ if (iframe instanceof HTMLIFrameElement) {
698
+ iframe.style.height = '100%';
699
+ iframe.style.minHeight = '0';
700
+ iframe.style.display = 'block';
701
+ try {
702
+ iframe.contentWindow?.dispatchEvent(new Event('resize'));
703
+ } catch {
704
+ // Cross-origin frame or transient load state; ignore.
705
+ }
706
+ }
707
+ }
708
+
709
+ /** @param {Element | null} card @param {string} filename */
710
+ function attachResizeHandle(card, filename) {
711
+ if (!card || !filename) return;
712
+ const resizeHandle = card.querySelector('.resize-handle');
713
+ const contentEl = card.querySelector('.artifact-content');
714
+ if (!resizeHandle || !contentEl) return;
715
+ const cardEl = /** @type {HTMLElement} */ (card);
716
+ const content = /** @type {HTMLElement} */ (contentEl);
717
+ const minH = 120;
718
+ const maxH = 2000;
719
+ resizeHandle.addEventListener('mousedown', (e) => {
720
+ e.preventDefault();
721
+ e.stopPropagation();
722
+ const startY = e.clientY;
723
+ const rect = content.getBoundingClientRect();
724
+ const startHeight = rect.height;
725
+ let didResize = false;
726
+ let rafId = 0;
727
+ const schedulePreviewResize = () => {
728
+ if (rafId) {
729
+ cancelAnimationFrame(rafId);
730
+ }
731
+ rafId = requestAnimationFrame(() => {
732
+ rafId = 0;
733
+ syncEmbeddedPreviewSize(content);
734
+ });
735
+ };
736
+ const onMove = (moveEvent) => {
737
+ const dy = moveEvent.clientY - startY;
738
+ if (Math.abs(dy) > 2) {
739
+ didResize = true;
740
+ }
741
+ let h = Math.round(startHeight + dy);
742
+ h = Math.max(minH, Math.min(maxH, h));
743
+ content.style.height = `${h}px`;
744
+ content.style.minHeight = `${h}px`;
745
+ content.style.overflow = 'hidden';
746
+ schedulePreviewResize();
747
+ };
748
+ const onUp = () => {
749
+ document.removeEventListener('mousemove', onMove);
750
+ document.removeEventListener('mouseup', onUp);
751
+ if (rafId) {
752
+ cancelAnimationFrame(rafId);
753
+ rafId = 0;
754
+ }
755
+ syncEmbeddedPreviewSize(content);
756
+ if (didResize) {
757
+ const finalHeight = content.getBoundingClientRect().height;
758
+ setChartHeight(filename, Math.round(finalHeight));
759
+ return;
760
+ }
761
+ const nextExpanded = !cardEl.classList.contains('expanded');
762
+ cardEl.classList.toggle('expanded', nextExpanded);
763
+ setChartExpanded(filename, nextExpanded);
764
+ };
765
+ document.addEventListener('mousemove', onMove);
766
+ document.addEventListener('mouseup', onUp);
767
+ });
768
+ }
769
+
770
+ function toggleSummarySection() {
771
+ state.panelState.summaryHidden = !state.panelState.summaryHidden;
772
+ applySummarySectionVisibility();
773
+ savePanelState();
774
+ }
775
+
776
+ function applySummarySectionVisibility() {
777
+ const section = document.getElementById('summary-section');
778
+ const btn = document.getElementById('summary-toggle-btn');
779
+ if (section) {
780
+ section.classList.toggle('collapsed', state.panelState.summaryHidden === true);
781
+ }
782
+ if (btn) {
783
+ btn.textContent = state.panelState.summaryHidden ? 'Show' : 'Hide';
784
+ btn.title = state.panelState.summaryHidden ? 'Show summary section' : 'Hide summary section';
785
+ btn.setAttribute('aria-label', state.panelState.summaryHidden ? 'Show summary' : 'Hide summary');
786
+ }
787
+ }
788
+
789
+ function savePanelState() {
790
+ panelApi.postMessage({
791
+ type: 'savePanelState',
792
+ panelId: PANEL_ID,
793
+ state: state.panelState,
794
+ });
795
+ }
796
+
797
+ /**
798
+ * @param {string} filename
799
+ * @param {'up' | 'down'} direction
800
+ * @param {string[]} activeChartKeys
801
+ */
802
+ function moveChartInPanelState(filename, direction, activeChartKeys) {
803
+ const targetKey = normalizeFilename(filename);
804
+ if (!targetKey) {
805
+ return;
806
+ }
807
+ getChartPanelState(filename);
808
+ const orderedKeys = Object.entries(state.panelState.charts)
809
+ .sort((a, b) => a[1].order - b[1].order || a[0].localeCompare(b[0]))
810
+ .map(([key]) => key);
811
+ const index = orderedKeys.indexOf(targetKey);
812
+ if (index < 0) {
813
+ return;
814
+ }
815
+ const normalizedActiveKeys = new Set(
816
+ (activeChartKeys || []).map(key => normalizeFilename(key)).filter(Boolean),
817
+ );
818
+ normalizedActiveKeys.add(targetKey);
819
+ const step = direction === 'up' ? -1 : 1;
820
+ let swapIndex = index + step;
821
+ while (swapIndex >= 0 && swapIndex < orderedKeys.length) {
822
+ if (normalizedActiveKeys.has(orderedKeys[swapIndex])) {
823
+ break;
824
+ }
825
+ swapIndex += step;
826
+ }
827
+ if (swapIndex < 0 || swapIndex >= orderedKeys.length || swapIndex === index) {
828
+ return;
829
+ }
830
+ const [entry] = orderedKeys.splice(index, 1);
831
+ orderedKeys.splice(swapIndex, 0, entry);
832
+ orderedKeys.forEach((key, orderIndex) => {
833
+ const chart = state.panelState.charts[key];
834
+ chart.order = orderIndex + 1;
835
+ });
836
+ savePanelState();
837
+ }
838
+
839
+ /**
840
+ * @param {string} filename
841
+ */
842
+ function toggleChartHiddenInPanelState(filename) {
843
+ const chartState = getChartPanelState(filename);
844
+ chartState.hidden = !chartState.hidden;
845
+ savePanelState();
846
+ }
847
+
848
+ // -----------------------------------------------------------------------------
849
+ // Summary Cards
850
+ // -----------------------------------------------------------------------------
851
+
852
+ /**
853
+ * Render summary cards in the header
854
+ * @param {Array<{label: string, value: string, icon: string}>} cards
855
+ */
856
+ function renderSummaryCards(cards) {
857
+ const container = document.getElementById('summary-cards');
858
+ if (!container) {
859
+ return;
860
+ }
861
+ if (!cards || cards.length === 0) {
862
+ renderSummaryPlaceholders();
863
+ return;
864
+ }
865
+
866
+ container.style.display = 'grid';
867
+ container.classList.remove('loading');
868
+ container.innerHTML = cards.map(card => `
869
+ <div class="metric-card">
870
+ <div class="metric-icon">${card.icon}</div>
871
+ <div class="metric-value">${escapeHtml(card.value)}</div>
872
+ <div class="metric-label">${escapeHtml(card.label)}</div>
873
+ </div>
874
+ `).join('');
875
+ }
876
+
877
+ /**
878
+ * Render summary placeholders while data is loading.
879
+ */
880
+ function renderSummaryPlaceholders() {
881
+ const container = document.getElementById('summary-cards');
882
+ if (!container) {
883
+ return;
884
+ }
885
+
886
+ container.style.display = 'grid';
887
+ container.classList.add('loading');
888
+ container.innerHTML = Array.from({ length: 4 }).map(() => `
889
+ <div class="metric-card skeleton">
890
+ <div class="skeleton-line skeleton-icon"></div>
891
+ <div class="skeleton-line skeleton-value"></div>
892
+ <div class="skeleton-line skeleton-label"></div>
893
+ </div>
894
+ `).join('');
895
+ }
896
+
897
+ // -----------------------------------------------------------------------------
898
+ // Gallery Rendering
899
+ // -----------------------------------------------------------------------------
900
+
901
+ /**
902
+ * Show a specific gallery state
903
+ * @param {'loading' | 'empty' | 'generating' | 'failed' | 'content'} stateName
904
+ */
905
+ function showGalleryState(stateName) {
906
+ const states = ['loading-state', 'empty-state', 'generating-state', 'failed-state', 'gallery-content'];
907
+ for (const id of states) {
908
+ const el = document.getElementById(id);
909
+ if (el) {
910
+ el.style.display = id === `${stateName}-state` || id === `${stateName}` ? 'flex' : 'none';
911
+ }
912
+ }
913
+
914
+ // gallery-content needs 'block' not 'flex'
915
+ const content = document.getElementById('gallery-content');
916
+ if (content) {
917
+ content.style.display = stateName === 'content' ? 'block' : 'none';
918
+ }
919
+ }
920
+
921
+ /**
922
+ * Render the artifact gallery
923
+ * @param {Array<object>} artifacts
924
+ * @param {{ forceFreshHtml?: boolean }} [options]
925
+ */
926
+ function renderGallery(artifacts, options) {
927
+ const forceFreshHtml = options?.forceFreshHtml === true;
928
+ const container = document.getElementById('gallery-content');
929
+ if (!container) {
930
+ return;
931
+ }
932
+ // Preserve existing HTML iframes across incremental refreshes when the
933
+ // artifact content version (modifiedAt) did not change.
934
+ /** @type {Map<string, HTMLIFrameElement>} */
935
+ const reusableHtmlPreviews = new Map();
936
+ container.querySelectorAll('.artifact-card').forEach((cardEl) => {
937
+ const card = /** @type {HTMLElement} */ (cardEl);
938
+ const path = card.dataset.path || '';
939
+ const modifiedAt = card.dataset.modifiedAt || '';
940
+ if (!path || !modifiedAt) {
941
+ return;
942
+ }
943
+ const iframe = /** @type {HTMLIFrameElement | null} */ (card.querySelector('.html-preview iframe'));
944
+ if (!iframe) {
945
+ return;
946
+ }
947
+ reusableHtmlPreviews.set(`${path}::${modifiedAt}`, iframe);
948
+ });
949
+
950
+ const layoutChanged = ensurePanelStateForArtifacts(artifacts);
951
+ if (layoutChanged && state.panelStateLoaded) {
952
+ savePanelState();
953
+ }
954
+
955
+ const visibleArtifacts = artifacts.filter((artifact) => {
956
+ const filename = String(artifact.filename || '').trim().toLowerCase();
957
+ // Hide artifacts matching galleryIgnorePatterns (suffix or exact name); list is from disabled-charts.yaml
958
+ const ignored = state.galleryIgnorePatterns.some((p) => {
959
+ const pattern = String(p).trim().toLowerCase();
960
+ return pattern && (filename.endsWith(pattern) || filename === pattern);
961
+ });
962
+ if (ignored) {
963
+ return false;
964
+ }
965
+ // Live Fleet iframe views: export JSON is pushed via viewExportSnapshot — skip gallery cards (Open/Delete).
966
+ if (activeViewIsLiveFleetSnapshot() && artifact.type !== 'chart' && artifact.type !== 'image') {
967
+ return false;
968
+ }
969
+ if (artifact.type !== 'chart' && artifact.type !== 'image') {
970
+ return true;
971
+ }
972
+ return !state.disabledCharts.has(filename);
973
+ });
974
+
975
+ const chartArtifacts = visibleArtifacts
976
+ .filter((artifact) => artifact.type === 'chart' || artifact.type === 'image')
977
+ .map((artifact) => ({
978
+ ...artifact,
979
+ _panel: getChartPanelState(String(artifact.filename || '')),
980
+ }))
981
+ .sort((a, b) => {
982
+ if (a._panel.hidden !== b._panel.hidden) {
983
+ return a._panel.hidden ? 1 : -1;
984
+ }
985
+ if (a._panel.order !== b._panel.order) {
986
+ return a._panel.order - b._panel.order;
987
+ }
988
+ return new Date(b.modifiedAt).getTime() - new Date(a.modifiedAt).getTime();
989
+ });
990
+ const interactiveChartKeys = chartArtifacts.map((artifact) => normalizeFilename(String(artifact.filename || '')));
991
+ const nonChartArtifacts = visibleArtifacts
992
+ .filter((artifact) => artifact.type !== 'chart')
993
+ .sort((a, b) => new Date(b.modifiedAt).getTime() - new Date(a.modifiedAt).getTime());
994
+ const orderedArtifacts = [...chartArtifacts, ...nonChartArtifacts];
995
+
996
+ if (orderedArtifacts.length === 0) {
997
+ showGalleryState('empty');
998
+ return;
999
+ }
1000
+
1001
+ // Export JSON alongside the chart is normal for live fleet views — do not fall back to gallery layout.
1002
+ const nonChartNonExportArtifacts = nonChartArtifacts.filter((artifact) => artifact.type !== 'export');
1003
+ const fleetHtmlCharts = chartArtifacts.filter((artifact) => artifact.format === 'html');
1004
+ const isFleetSingleHtml =
1005
+ (fleetHtmlCharts.length >= 1 && activeViewIsLiveFleetSnapshot())
1006
+ || (activeViewUsesFleetDashboardLayout() &&
1007
+ chartArtifacts.length === 1 &&
1008
+ chartArtifacts[0].format === 'html' &&
1009
+ nonChartNonExportArtifacts.length === 0);
1010
+ if (isFleetSingleHtml) {
1011
+ const artifact = fleetHtmlCharts[0];
1012
+ const path = artifact.localPath || '';
1013
+ const modifiedAt = String(artifact.modifiedAt || '');
1014
+ const fleetFilename = String(artifact.filename || '').trim() || '__fleet__';
1015
+ const chartState = getChartPanelState(fleetFilename);
1016
+ const fleetExpandedClass = chartState.expanded ? ' expanded' : '';
1017
+ const fleetHeightStyle = chartState.height
1018
+ ? `style="height: ${chartState.height}px; min-height: ${chartState.height}px; overflow: auto;"`
1019
+ : '';
1020
+ const existingFleetCard = /** @type {HTMLElement | null} */ (
1021
+ container.querySelector('.fleet-dashboard-full.artifact-card')
1022
+ );
1023
+ // preserveFleetIframe views: keep the iframe mounted; data updates arrive via viewExportSnapshot postMessage.
1024
+ let fleetIframeHasSnapshotShell = false;
1025
+ if (activeViewPreservesFleetIframe() && existingFleetCard) {
1026
+ const fleetIframe = existingFleetCard.querySelector('.html-preview iframe');
1027
+ if (fleetIframe instanceof HTMLIFrameElement) {
1028
+ try {
1029
+ const doc = fleetIframe.contentDocument;
1030
+ fleetIframeHasSnapshotShell =
1031
+ doc?.body?.dataset?.fleetSnapshotShell === '1'
1032
+ || doc?.documentElement?.hasAttribute('data-live-fleet-shell');
1033
+ } catch {
1034
+ fleetIframeHasSnapshotShell = false;
1035
+ }
1036
+ }
1037
+ }
1038
+ if (
1039
+ activeViewPreservesFleetIframe()
1040
+ && existingFleetCard
1041
+ && existingFleetCard.dataset.path === path
1042
+ && existingFleetCard.querySelector('.html-preview iframe')
1043
+ && fleetIframeHasSnapshotShell
1044
+ && !forceFreshHtml
1045
+ ) {
1046
+ showGalleryState('content');
1047
+ existingFleetCard.dataset.modifiedAt = modifiedAt;
1048
+ attachResizeHandle(existingFleetCard, fleetFilename);
1049
+ return;
1050
+ }
1051
+ const reusable = forceFreshHtml ? undefined : reusableHtmlPreviews.get(`${path}::${modifiedAt}`);
1052
+ showGalleryState('content');
1053
+ container.innerHTML = `
1054
+ <div class="fleet-dashboard-full artifact-card${fleetExpandedClass}" data-path="${escapeHtml(path)}" data-filename="${escapeHtml(fleetFilename)}" data-modified-at="${escapeHtml(modifiedAt)}">
1055
+ <div class="artifact-content" ${fleetHeightStyle}>
1056
+ <div class="html-preview">${reusable ? '' : '<div class="html-preview-loading">Loading Fleet Operations...</div>'}</div>
1057
+ </div>
1058
+ <div class="resize-handle" data-filename="${escapeHtml(fleetFilename)}" title="Drag to resize height" role="separator"></div>
1059
+ </div>
1060
+ `;
1061
+ if (reusable) {
1062
+ const host = container.querySelector('.html-preview');
1063
+ if (host) {
1064
+ host.classList.add('ready');
1065
+ host.appendChild(reusable);
1066
+ }
1067
+ } else {
1068
+ requestHtmlPreviewContents([artifact]);
1069
+ }
1070
+ const fleetCard = container.querySelector('.artifact-card');
1071
+ attachResizeHandle(fleetCard, fleetFilename);
1072
+ if (fleetCard instanceof HTMLElement) {
1073
+ fleetCard.addEventListener('click', (event) => {
1074
+ const target = /** @type {HTMLElement | null} */ (event.target instanceof HTMLElement ? event.target : null);
1075
+ if (target?.closest('button,.resize-handle')) {
1076
+ return;
1077
+ }
1078
+ const nextExpanded = !fleetCard.classList.contains('expanded');
1079
+ fleetCard.classList.toggle('expanded', nextExpanded);
1080
+ setChartExpanded(fleetFilename, nextExpanded);
1081
+ });
1082
+ }
1083
+ return;
1084
+ }
1085
+
1086
+ showGalleryState('content');
1087
+
1088
+ const html = orderedArtifacts.map(artifact => {
1089
+ const timeAgo = formatTimeAgo(new Date(artifact.modifiedAt));
1090
+ const sizeFormatted = formatSize(artifact.size);
1091
+ const isChart = artifact.type === 'chart';
1092
+ const isImage = artifact.type === 'image';
1093
+ const isVisual = isChart || isImage;
1094
+ const chartState = isVisual ? artifact._panel : undefined;
1095
+ const isHidden = chartState?.hidden === true;
1096
+ const isExpanded = chartState?.expanded === true;
1097
+ const order = Number.isFinite(chartState?.order) ? chartState.order : undefined;
1098
+
1099
+ // Render based on format
1100
+ let content = '';
1101
+ switch (artifact.format) {
1102
+ case 'png':
1103
+ case 'svg':
1104
+ case 'jpg':
1105
+ content = `<img src="${artifact.webviewUri}" alt="${escapeHtml(artifact.filename)}" loading="lazy" />`;
1106
+ break;
1107
+ case 'html':
1108
+ content = `
1109
+ <div class="html-preview" data-path="${escapeHtml(artifact.localPath)}">
1110
+ <div class="html-preview-loading">Loading preview...</div>
1111
+ </div>
1112
+ `;
1113
+ break;
1114
+ case 'md':
1115
+ content = `<div class="markdown-preview" data-path="${escapeHtml(artifact.localPath)}">Loading...</div>`;
1116
+ break;
1117
+ case 'csv':
1118
+ case 'json':
1119
+ content = `<div class="data-preview"><code>${escapeHtml(artifact.filename)}</code></div>`;
1120
+ break;
1121
+ default:
1122
+ content = `<div class="data-preview"><code>${escapeHtml(artifact.filename)}</code></div>`;
1123
+ }
1124
+
1125
+ // Find manifest entry for this artifact
1126
+ const manifestEntry = state.manifest?.artifacts?.find(e => e.filename === artifact.filename);
1127
+ const title = manifestEntry?.title || artifact.filename;
1128
+ const generatorBadge = artifact.generator
1129
+ ? `<span class="generator-badge">${escapeHtml(artifact.generator)}</span>`
1130
+ : '';
1131
+ const sourceBadge = manifestEntry?.source === 'default'
1132
+ ? '<span class="source-badge">Auto</span>'
1133
+ : '';
1134
+ const hiddenBadge = isVisual && isHidden
1135
+ ? '<span class="layout-badge disabled">Hidden</span>'
1136
+ : '';
1137
+ const orderBadge = isVisual && Number.isFinite(order)
1138
+ ? `<span class="layout-badge">#${order}</span>`
1139
+ : '';
1140
+ const layoutActions = isVisual
1141
+ ? `
1142
+ <button class="action-btn move-up-btn" title="Move up">↑</button>
1143
+ <button class="action-btn move-down-btn" title="Move down">↓</button>
1144
+ <button class="action-btn toggle-enabled-btn" title="${isHidden ? 'Show chart' : 'Hide chart'}">
1145
+ ${isHidden ? 'Show' : 'Hide'}
1146
+ </button>
1147
+ `
1148
+ : '';
1149
+ const contentHeightStyle = isVisual && chartState?.height
1150
+ ? `style="height: ${chartState.height}px; min-height: ${chartState.height}px; overflow: auto;"`
1151
+ : '';
1152
+ const resizeHandle = isVisual
1153
+ ? `<div class="resize-handle" data-filename="${escapeHtml(artifact.filename)}" title="Drag to resize height" role="separator"></div>`
1154
+ : '';
1155
+
1156
+ return `
1157
+ <div class="artifact-card ${isVisual && isHidden ? 'layout-disabled' : ''}${isExpanded ? ' expanded' : ''}" data-path="${escapeHtml(artifact.localPath)}" data-type="${artifact.type}" data-filename="${escapeHtml(artifact.filename)}" data-modified-at="${escapeHtml(String(artifact.modifiedAt || ''))}">
1158
+ <div class="artifact-content" ${contentHeightStyle}>
1159
+ ${content}
1160
+ </div>
1161
+ <div class="artifact-meta">
1162
+ <span class="artifact-name" title="${escapeHtml(artifact.filename)}">${escapeHtml(title)}</span>
1163
+ ${sourceBadge}
1164
+ ${hiddenBadge}
1165
+ ${orderBadge}
1166
+ ${generatorBadge}
1167
+ <span class="artifact-time">${timeAgo}</span>
1168
+ <span class="artifact-size">${sizeFormatted}</span>
1169
+ </div>
1170
+ <div class="artifact-actions">
1171
+ <button class="action-btn open-btn" title="Open in editor">Open</button>
1172
+ <button class="action-btn delete-btn" title="Delete artifact">Delete</button>
1173
+ ${layoutActions}
1174
+ </div>
1175
+ ${resizeHandle}
1176
+ </div>
1177
+ `;
1178
+ }).join('');
1179
+
1180
+ container.innerHTML = html;
1181
+ /** @type {Array<object>} */
1182
+ const htmlArtifactsNeedingFetch = [];
1183
+ /** @type {Array<object>} */
1184
+ const markdownArtifactsNeedingFetch = [];
1185
+ container.querySelectorAll('.artifact-card').forEach((cardEl) => {
1186
+ const card = /** @type {HTMLElement} */ (cardEl);
1187
+ const path = card.dataset.path || '';
1188
+ const modifiedAt = card.dataset.modifiedAt || '';
1189
+ const htmlHost = /** @type {HTMLElement | null} */ (card.querySelector('.html-preview'));
1190
+ if (htmlHost && path && modifiedAt) {
1191
+ const reusable = forceFreshHtml ? undefined : reusableHtmlPreviews.get(`${path}::${modifiedAt}`);
1192
+ if (reusable) {
1193
+ htmlHost.classList.add('ready');
1194
+ htmlHost.innerHTML = '';
1195
+ htmlHost.appendChild(reusable);
1196
+ } else {
1197
+ const htmlArtifact = orderedArtifacts.find((item) => item.localPath === path && item.format === 'html');
1198
+ if (htmlArtifact) {
1199
+ htmlArtifactsNeedingFetch.push(htmlArtifact);
1200
+ }
1201
+ }
1202
+ }
1203
+
1204
+ const markdownHost = /** @type {HTMLElement | null} */ (card.querySelector('.markdown-preview'));
1205
+ if (markdownHost && path) {
1206
+ const markdownArtifact = orderedArtifacts.find((item) => item.localPath === path && item.format === 'md');
1207
+ if (markdownArtifact) {
1208
+ markdownArtifactsNeedingFetch.push(markdownArtifact);
1209
+ }
1210
+ }
1211
+ });
1212
+ requestHtmlPreviewContents(htmlArtifactsNeedingFetch);
1213
+ requestMarkdownPreviewContents(markdownArtifactsNeedingFetch);
1214
+
1215
+ // Attach event handlers
1216
+ container.querySelectorAll('.artifact-card').forEach(cardEl => {
1217
+ const card = /** @type {HTMLElement} */ (cardEl);
1218
+ const path = card.dataset.path;
1219
+ const filename = card.dataset.filename;
1220
+ const isChart = card.dataset.type === 'chart';
1221
+ const isVisual = card.dataset.type === 'chart' || card.dataset.type === 'image';
1222
+
1223
+ card.querySelector('.open-btn')?.addEventListener('click', (e) => {
1224
+ e.stopPropagation();
1225
+ panelApi.postMessage({ type: 'openArtifact', path });
1226
+ });
1227
+
1228
+ card.querySelector('.delete-btn')?.addEventListener('click', (e) => {
1229
+ e.stopPropagation();
1230
+ panelApi.postMessage({ type: 'deleteArtifact', path });
1231
+ });
1232
+
1233
+ if (isChart && filename) {
1234
+ card.querySelector('.move-up-btn')?.addEventListener('click', (e) => {
1235
+ e.stopPropagation();
1236
+ moveChartInPanelState(filename, 'up', interactiveChartKeys);
1237
+ renderGallery(state.artifacts);
1238
+ });
1239
+
1240
+ card.querySelector('.move-down-btn')?.addEventListener('click', (e) => {
1241
+ e.stopPropagation();
1242
+ moveChartInPanelState(filename, 'down', interactiveChartKeys);
1243
+ renderGallery(state.artifacts);
1244
+ });
1245
+
1246
+ card.querySelector('.toggle-enabled-btn')?.addEventListener('click', (e) => {
1247
+ e.stopPropagation();
1248
+ toggleChartHiddenInPanelState(filename);
1249
+ renderGallery(state.artifacts);
1250
+ });
1251
+ }
1252
+
1253
+ // Click on card to expand/collapse
1254
+ card.addEventListener('click', (event) => {
1255
+ const target = /** @type {HTMLElement | null} */ (event.target instanceof HTMLElement ? event.target : null);
1256
+ if (target?.closest('button,.resize-handle')) {
1257
+ return;
1258
+ }
1259
+ const nextExpanded = !card.classList.contains('expanded');
1260
+ card.classList.toggle('expanded', nextExpanded);
1261
+ if (isVisual && filename) {
1262
+ setChartExpanded(filename, nextExpanded);
1263
+ }
1264
+ });
1265
+
1266
+ if (filename) {
1267
+ attachResizeHandle(card, filename);
1268
+ }
1269
+ });
1270
+
1271
+ // Markdown previews are loaded via host message passing (artifactContent)
1272
+ // to avoid fetch restrictions for local report files.
1273
+ }
1274
+
1275
+ /**
1276
+ * Build a compact signature for artifact catalog identity.
1277
+ * Used to avoid expensive no-op rerenders on duplicate updates.
1278
+ * @param {Array<object>} artifacts
1279
+ * @returns {string}
1280
+ */
1281
+ function getArtifactCatalogSignature(artifacts) {
1282
+ return artifacts
1283
+ .map((artifact) => [
1284
+ String(artifact.localPath || ''),
1285
+ String(artifact.modifiedAt || ''),
1286
+ String(artifact.size || ''),
1287
+ String(artifact.format || ''),
1288
+ String(artifact.type || ''),
1289
+ ].join('|'))
1290
+ .sort()
1291
+ .join('\n');
1292
+ }
1293
+
1294
+ /** Signature ignoring export files — chart HTML identity for preserveFleetIframe fast path. */
1295
+ function getArtifactCatalogSignatureExcludingExports(artifacts) {
1296
+ return getArtifactCatalogSignature(artifacts.filter((artifact) => artifact.type !== 'export'));
1297
+ }
1298
+
1299
+ /**
1300
+ * Add nonce to inline script tags so they can run in srcdoc.
1301
+ * @param {string} html
1302
+ * @param {string} nonce
1303
+ * @returns {string}
1304
+ */
1305
+ function injectNonceIntoInlineScripts(html, nonce) {
1306
+ if (!nonce) {
1307
+ return html;
1308
+ }
1309
+ return html.replace(/<script(?![^>]*\bnonce=)([^>]*)>/gi, `<script nonce="${nonce}"$1>`);
1310
+ }
1311
+
1312
+ /**
1313
+ * Inject a keyboard shortcut bridge for embedded iframe widgets so common
1314
+ * shortcuts (Ctrl/Cmd+A/C/X/V) work reliably in webview sandboxes.
1315
+ * @param {string} html
1316
+ * @param {string} nonce
1317
+ * @returns {string}
1318
+ */
1319
+ function injectIframeKeyboardShortcutBridge(html, nonce) {
1320
+ const bridge = `<script nonce="${nonce}">(function(){
1321
+ try {
1322
+ var pending = new Map();
1323
+ function requestClipboardText() {
1324
+ return new Promise(function(resolve){
1325
+ var requestId = 'clip-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8);
1326
+ var timer = setTimeout(function(){ pending.delete(requestId); resolve(''); }, 1500);
1327
+ pending.set(requestId, { resolve: resolve, timer: timer });
1328
+ window.parent.postMessage({ source: 'agentide-wow-widget', type: 'requestClipboardText', requestId: requestId }, '*');
1329
+ });
1330
+ }
1331
+ window.addEventListener('message', function(event){
1332
+ var payload = event.data;
1333
+ if (!payload || payload.source !== 'agentide-panel' || payload.type !== 'clipboardText') return;
1334
+ var entry = pending.get(payload.requestId);
1335
+ if (!entry) return;
1336
+ clearTimeout(entry.timer);
1337
+ pending.delete(payload.requestId);
1338
+ entry.resolve(typeof payload.text === 'string' ? payload.text : '');
1339
+ });
1340
+ function getEditable(target) {
1341
+ if (!target) return null;
1342
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) return target;
1343
+ if (target instanceof HTMLElement && target.isContentEditable) return target;
1344
+ if (target instanceof HTMLElement) {
1345
+ var parentEditable = target.closest('[contenteditable=""],[contenteditable="true"]');
1346
+ return parentEditable instanceof HTMLElement ? parentEditable : null;
1347
+ }
1348
+ return null;
1349
+ }
1350
+ function writeClipboardText(text) {
1351
+ var value = String(text || '');
1352
+ if (!value) return;
1353
+ try {
1354
+ if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
1355
+ void navigator.clipboard.writeText(value).catch(function(){
1356
+ window.parent.postMessage({ source: 'agentide-wow-widget', type: 'writeClipboardText', text: value }, '*');
1357
+ });
1358
+ return;
1359
+ }
1360
+ } catch (_err) {
1361
+ // fall back to host bridge
1362
+ }
1363
+ window.parent.postMessage({ source: 'agentide-wow-widget', type: 'writeClipboardText', text: value }, '*');
1364
+ }
1365
+ function selectAll(editable) {
1366
+ if (editable instanceof HTMLInputElement || editable instanceof HTMLTextAreaElement) {
1367
+ editable.focus();
1368
+ editable.selectionStart = 0;
1369
+ editable.selectionEnd = editable.value.length;
1370
+ return;
1371
+ }
1372
+ if (editable instanceof HTMLElement) {
1373
+ var range = document.createRange();
1374
+ range.selectNodeContents(editable);
1375
+ var selection = window.getSelection();
1376
+ if (!selection) return;
1377
+ selection.removeAllRanges();
1378
+ selection.addRange(range);
1379
+ }
1380
+ }
1381
+ function getSelectedText(editable) {
1382
+ if (editable instanceof HTMLInputElement || editable instanceof HTMLTextAreaElement) {
1383
+ var start = Number.isFinite(editable.selectionStart) ? editable.selectionStart : 0;
1384
+ var end = Number.isFinite(editable.selectionEnd) ? editable.selectionEnd : 0;
1385
+ if (end <= start) return '';
1386
+ return editable.value.slice(start, end);
1387
+ }
1388
+ var selection = window.getSelection();
1389
+ return selection ? String(selection.toString() || '') : '';
1390
+ }
1391
+ function replaceSelection(editable, text) {
1392
+ if (editable instanceof HTMLInputElement || editable instanceof HTMLTextAreaElement) {
1393
+ var start = Number.isFinite(editable.selectionStart) ? editable.selectionStart : editable.value.length;
1394
+ var end = Number.isFinite(editable.selectionEnd) ? editable.selectionEnd : editable.value.length;
1395
+ var before = editable.value.slice(0, start);
1396
+ var after = editable.value.slice(end);
1397
+ editable.value = before + text + after;
1398
+ var caret = start + text.length;
1399
+ editable.selectionStart = caret;
1400
+ editable.selectionEnd = caret;
1401
+ editable.dispatchEvent(new Event('input', { bubbles: true }));
1402
+ return;
1403
+ }
1404
+ if (!(editable instanceof HTMLElement)) return;
1405
+ var selection = window.getSelection();
1406
+ if (!selection || selection.rangeCount === 0) return;
1407
+ var range = selection.getRangeAt(0);
1408
+ range.deleteContents();
1409
+ var node = document.createTextNode(text);
1410
+ range.insertNode(node);
1411
+ range.setStartAfter(node);
1412
+ range.collapse(true);
1413
+ selection.removeAllRanges();
1414
+ selection.addRange(range);
1415
+ }
1416
+ document.addEventListener('keydown', function(event){
1417
+ if (event.defaultPrevented || event.altKey || !(event.metaKey || event.ctrlKey)) return;
1418
+ var key = String(event.key || '').toLowerCase();
1419
+ var editable = getEditable(document.activeElement) || getEditable(event.target);
1420
+ if (key === 'a') {
1421
+ if (!editable) return;
1422
+ event.preventDefault();
1423
+ selectAll(editable);
1424
+ return;
1425
+ }
1426
+ if (key === 'c') {
1427
+ var selected = getSelectedText(editable || null);
1428
+ if (!selected) return;
1429
+ event.preventDefault();
1430
+ writeClipboardText(selected);
1431
+ return;
1432
+ }
1433
+ if (key === 'x') {
1434
+ if (!editable) return;
1435
+ var selectedForCut = getSelectedText(editable);
1436
+ if (!selectedForCut) return;
1437
+ event.preventDefault();
1438
+ writeClipboardText(selectedForCut);
1439
+ replaceSelection(editable, '');
1440
+ return;
1441
+ }
1442
+ if (key === 'v') {
1443
+ if (!editable) return;
1444
+ event.preventDefault();
1445
+ void requestClipboardText().then(function(text){
1446
+ if (!text) return;
1447
+ replaceSelection(editable, text);
1448
+ });
1449
+ }
1450
+ }, true);
1451
+ document.addEventListener('copy', function(event){
1452
+ if (event.defaultPrevented) return;
1453
+ var editable = getEditable(document.activeElement) || getEditable(event.target);
1454
+ var selected = getSelectedText(editable || null);
1455
+ if (!selected) return;
1456
+ if (event.clipboardData) {
1457
+ event.clipboardData.setData('text/plain', selected);
1458
+ event.preventDefault();
1459
+ }
1460
+ writeClipboardText(selected);
1461
+ }, true);
1462
+ document.addEventListener('cut', function(event){
1463
+ if (event.defaultPrevented) return;
1464
+ var editable = getEditable(document.activeElement) || getEditable(event.target);
1465
+ if (!editable) return;
1466
+ var selected = getSelectedText(editable);
1467
+ if (!selected) return;
1468
+ if (event.clipboardData) {
1469
+ event.clipboardData.setData('text/plain', selected);
1470
+ event.preventDefault();
1471
+ }
1472
+ writeClipboardText(selected);
1473
+ replaceSelection(editable, '');
1474
+ }, true);
1475
+ document.addEventListener('paste', function(event){
1476
+ if (event.defaultPrevented) return;
1477
+ var editable = getEditable(document.activeElement) || getEditable(event.target);
1478
+ if (!editable) return;
1479
+ if (event.clipboardData && event.clipboardData.getData('text')) return;
1480
+ event.preventDefault();
1481
+ void requestClipboardText().then(function(text){
1482
+ if (!text) return;
1483
+ replaceSelection(editable, text);
1484
+ });
1485
+ }, true);
1486
+ } catch (err) {
1487
+ // no-op
1488
+ }
1489
+ })();</script>`;
1490
+ if (/<\/body>/i.test(html)) {
1491
+ return html.replace(/<\/body>/i, `${bridge}</body>`);
1492
+ }
1493
+ return `${html}${bridge}`;
1494
+ }
1495
+
1496
+ /**
1497
+ * Ask host to read HTML artifact contents by local path.
1498
+ * This avoids webview URL fetch/DNS issues for nested iframe previews.
1499
+ * @param {Array<object>} artifacts
1500
+ */
1501
+ function requestHtmlPreviewContents(artifacts) {
1502
+ for (const artifact of artifacts) {
1503
+ if (artifact.format !== 'html' || !artifact.localPath) {
1504
+ continue;
1505
+ }
1506
+ panelApi.postMessage({
1507
+ type: 'requestArtifactContent',
1508
+ path: artifact.localPath,
1509
+ format: 'html',
1510
+ });
1511
+ }
1512
+ }
1513
+
1514
+ /**
1515
+ * Ask host to read markdown artifact contents by local path.
1516
+ * @param {Array<object>} artifacts
1517
+ */
1518
+ function requestMarkdownPreviewContents(artifacts) {
1519
+ for (const artifact of artifacts) {
1520
+ if (artifact.format !== 'md' || !artifact.localPath) {
1521
+ continue;
1522
+ }
1523
+ panelApi.postMessage({
1524
+ type: 'requestArtifactContent',
1525
+ path: artifact.localPath,
1526
+ format: 'md',
1527
+ });
1528
+ }
1529
+ }
1530
+
1531
+ /**
1532
+ * Render HTML preview for one artifact path from host-provided content.
1533
+ * @param {string} path
1534
+ * @param {string | undefined} content
1535
+ * @param {string | undefined} error
1536
+ */
1537
+ function renderHtmlPreview(path, content, error) {
1538
+ const card = Array.from(document.querySelectorAll('.artifact-card')).find((cardEl) => {
1539
+ const element = /** @type {HTMLElement} */ (cardEl);
1540
+ return element.dataset.path === path;
1541
+ });
1542
+ const host = /** @type {HTMLElement | null} */ (card?.querySelector('.html-preview'));
1543
+ if (!host) {
1544
+ return;
1545
+ }
1546
+
1547
+ if (error || !content) {
1548
+ const reason = error || 'No preview content available.';
1549
+ host.innerHTML = `<div class="html-preview-fallback">Preview unavailable (${escapeHtml(reason)}). Use <strong>Open</strong> to inspect the artifact.</div>`;
1550
+ return;
1551
+ }
1552
+
1553
+ const scriptNonce = document.querySelector('script[nonce]')?.getAttribute('nonce') || '';
1554
+ const iframe = document.createElement('iframe');
1555
+ iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
1556
+ iframe.setAttribute('allow', 'clipboard-read; clipboard-write');
1557
+ iframe.setAttribute('loading', 'lazy');
1558
+ iframe.tabIndex = 0;
1559
+ const contentWithNonce = injectNonceIntoInlineScripts(content, scriptNonce);
1560
+ iframe.srcdoc = injectIframeKeyboardShortcutBridge(contentWithNonce, scriptNonce);
1561
+ iframe.addEventListener('load', () => {
1562
+ host.classList.add('ready');
1563
+ const parentCard = host.closest('.artifact-card');
1564
+ if (parentCard instanceof HTMLElement && parentCard.classList.contains('fleet-dashboard-full')) {
1565
+ autoFitFleetCardHeight(parentCard);
1566
+ }
1567
+ });
1568
+ host.innerHTML = '';
1569
+ host.appendChild(iframe);
1570
+ }
1571
+
1572
+ // -----------------------------------------------------------------------------
1573
+ // Quick Prompts & Ask AI
1574
+ // -----------------------------------------------------------------------------
1575
+
1576
+ function initQuickPrompts() {
1577
+ // Quick prompt buttons in empty state
1578
+ document.querySelectorAll('.quick-prompt').forEach(buttonEl => {
1579
+ const btn = /** @type {HTMLElement} */ (buttonEl);
1580
+ btn.addEventListener('click', () => {
1581
+ const prompt = btn.dataset.prompt;
1582
+ if (prompt) {
1583
+ panelApi.postMessage({ type: 'openChatWithPrompt', prompt });
1584
+ }
1585
+ });
1586
+ });
1587
+
1588
+ // Main "Ask AI" button
1589
+ document.getElementById('ask-ai-btn')?.addEventListener('click', () => {
1590
+ panelApi.postMessage({ type: 'openChat' });
1591
+ });
1592
+
1593
+ // Refresh button
1594
+ document.getElementById('refresh-charts-btn')?.addEventListener('click', () => {
1595
+ refreshCurrentView();
1596
+ });
1597
+
1598
+ // Remove legacy Expand/Shrink buttons if a user-customized panel.html still contains them.
1599
+ document.getElementById('expand-sidebar-btn')?.remove();
1600
+ document.getElementById('shrink-sidebar-btn')?.remove();
1601
+
1602
+ document.getElementById('summary-toggle-btn')?.addEventListener('click', (e) => {
1603
+ e.stopPropagation();
1604
+ toggleSummarySection();
1605
+ });
1606
+ applySummarySectionVisibility();
1607
+ }
1608
+
1609
+ // -----------------------------------------------------------------------------
1610
+ // Filters
1611
+ // -----------------------------------------------------------------------------
1612
+
1613
+ function applyFiltersToControls() {
1614
+ const agentSelect = /** @type {HTMLSelectElement | null} */ (document.getElementById('agent-filter-select'));
1615
+ if (agentSelect) {
1616
+ agentSelect.innerHTML = '<option value="">All agents</option>';
1617
+ for (const agentId of state.agentIds) {
1618
+ const option = document.createElement('option');
1619
+ option.value = agentId;
1620
+ option.textContent = agentId;
1621
+ agentSelect.appendChild(option);
1622
+ }
1623
+ agentSelect.value = state.agentId || '';
1624
+ }
1625
+ }
1626
+
1627
+ function postFilterUpdate() {
1628
+ panelApi.postMessage({
1629
+ type: 'updateFilters',
1630
+ agentId: state.agentId || '',
1631
+ });
1632
+ }
1633
+
1634
+ function initFilters() {
1635
+ const agentSelect = /** @type {HTMLSelectElement | null} */ (document.getElementById('agent-filter-select'));
1636
+ agentSelect?.addEventListener('change', () => {
1637
+ state.agentId = agentSelect.value || '';
1638
+ postFilterUpdate();
1639
+ });
1640
+ }
1641
+
1642
+ // -----------------------------------------------------------------------------
1643
+ // State Actions
1644
+ // -----------------------------------------------------------------------------
1645
+
1646
+ function initStateActions() {
1647
+ // Cancel generation
1648
+ document.getElementById('cancel-generate-btn')?.addEventListener('click', () => {
1649
+ panelApi.postMessage({ type: 'cancelGeneration' });
1650
+ });
1651
+
1652
+ // Retry generation
1653
+ document.getElementById('retry-generate-btn')?.addEventListener('click', () => {
1654
+ panelApi.postMessage({ type: 'retryGeneration' });
1655
+ });
1656
+ }
1657
+
1658
+ // -----------------------------------------------------------------------------
1659
+ // Toolbar Menu
1660
+ // -----------------------------------------------------------------------------
1661
+
1662
+ function initToolbarMenu() {
1663
+ const menuBtn = document.getElementById('toolbar-menu-btn');
1664
+ const dropdown = document.getElementById('toolbar-menu-dropdown');
1665
+
1666
+ if (!menuBtn || !dropdown) {
1667
+ return;
1668
+ }
1669
+
1670
+ /**
1671
+ * Toggle dropdown visibility.
1672
+ * @param {boolean} [forceOpen]
1673
+ */
1674
+ function toggleMenu(forceOpen) {
1675
+ const isOpen = forceOpen ?? !dropdown.classList.contains('open');
1676
+ dropdown.classList.toggle('open', isOpen);
1677
+ dropdown.setAttribute('aria-hidden', String(!isOpen));
1678
+ menuBtn.setAttribute('aria-expanded', String(isOpen));
1679
+ }
1680
+
1681
+ // Toggle on button click
1682
+ menuBtn.addEventListener('click', (e) => {
1683
+ e.stopPropagation();
1684
+ toggleMenu();
1685
+ });
1686
+
1687
+ // Close when clicking outside
1688
+ document.addEventListener('click', (e) => {
1689
+ if (!dropdown.contains(/** @type {Node} */(e.target)) && e.target !== menuBtn) {
1690
+ toggleMenu(false);
1691
+ }
1692
+ });
1693
+
1694
+ // Close on Escape
1695
+ document.addEventListener('keydown', (e) => {
1696
+ if (e.key === 'Escape' && dropdown.classList.contains('open')) {
1697
+ toggleMenu(false);
1698
+ menuBtn.focus();
1699
+ }
1700
+ });
1701
+
1702
+ // Handle menu item clicks
1703
+ dropdown.querySelectorAll('.dropdown-item[data-action]').forEach((itemEl) => {
1704
+ const item = /** @type {HTMLElement} */ (itemEl);
1705
+ item.addEventListener('click', () => {
1706
+ const action = item.dataset.action;
1707
+ toggleMenu(false);
1708
+ const activeView = getActiveView();
1709
+
1710
+ switch (action) {
1711
+ case 'new-view':
1712
+ promptCreateView();
1713
+ break;
1714
+
1715
+ case 'duplicate-view':
1716
+ if (activeView?.id) {
1717
+ panelApi.postMessage({ type: 'duplicateView', viewId: activeView.id });
1718
+ }
1719
+ break;
1720
+
1721
+ case 'rename-view': {
1722
+ if (!activeView || activeView.system) {
1723
+ break;
1724
+ }
1725
+ panelApi.postMessage({ type: 'requestRenameView', viewId: activeView.id });
1726
+ break;
1727
+ }
1728
+
1729
+ case 'delete-view': {
1730
+ if (!activeView || activeView.system) {
1731
+ break;
1732
+ }
1733
+ panelApi.postMessage({ type: 'requestDeleteView', viewId: activeView.id });
1734
+ break;
1735
+ }
1736
+
1737
+ case 'edit-view-scripts':
1738
+ panelApi.postMessage({ type: 'editViewScripts', viewId: activeView?.id });
1739
+ break;
1740
+
1741
+ case 'share-scripts-only':
1742
+ onShareScriptsOnlyClick();
1743
+ break;
1744
+
1745
+ case 'refresh-all-views':
1746
+ panelApi.postMessage({ type: 'refreshAllViews' });
1747
+ break;
1748
+
1749
+ case 'reset-home':
1750
+ if (confirm('Reset all charts and scripts in your home analytics folder? This will delete generated charts and user scripts, then regenerate defaults.')) {
1751
+ panelApi.postMessage({ type: 'resetChartsHome' });
1752
+ }
1753
+ break;
1754
+
1755
+ case 'reset-workspace':
1756
+ if (confirm('Reset all charts and scripts in this workspace? This will delete generated charts and user scripts for the current workspace, then regenerate defaults.')) {
1757
+ panelApi.postMessage({ type: 'resetChartsWorkspace' });
1758
+ }
1759
+ break;
1760
+
1761
+ case 'reenable-charts':
1762
+ panelApi.postMessage({ type: 'reenableAllCharts' });
1763
+ break;
1764
+ }
1765
+ });
1766
+ });
1767
+ }
1768
+
1769
+ /**
1770
+ * Push a view export snapshot into embedded dashboard iframes (no iframe reload).
1771
+ * Only when the active view has preserveFleetIframe (host already gates pushes).
1772
+ * @param {{ payload?: object, meta?: object }} message
1773
+ */
1774
+ function postViewExportSnapshotToDashboardIframes(message) {
1775
+ const payload = message.payload;
1776
+ const meta = message.meta;
1777
+ document.querySelectorAll('.html-preview iframe').forEach((frameEl) => {
1778
+ const iframe = /** @type {HTMLIFrameElement} */ (frameEl);
1779
+ try {
1780
+ iframe.contentWindow?.postMessage({ type: VIEW_EXPORT_SNAPSHOT, payload, meta }, '*');
1781
+ // Legacy widgets (e.g. live-fleet-2.html) still listen for liveFleetSnapshot.
1782
+ iframe.contentWindow?.postMessage({ type: LEGACY_VIEW_EXPORT_SNAPSHOT, payload, meta }, '*');
1783
+ } catch {
1784
+ // Cross-origin or unloaded iframe — ignore.
1785
+ }
1786
+ });
1787
+ }
1788
+
1789
+ /**
1790
+ * Bridge postMessage events from embedded HTML widgets.
1791
+ * Allows widgets to trigger host actions (e.g. copy session id).
1792
+ */
1793
+ function initEmbeddedWidgetBridge() {
1794
+ window.addEventListener('message', (event) => {
1795
+ const data = event.data;
1796
+ if (!data || typeof data !== 'object') {
1797
+ return;
1798
+ }
1799
+ if (data.source !== 'agentide-wow-widget') {
1800
+ return;
1801
+ }
1802
+ if (data.type === 'copySessionId' && typeof data.sessionId === 'string') {
1803
+ const sessionId = data.sessionId.trim();
1804
+ if (sessionId) {
1805
+ panelApi.postMessage({ type: 'copySessionId', sessionId });
1806
+ }
1807
+ return;
1808
+ }
1809
+ if (data.type === 'requestClipboardText' && typeof data.requestId === 'string') {
1810
+ const requestId = data.requestId.trim();
1811
+ if (requestId) {
1812
+ panelApi.postMessage({ type: 'requestClipboardText', requestId });
1813
+ }
1814
+ return;
1815
+ }
1816
+ if (data.type === 'writeClipboardText' && typeof data.text === 'string') {
1817
+ panelApi.postMessage({ type: 'writeClipboardText', text: data.text });
1818
+ return;
1819
+ }
1820
+ if (data.type === 'openArtifact' && typeof data.path === 'string') {
1821
+ const artifactPath = data.path.trim();
1822
+ if (artifactPath) {
1823
+ panelApi.postMessage({ type: 'openArtifact', path: artifactPath });
1824
+ }
1825
+ return;
1826
+ }
1827
+ if (data.type === 'openChatWithPrompt' && typeof data.prompt === 'string') {
1828
+ const prompt = data.prompt.trim();
1829
+ if (prompt) {
1830
+ panelApi.postMessage({
1831
+ type: 'openChatWithPrompt',
1832
+ prompt: prompt.slice(0, 12_000),
1833
+ });
1834
+ }
1835
+ return;
1836
+ }
1837
+ if (data.type === 'disconnectFleetChannel' && typeof data.sessionId === 'string') {
1838
+ const sessionId = data.sessionId.trim();
1839
+ if (sessionId) {
1840
+ panelApi.postMessage({
1841
+ type: 'disconnectFleetChannel',
1842
+ sessionId,
1843
+ channelType: typeof data.channelType === 'string' ? data.channelType : '',
1844
+ channelTarget: typeof data.channelTarget === 'string' ? data.channelTarget : '',
1845
+ });
1846
+ }
1847
+ return;
1848
+ }
1849
+ if (data.type === 'updateFleetCardTitle' && typeof data.sessionId === 'string' && typeof data.cardTitle === 'string') {
1850
+ const sessionId = data.sessionId.trim();
1851
+ const sourceId = typeof data.sourceId === 'string' ? data.sourceId.trim() : 'local';
1852
+ if (sessionId) {
1853
+ panelApi.postMessage({
1854
+ type: 'updateFleetCardTitle',
1855
+ sourceId,
1856
+ sessionId,
1857
+ cardTitle: data.cardTitle,
1858
+ });
1859
+ }
1860
+ return;
1861
+ }
1862
+ if (data.type === 'openSpotlightForSession' && typeof data.sessionId === 'string') {
1863
+ const sessionId = data.sessionId.trim();
1864
+ if (sessionId) {
1865
+ panelApi.postMessage({
1866
+ type: 'openSpotlightForSession',
1867
+ sessionId,
1868
+ channelType: typeof data.channelType === 'string' ? data.channelType : '',
1869
+ channelTarget: typeof data.channelTarget === 'string' ? data.channelTarget : '',
1870
+ model: typeof data.model === 'string' ? data.model : '',
1871
+ engine: typeof data.engine === 'string' ? data.engine : '',
1872
+ agentId: typeof data.agentId === 'string' ? data.agentId : '',
1873
+ });
1874
+ }
1875
+ return;
1876
+ }
1877
+ if (data.type === 'refreshView') {
1878
+ panelApi.postMessage({ type: 'refreshView' });
1879
+ return;
1880
+ }
1881
+ if (data.type === 'refreshFleetWidget') {
1882
+ panelApi.postMessage({ type: 'refreshFleetWidget' });
1883
+ return;
1884
+ }
1885
+ if (data.type === 'refreshWidget') {
1886
+ const script = typeof data.script === 'string' ? data.script.trim() : '';
1887
+ const scripts = Array.isArray(data.scripts)
1888
+ ? data.scripts.filter((s) => typeof s === 'string').map((s) => s.trim()).filter(Boolean)
1889
+ : [];
1890
+ if (!script && scripts.length === 0) {
1891
+ return;
1892
+ }
1893
+ panelApi.postMessage({
1894
+ type: 'refreshWidget',
1895
+ script: script || undefined,
1896
+ scripts: scripts.length > 0 ? scripts : undefined,
1897
+ });
1898
+ return;
1899
+ }
1900
+ if (data.type === REQUEST_VIEW_EXPORT_SNAPSHOT || data.type === LEGACY_REQUEST_LIVE_FLEET_SNAPSHOT) {
1901
+ panelApi.postMessage({ type: REQUEST_VIEW_EXPORT_SNAPSHOT });
1902
+ return;
1903
+ }
1904
+ if (data.type === 'openChat') {
1905
+ panelApi.postMessage({ type: 'openChat' });
1906
+ }
1907
+ });
1908
+
1909
+ // Fallback: forward openSpotlightForSession when message is from a frame that didn't set
1910
+ // source (e.g. fleet iframe in some contexts) so card click always opens chat
1911
+ window.addEventListener('message', (event) => {
1912
+ const data = event.data;
1913
+ if (!data || typeof data !== 'object' || data.type !== 'openSpotlightForSession') {
1914
+ return;
1915
+ }
1916
+ if (data.source === 'agentide-wow-widget') {
1917
+ return; // already handled above
1918
+ }
1919
+ if (typeof data.sessionId !== 'string') {
1920
+ return;
1921
+ }
1922
+ const sessionId = data.sessionId.trim();
1923
+ if (!sessionId) {
1924
+ return;
1925
+ }
1926
+ panelApi.postMessage({
1927
+ type: 'openSpotlightForSession',
1928
+ sessionId,
1929
+ channelType: typeof data.channelType === 'string' ? data.channelType : '',
1930
+ channelTarget: typeof data.channelTarget === 'string' ? data.channelTarget : '',
1931
+ model: typeof data.model === 'string' ? data.model : '',
1932
+ engine: typeof data.engine === 'string' ? data.engine : '',
1933
+ agentId: typeof data.agentId === 'string' ? data.agentId : '',
1934
+ });
1935
+ });
1936
+ }
1937
+
1938
+ /**
1939
+ * Show generating state
1940
+ */
1941
+ function showGeneratingState() {
1942
+ showGalleryState('generating');
1943
+ }
1944
+
1945
+ /**
1946
+ * Show failed state with error message
1947
+ * @param {string} errorMessage
1948
+ */
1949
+ function showFailedState(errorMessage) {
1950
+ const errorEl = document.getElementById('error-message');
1951
+ if (errorEl) {
1952
+ errorEl.textContent = errorMessage;
1953
+ }
1954
+ showGalleryState('failed');
1955
+ }
1956
+
1957
+ /**
1958
+ * Show refresh hint
1959
+ */
1960
+ function showRefreshHint() {
1961
+ const hint = document.getElementById('refresh-hint');
1962
+ if (hint) {
1963
+ hint.style.display = 'flex';
1964
+ }
1965
+ }
1966
+
1967
+ /**
1968
+ * Hide refresh hint
1969
+ */
1970
+ function hideRefreshHint() {
1971
+ const hint = document.getElementById('refresh-hint');
1972
+ if (hint) {
1973
+ hint.style.display = 'none';
1974
+ }
1975
+ }
1976
+
1977
+ /**
1978
+ * Show/hide per-chart refresh indicator.
1979
+ * @param {boolean} isRefreshing
1980
+ */
1981
+ function setPerChartRefreshing(isRefreshing) {
1982
+ document.querySelectorAll('.artifact-card[data-type="chart"]').forEach(card => {
1983
+ card.classList.toggle('chart-refreshing', isRefreshing);
1984
+ let indicator = card.querySelector('.artifact-refresh-indicator');
1985
+
1986
+ if (isRefreshing) {
1987
+ if (!indicator) {
1988
+ indicator = document.createElement('div');
1989
+ indicator.className = 'artifact-refresh-indicator';
1990
+ indicator.textContent = 'Refreshing';
1991
+ card.appendChild(indicator);
1992
+ }
1993
+ } else if (indicator) {
1994
+ indicator.remove();
1995
+ }
1996
+ });
1997
+ }
1998
+
1999
+ /**
2000
+ * Toggle refresh button busy state.
2001
+ * @param {boolean} isRefreshing
2002
+ */
2003
+ function setRefreshButtonState(isRefreshing) {
2004
+ const btn = document.getElementById('refresh-charts-btn');
2005
+ if (!btn) {
2006
+ return;
2007
+ }
2008
+ btn.classList.toggle('refreshing', isRefreshing);
2009
+ }
2010
+
2011
+ // -----------------------------------------------------------------------------
2012
+ // Message Handling
2013
+ // -----------------------------------------------------------------------------
2014
+
2015
+ panelApi.onMessage(msg => {
2016
+ switch (msg.type) {
2017
+ case 'artifactsUpdated': {
2018
+ const nextSignature = getArtifactCatalogSignature(msg.artifacts);
2019
+ const nextActiveViewId = typeof msg.activeViewId === 'string' ? msg.activeViewId : state.activeViewId;
2020
+ const nextFleetRefreshVersion = typeof msg.fleetRefreshVersion === 'number'
2021
+ ? Math.max(0, Math.floor(msg.fleetRefreshVersion))
2022
+ : state.fleetRefreshVersion;
2023
+ const nextGalleryIgnore = Array.isArray(msg.galleryIgnorePatterns)
2024
+ ? msg.galleryIgnorePatterns.filter((p) => typeof p === 'string').map((p) => String(p).trim()).filter(Boolean)
2025
+ : [];
2026
+ const isFleetView = nextActiveViewId === 'fleet';
2027
+ const exportOnlyCatalogChange =
2028
+ activeViewPreservesFleetIframe()
2029
+ && nextSignature !== state.lastArtifactCatalogSignature
2030
+ && getArtifactCatalogSignatureExcludingExports(msg.artifacts)
2031
+ === getArtifactCatalogSignatureExcludingExports(state.artifacts);
2032
+ // preserveFleetIframe views (default for fleet-dashboard layout) keep the iframe mounted
2033
+ // and rely on viewExportSnapshot postMessages for updates — the widget's DOM-diff
2034
+ // renderer applies them in-place. Non-preserve flows still reload when version bumps.
2035
+ const hasFleetVersionUpdate =
2036
+ isFleetView
2037
+ && nextFleetRefreshVersion > state.fleetRefreshVersion
2038
+ && !activeViewPreservesFleetIframe();
2039
+ // Skip only when signature unchanged and not refreshing, unless Fleet explicitly signaled fresher data.
2040
+ if (nextSignature === state.lastArtifactCatalogSignature && !state.isRefreshing && !hasFleetVersionUpdate) {
2041
+ break;
2042
+ }
2043
+ state.lastArtifactCatalogSignature = nextSignature;
2044
+ state.artifacts = msg.artifacts;
2045
+ state.galleryIgnorePatterns = nextGalleryIgnore;
2046
+ // View selection is owned by viewsLoaded; do not let artifact batches revert the dropdown.
2047
+ if (
2048
+ nextActiveViewId === state.activeViewId
2049
+ || !state.views.some((view) => view.id === state.activeViewId)
2050
+ ) {
2051
+ state.activeViewId = nextActiveViewId;
2052
+ }
2053
+ state.fleetRefreshVersion = nextFleetRefreshVersion;
2054
+ applyFleetAutoRefresh();
2055
+ if (exportOnlyCatalogChange) {
2056
+ // Export JSON changed; host already pushes viewExportSnapshot — avoid gallery/iframe rebuild.
2057
+ break;
2058
+ }
2059
+ // Fleet JSON can change without agent-fleet.html mtime changing if the host
2060
+ // did not re-run the widget script; bumping fleetRefreshVersion means data is
2061
+ // fresher — force a preview reload so the iframe is not reused with stale HTML.
2062
+ renderGallery(state.artifacts, { forceFreshHtml: hasFleetVersionUpdate });
2063
+ if (state.isRefreshing) {
2064
+ setPerChartRefreshing(true);
2065
+ }
2066
+ break;
2067
+ }
2068
+
2069
+ case 'artifactDeleted': {
2070
+ // Remove from gallery with animation
2071
+ const card = document.querySelector(`[data-path="${msg.path}"]`);
2072
+ if (card) {
2073
+ card.classList.add('deleting');
2074
+ setTimeout(() => card.remove(), 300);
2075
+
2076
+ // Update state
2077
+ state.artifacts = state.artifacts.filter(a => a.localPath !== msg.path);
2078
+
2079
+ // Check if gallery is now empty
2080
+ if (state.artifacts.length === 0) {
2081
+ showGalleryState('empty');
2082
+ }
2083
+ }
2084
+ break;
2085
+ }
2086
+
2087
+ case 'summaryCards':
2088
+ state.summaryCards = msg.cards;
2089
+ renderSummaryCards(msg.cards);
2090
+ break;
2091
+
2092
+ case 'manifestUpdated':
2093
+ state.manifest = msg.manifest;
2094
+ // Re-render gallery with new manifest data
2095
+ if (state.artifacts.length > 0) {
2096
+ renderGallery(state.artifacts);
2097
+ }
2098
+ break;
2099
+
2100
+ case VIEW_EXPORT_SNAPSHOT:
2101
+ case LEGACY_VIEW_EXPORT_SNAPSHOT:
2102
+ postViewExportSnapshotToDashboardIframes(msg);
2103
+ break;
2104
+
2105
+ case 'artifactContent':
2106
+ if (msg.path && msg.format === 'html') {
2107
+ renderHtmlPreview(msg.path, msg.content, msg.error);
2108
+ }
2109
+ if (msg.path && msg.format === 'md') {
2110
+ const card = Array.from(document.querySelectorAll('.artifact-card')).find((cardEl) => {
2111
+ const element = /** @type {HTMLElement} */ (cardEl);
2112
+ return element.dataset.path === msg.path;
2113
+ });
2114
+ const host = /** @type {HTMLElement | null} */ (card?.querySelector('.markdown-preview'));
2115
+ if (host) {
2116
+ if (msg.error || !msg.content) {
2117
+ host.innerHTML = '<em>Failed to load preview</em>';
2118
+ } else {
2119
+ host.innerHTML = renderMarkdown(msg.content);
2120
+ }
2121
+ }
2122
+ }
2123
+ break;
2124
+
2125
+ case 'clipboardText':
2126
+ document.querySelectorAll('.html-preview iframe').forEach((frame) => {
2127
+ const iframe = /** @type {HTMLIFrameElement} */ (frame);
2128
+ iframe.contentWindow?.postMessage({
2129
+ source: 'agentide-panel',
2130
+ type: 'clipboardText',
2131
+ requestId: msg.requestId,
2132
+ text: typeof msg.text === 'string' ? msg.text : '',
2133
+ }, '*');
2134
+ });
2135
+ break;
2136
+
2137
+ case 'generatingStarted':
2138
+ setRefreshButtonState(true);
2139
+ state.isRefreshing = false;
2140
+ setPerChartRefreshing(false);
2141
+ showGeneratingState();
2142
+ break;
2143
+
2144
+ case 'generatingCancelled':
2145
+ setRefreshButtonState(false);
2146
+ state.isRefreshing = false;
2147
+ setPerChartRefreshing(false);
2148
+ // Go back to empty or content state
2149
+ if (state.artifacts.length > 0) {
2150
+ renderGallery(state.artifacts);
2151
+ } else {
2152
+ showGalleryState('empty');
2153
+ }
2154
+ break;
2155
+
2156
+ case 'generatingFailed':
2157
+ setRefreshButtonState(false);
2158
+ state.isRefreshing = false;
2159
+ setPerChartRefreshing(false);
2160
+ showFailedState(msg.error);
2161
+ break;
2162
+
2163
+ case 'refreshStarted':
2164
+ if (!msg.viewId || msg.viewId === state.activeViewId) {
2165
+ setRefreshButtonState(true);
2166
+ state.isRefreshing = true;
2167
+ setPerChartRefreshing(true);
2168
+ }
2169
+ break;
2170
+
2171
+ case 'refreshFinished':
2172
+ if (!msg.viewId || msg.viewId === state.activeViewId) {
2173
+ setRefreshButtonState(false);
2174
+ state.isRefreshing = false;
2175
+ setPerChartRefreshing(false);
2176
+ if (activeViewPreservesFleetIframe()) {
2177
+ document.querySelectorAll('.html-preview iframe').forEach((frameEl) => {
2178
+ const iframe = /** @type {HTMLIFrameElement} */ (frameEl);
2179
+ try {
2180
+ iframe.contentWindow?.postMessage({ type: 'fleetRefreshComplete' }, '*');
2181
+ } catch {
2182
+ // Cross-origin or unloaded iframe — ignore.
2183
+ }
2184
+ });
2185
+ }
2186
+ }
2187
+ break;
2188
+
2189
+ case 'viewsLoaded':
2190
+ if (msg.surfaceMode === 'team' || msg.surfaceMode === 'spotlight') {
2191
+ applySurfaceMode(msg.surfaceMode);
2192
+ }
2193
+ state.views = Array.isArray(msg.views) ? msg.views : [];
2194
+ state.activeViewId = typeof msg.activeViewId === 'string' ? msg.activeViewId : state.activeViewId;
2195
+ applyViewOptions();
2196
+ applyFleetAutoRefresh();
2197
+ break;
2198
+
2199
+ case 'refreshNoData':
2200
+ if (!msg.viewId || msg.viewId === state.activeViewId) {
2201
+ setRefreshButtonState(false);
2202
+ state.isRefreshing = false;
2203
+ setPerChartRefreshing(false);
2204
+ }
2205
+ break;
2206
+
2207
+ case 'refreshFailed':
2208
+ if (!msg.viewId || msg.viewId === state.activeViewId) {
2209
+ setRefreshButtonState(false);
2210
+ state.isRefreshing = false;
2211
+ setPerChartRefreshing(false);
2212
+ if (state.artifacts.length === 0) {
2213
+ showFailedState(msg.error);
2214
+ }
2215
+ }
2216
+ break;
2217
+
2218
+ case 'filtersUpdated':
2219
+ state.scope = msg.scope === 'workspace' ? 'workspace' : 'global';
2220
+ state.projectId = msg.projectId || '';
2221
+ state.agentId = msg.agentId || '';
2222
+ state.agentIds = Array.isArray(msg.agentIds) ? msg.agentIds : [];
2223
+ // Note: Don't override activeViewId from filtersUpdated. This prevents race conditions
2224
+ // where filtersUpdated (sent last) overwrites the correct activeViewId set by viewsLoaded.
2225
+ // We still call applyViewOptions() to keep the dropdown UI in sync with the views list.
2226
+ applyViewOptions();
2227
+ applyFleetAutoRefresh();
2228
+ applyFiltersToControls();
2229
+ break;
2230
+
2231
+ case 'panelStateLoaded':
2232
+ if (msg.panelId && msg.panelId !== PANEL_ID) {
2233
+ break;
2234
+ }
2235
+ if (msg.surfaceMode === 'team' || msg.surfaceMode === 'spotlight') {
2236
+ applySurfaceMode(msg.surfaceMode);
2237
+ }
2238
+ state.panelState = normalizePanelState(msg.state);
2239
+ state.panelStateLoaded = true;
2240
+ applySummarySectionVisibility();
2241
+ if (typeof msg.fleetAutoRefreshIntervalSeconds === 'number') {
2242
+ state.fleetAutoRefreshIntervalSeconds = Math.max(0, msg.fleetAutoRefreshIntervalSeconds);
2243
+ }
2244
+ applyFleetAutoRefresh();
2245
+ if (ensurePanelStateForArtifacts(state.artifacts)) {
2246
+ savePanelState();
2247
+ }
2248
+ if (state.artifacts.length > 0) {
2249
+ renderGallery(state.artifacts);
2250
+ }
2251
+ break;
2252
+
2253
+ case 'disabledChartsUpdated':
2254
+ state.disabledCharts = new Set(
2255
+ (Array.isArray(msg.charts) ? msg.charts : [])
2256
+ .map((name) => String(name || '').trim().toLowerCase())
2257
+ .filter(Boolean),
2258
+ );
2259
+ renderGallery(state.artifacts);
2260
+ break;
2261
+
2262
+ case 'dataStale':
2263
+ showRefreshHint();
2264
+ break;
2265
+
2266
+ case 'dataFresh':
2267
+ hideRefreshHint();
2268
+ break;
2269
+ }
2270
+ });
2271
+
2272
+ // -----------------------------------------------------------------------------
2273
+ // Initialization
2274
+ // -----------------------------------------------------------------------------
2275
+
2276
+ document.addEventListener('DOMContentLoaded', () => {
2277
+ if (document.body.classList.contains('spotlight-surface-team')) {
2278
+ applySurfaceMode('team');
2279
+ }
2280
+ ensureShareToolbarButton();
2281
+ initQuickPrompts();
2282
+ initStateActions();
2283
+ initEmbeddedWidgetBridge();
2284
+ initFilters();
2285
+ initViews();
2286
+ initToolbarMenu();
2287
+ renderSummaryPlaceholders();
2288
+
2289
+ // Request initial data
2290
+ panelApi.postMessage({ type: 'requestViews' });
2291
+ panelApi.postMessage({ type: 'requestPanelState', panelId: PANEL_ID });
2292
+ panelApi.postMessage({ type: 'requestArtifacts' });
2293
+ panelApi.postMessage({ type: 'requestSummary' });
2294
+ panelApi.postMessage({ type: 'requestFilters' });
2295
+ });