openclaw-multi-auto 1.3.6 → 1.3.8

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 (328) hide show
  1. package/dist/{audio-preflight-5FEeDooz.js → audio-preflight-DDBLZBdb.js} +4 -4
  2. package/dist/{audio-transcription-runner-B-UvoDjZ.js → audio-transcription-runner-DZbSWT9E.js} +1 -1
  3. package/dist/build-info.json +3 -3
  4. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  5. package/dist/{chrome-D45SyhQL.js → chrome-CMU2WVFh.js} +8 -8
  6. package/dist/{deliver-B9cys0EZ.js → deliver-BXVcFIHL.js} +1 -1
  7. package/dist/{deliver-runtime-DhaQJ0pI.js → deliver-runtime-DTaIS-1i.js} +3 -3
  8. package/dist/{deps-send-whatsapp.runtime-DvTL2tzN.js → deps-send-whatsapp.runtime-CIZqFAqb.js} +7 -7
  9. package/dist/extensionAPI.js +6 -6
  10. package/dist/{image-DAOPwVXi.js → image-BCVLo0qw.js} +1 -1
  11. package/dist/{image-runtime-wlCLVvVv.js → image-runtime-DtCKpMPZ.js} +3 -3
  12. package/dist/{pi-embedded-DYU79yGe.js → pi-embedded-CgQ_W6Xs.js} +24 -24
  13. package/dist/{pi-embedded-helpers-uTRAmQ4n.js → pi-embedded-helpers-CwuBTKza.js} +3 -3
  14. package/dist/plugin-sdk/{accounts-DyFCXtHv.js → accounts-BslAlVYS.js} +2 -2
  15. package/dist/plugin-sdk/{accounts-BJAXxY46.js → accounts-C3m65--E.js} +2 -2
  16. package/dist/plugin-sdk/{accounts-C1j7HSL0.js → accounts-CNCCkdEF.js} +3 -3
  17. package/dist/plugin-sdk/{active-listener-CftX5jLD.js → active-listener-CkPnMUkB.js} +2 -2
  18. package/dist/plugin-sdk/{api-key-rotation-8nyyt1kx.js → api-key-rotation-BXnNsojA.js} +2 -2
  19. package/dist/plugin-sdk/{audio-preflight-C_aSAPR1.js → audio-preflight-CtO4fFvp.js} +26 -26
  20. package/dist/plugin-sdk/{audio-transcription-runner-CB53F7_7.js → audio-transcription-runner-DnxvOS1-.js} +11 -11
  21. package/dist/plugin-sdk/{audit-membership-runtime-BXndI4LG.js → audit-membership-runtime-BpfoSk8M.js} +2 -2
  22. package/dist/plugin-sdk/{channel-activity-C5y8AgAV.js → channel-activity-WJYxcJ3S.js} +3 -3
  23. package/dist/plugin-sdk/{channel-web-DBTRO03V.js → channel-web-dO5k3ubM.js} +18 -18
  24. package/dist/plugin-sdk/{chrome-f00sZkDX.js → chrome-CjNTuJML.js} +6 -6
  25. package/dist/plugin-sdk/{commands-registry-BJ_NxG2F.js → commands-registry-CdYjoI0i.js} +4 -4
  26. package/dist/plugin-sdk/{common-Cf27Jwxu.js → common-oYc5vPFl.js} +2 -2
  27. package/dist/plugin-sdk/{config-CHQrpx-Q.js → config-B1z-UxQ3.js} +7 -7
  28. package/dist/plugin-sdk/{deliver-DNEuetST.js → deliver-D5_6T567.js} +10 -10
  29. package/dist/plugin-sdk/deliver-runtime-C5dgvvga.js +32 -0
  30. package/dist/plugin-sdk/deps-send-discord.runtime-Dg4N7PHJ.js +23 -0
  31. package/dist/plugin-sdk/deps-send-imessage.runtime-0OEwzMQm.js +22 -0
  32. package/dist/plugin-sdk/deps-send-signal.runtime-BM1jRt3G.js +21 -0
  33. package/dist/plugin-sdk/deps-send-slack.runtime-1E3BYRdF.js +19 -0
  34. package/dist/plugin-sdk/deps-send-telegram.runtime-DNCxIflA.js +24 -0
  35. package/dist/plugin-sdk/deps-send-whatsapp.runtime-OLwr-9c8.js +57 -0
  36. package/dist/plugin-sdk/{diagnostic-LYUUmjJ5.js → diagnostic-Bxxu0ig-.js} +2 -2
  37. package/dist/plugin-sdk/{errors-CtMWwS2Z.js → errors-B3cHyZZA.js} +1 -1
  38. package/dist/plugin-sdk/{fetch-guard-CxYB5Kg6.js → fetch-guard-Dcgod0tg.js} +2 -2
  39. package/dist/plugin-sdk/{fs-safe-DtfhxbrI.js → fs-safe-BaKqI3G4.js} +3 -3
  40. package/dist/plugin-sdk/{image-BwjYjRHx.js → image-B2mQW9Rb.js} +6 -6
  41. package/dist/plugin-sdk/{image-ops-BnZKcbd6.js → image-ops-Cbzr4U9l.js} +2 -2
  42. package/dist/plugin-sdk/image-runtime-BFm45j49.js +25 -0
  43. package/dist/plugin-sdk/{ir-Z4hX67TJ.js → ir-ZEmrTr4J.js} +7 -7
  44. package/dist/plugin-sdk/{local-roots-KhjQw04O.js → local-roots-CIPRxA-4.js} +4 -4
  45. package/dist/plugin-sdk/{logger-DHIIvMxj.js → logger-CvPFVOgT.js} +2 -2
  46. package/dist/plugin-sdk/{login-C31642Ld.js → login-CCTew9bt.js} +4 -4
  47. package/dist/plugin-sdk/{login-qr--y2SG_Ue.js → login-qr-BI3Vi_wJ.js} +5 -5
  48. package/dist/plugin-sdk/{manager-2UZBMCc7.js → manager-BEoYPn7R.js} +8 -8
  49. package/dist/plugin-sdk/manager-runtime-DxclHQ4U.js +15 -0
  50. package/dist/plugin-sdk/{outbound-Ba0QUI5h.js → outbound-ByOw1K6W.js} +5 -5
  51. package/dist/plugin-sdk/{outbound-attachment-B1Laso-8.js → outbound-attachment-BzVhxRRw.js} +2 -2
  52. package/dist/plugin-sdk/{path-alias-guards-C7Vm5DZ1.js → path-alias-guards-sWayacde.js} +1 -1
  53. package/dist/plugin-sdk/{paths-DopV9PQG.js → paths-Dpg3qxcl.js} +1 -1
  54. package/dist/plugin-sdk/{pi-embedded-helpers-DnA_OCzP.js → pi-embedded-helpers-DIxXkGJf.js} +16 -16
  55. package/dist/plugin-sdk/{pi-model-discovery-DdPqXk8f.js → pi-model-discovery-DM_2uFtj.js} +1 -1
  56. package/dist/plugin-sdk/pi-model-discovery-runtime-BuzvkvNR.js +8 -0
  57. package/dist/plugin-sdk/{pi-tools.before-tool-call.runtime-DxFHiLUE.js → pi-tools.before-tool-call.runtime-w1dqL_ty.js} +4 -4
  58. package/dist/plugin-sdk/{plugins-CbCt4osF.js → plugins-C4USiH29.js} +4 -4
  59. package/dist/plugin-sdk/{proxy-env-C63mMdas.js → proxy-env-ET-rp8eg.js} +1 -1
  60. package/dist/plugin-sdk/{proxy-fetch-Ch95c_Y2.js → proxy-fetch-uDXGKG3Z.js} +1 -1
  61. package/dist/plugin-sdk/{pw-ai-DpJk62D4.js → pw-ai-CyOt3RDA.js} +9 -9
  62. package/dist/plugin-sdk/{qmd-manager-Ca-iSfEE.js → qmd-manager-BySdoVR7.js} +7 -7
  63. package/dist/plugin-sdk/{query-expansion-B_Xe41Ab.js → query-expansion-C6uS-7lj.js} +4 -4
  64. package/dist/plugin-sdk/{redact-hp9TOulW.js → redact-Bvxt1T_Q.js} +1 -1
  65. package/dist/plugin-sdk/{reply-CovBlFea.js → reply-CTCSeQqW.js} +73 -73
  66. package/dist/plugin-sdk/{resolve-outbound-target-BbrHgyUk.js → resolve-outbound-target-Bw8YNANu.js} +2 -2
  67. package/dist/plugin-sdk/{run-with-concurrency-BR1DXa8T.js → run-with-concurrency-C_KCHwvf.js} +1 -1
  68. package/dist/plugin-sdk/runtime-whatsapp-login.runtime-BxgRDkhc.js +10 -0
  69. package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-elOqrkfg.js +19 -0
  70. package/dist/plugin-sdk/{send-BvAtLLPl.js → send-BZ6nYFZr.js} +5 -5
  71. package/dist/plugin-sdk/{send-BTztm3D2.js → send-C0w6xP2x.js} +6 -6
  72. package/dist/plugin-sdk/{send-CWJUuG0i.js → send-CFf-1V89.js} +8 -8
  73. package/dist/plugin-sdk/{send-EcglC4cG.js → send-CY-Qfwia.js} +7 -7
  74. package/dist/plugin-sdk/{send-BXpXBwM_.js → send-qPyNGSe4.js} +13 -13
  75. package/dist/plugin-sdk/{session-k256LJZT.js → session-COrvpvUQ.js} +3 -3
  76. package/dist/plugin-sdk/signal.js +2 -2
  77. package/dist/plugin-sdk/{skill-commands-DoRqLzxm.js → skill-commands-DZqhtmiv.js} +4 -4
  78. package/dist/plugin-sdk/{skills-QudILG6e.js → skills-Cw_vXEJb.js} +6 -6
  79. package/dist/plugin-sdk/slash-commands.runtime-D67JLweo.js +13 -0
  80. package/dist/plugin-sdk/slash-dispatch.runtime-DvcpvCJ0.js +52 -0
  81. package/dist/plugin-sdk/slash-skill-commands.runtime-BM1x3azR.js +16 -0
  82. package/dist/plugin-sdk/{store-BbDQw3g6.js → store-CMHj6IIw.js} +2 -2
  83. package/dist/plugin-sdk/subagent-registry-runtime-1lbDyRzz.js +52 -0
  84. package/dist/plugin-sdk/{tables-BhvloMKN.js → tables-CSqrHsKL.js} +1 -1
  85. package/dist/plugin-sdk/{thinking-URzkT-3p.js → thinking-DOnsR_A8.js} +7 -7
  86. package/dist/plugin-sdk/{tokens-B1PW5Ayy.js → tokens-BDr0Z9o3.js} +1 -1
  87. package/dist/plugin-sdk/{tool-images-xpqbP6RR.js → tool-images-eEfOVkzf.js} +2 -2
  88. package/dist/plugin-sdk/web-BLyT64pW.js +56 -0
  89. package/dist/plugin-sdk/{whatsapp-actions-RcZ6vp61.js → whatsapp-actions-xcleMoMv.js} +17 -17
  90. package/dist/plugin-sdk/whatsapp.js +50 -50
  91. package/dist/{pw-ai-GcYO6HPE.js → pw-ai-CmphSzHx.js} +1 -1
  92. package/dist/{slash-dispatch.runtime-Dh053pQK.js → slash-dispatch.runtime-131yup2e.js} +6 -6
  93. package/dist/{subagent-registry-runtime-DSi5mnCQ.js → subagent-registry-runtime-DbSf_Je6.js} +6 -6
  94. package/dist/{web-1hWJDzNA.js → web-MR9d7KyB.js} +6 -6
  95. package/package.json +5 -2
  96. package/scripts/create-instance.sh +44 -19
  97. package/scripts/install-maca.sh +39 -28
  98. package/scripts/npm_publish.sh +8 -6
  99. package/ui/index.html +16 -0
  100. package/ui/node_modules/.bin/jiti +21 -0
  101. package/ui/node_modules/.bin/lessc +21 -0
  102. package/ui/node_modules/.bin/marked +21 -0
  103. package/ui/node_modules/.bin/playwright +21 -0
  104. package/ui/node_modules/.bin/sass +21 -0
  105. package/ui/node_modules/.bin/tsx +21 -0
  106. package/ui/node_modules/.bin/vite +21 -0
  107. package/ui/node_modules/.bin/vitest +21 -0
  108. package/ui/node_modules/.bin/yaml +21 -0
  109. package/ui/package.json +27 -0
  110. package/ui/public/apple-touch-icon.png +0 -0
  111. package/ui/public/favicon-32.png +0 -0
  112. package/ui/public/favicon.ico +0 -0
  113. package/ui/public/favicon.svg +22 -0
  114. package/ui/src/css.d.ts +1 -0
  115. package/ui/src/i18n/index.ts +3 -0
  116. package/ui/src/i18n/lib/lit-controller.ts +22 -0
  117. package/ui/src/i18n/lib/registry.ts +64 -0
  118. package/ui/src/i18n/lib/translate.ts +123 -0
  119. package/ui/src/i18n/lib/types.ts +9 -0
  120. package/ui/src/i18n/locales/de.ts +129 -0
  121. package/ui/src/i18n/locales/en.ts +337 -0
  122. package/ui/src/i18n/locales/pt-BR.ts +128 -0
  123. package/ui/src/i18n/locales/zh-CN.ts +330 -0
  124. package/ui/src/i18n/locales/zh-TW.ts +125 -0
  125. package/ui/src/i18n/test/translate.test.ts +56 -0
  126. package/ui/src/main.ts +2 -0
  127. package/ui/src/styles/base.css +385 -0
  128. package/ui/src/styles/chat/grouped.css +300 -0
  129. package/ui/src/styles/chat/layout.css +481 -0
  130. package/ui/src/styles/chat/sidebar.css +117 -0
  131. package/ui/src/styles/chat/text.css +146 -0
  132. package/ui/src/styles/chat/tool-cards.css +202 -0
  133. package/ui/src/styles/chat.css +5 -0
  134. package/ui/src/styles/components.css +2612 -0
  135. package/ui/src/styles/config.css +1658 -0
  136. package/ui/src/styles/layout.css +621 -0
  137. package/ui/src/styles/layout.mobile.css +374 -0
  138. package/ui/src/styles.css +5 -0
  139. package/ui/src/ui/__screenshots__/config-form.browser.test.ts/config-form-renderer-flags-unsupported-unions-1.png +0 -0
  140. package/ui/src/ui/__screenshots__/config-form.browser.test.ts/config-form-renderer-renders-inputs-and-patches-values-1.png +0 -0
  141. package/ui/src/ui/__screenshots__/config-form.browser.test.ts/config-form-renderer-renders-union-literals-as-select-options-1.png +0 -0
  142. package/ui/src/ui/__screenshots__/navigation.browser.test.ts/control-UI-routing-auto-scrolls-chat-history-to-the-latest-message-1.png +0 -0
  143. package/ui/src/ui/app-channels.ts +279 -0
  144. package/ui/src/ui/app-chat.ts +266 -0
  145. package/ui/src/ui/app-defaults.ts +50 -0
  146. package/ui/src/ui/app-events.ts +5 -0
  147. package/ui/src/ui/app-gateway.node.test.ts +229 -0
  148. package/ui/src/ui/app-gateway.ts +349 -0
  149. package/ui/src/ui/app-lifecycle.node.test.ts +44 -0
  150. package/ui/src/ui/app-lifecycle.ts +109 -0
  151. package/ui/src/ui/app-polling.ts +69 -0
  152. package/ui/src/ui/app-render-usage-tab.ts +273 -0
  153. package/ui/src/ui/app-render.helpers.node.test.ts +286 -0
  154. package/ui/src/ui/app-render.helpers.ts +574 -0
  155. package/ui/src/ui/app-render.ts +1168 -0
  156. package/ui/src/ui/app-scroll.test.ts +275 -0
  157. package/ui/src/ui/app-scroll.ts +179 -0
  158. package/ui/src/ui/app-settings.test.ts +70 -0
  159. package/ui/src/ui/app-settings.ts +440 -0
  160. package/ui/src/ui/app-tool-stream.node.test.ts +139 -0
  161. package/ui/src/ui/app-tool-stream.ts +455 -0
  162. package/ui/src/ui/app-view-state.ts +321 -0
  163. package/ui/src/ui/app.ts +621 -0
  164. package/ui/src/ui/assistant-identity.ts +23 -0
  165. package/ui/src/ui/chat/constants.ts +12 -0
  166. package/ui/src/ui/chat/copy-as-markdown.ts +97 -0
  167. package/ui/src/ui/chat/grouped-render.ts +287 -0
  168. package/ui/src/ui/chat/message-extract.test.ts +64 -0
  169. package/ui/src/ui/chat/message-extract.ts +122 -0
  170. package/ui/src/ui/chat/message-normalizer.test.ts +179 -0
  171. package/ui/src/ui/chat/message-normalizer.ts +101 -0
  172. package/ui/src/ui/chat/tool-cards.ts +156 -0
  173. package/ui/src/ui/chat/tool-helpers.test.ts +141 -0
  174. package/ui/src/ui/chat/tool-helpers.ts +37 -0
  175. package/ui/src/ui/chat-event-reload.test.ts +47 -0
  176. package/ui/src/ui/chat-event-reload.ts +16 -0
  177. package/ui/src/ui/chat-markdown.browser.test.ts +37 -0
  178. package/ui/src/ui/components/resizable-divider.ts +110 -0
  179. package/ui/src/ui/config-form.browser.test.ts +443 -0
  180. package/ui/src/ui/controllers/agent-files.ts +126 -0
  181. package/ui/src/ui/controllers/agent-identity.ts +59 -0
  182. package/ui/src/ui/controllers/agent-skills.ts +33 -0
  183. package/ui/src/ui/controllers/agents.test.ts +61 -0
  184. package/ui/src/ui/controllers/agents.ts +64 -0
  185. package/ui/src/ui/controllers/assistant-identity.ts +34 -0
  186. package/ui/src/ui/controllers/channels.ts +94 -0
  187. package/ui/src/ui/controllers/channels.types.ts +15 -0
  188. package/ui/src/ui/controllers/chat.test.ts +568 -0
  189. package/ui/src/ui/controllers/chat.ts +318 -0
  190. package/ui/src/ui/controllers/config/form-coerce.ts +160 -0
  191. package/ui/src/ui/controllers/config/form-utils.node.test.ts +455 -0
  192. package/ui/src/ui/controllers/config/form-utils.ts +90 -0
  193. package/ui/src/ui/controllers/config.test.ts +289 -0
  194. package/ui/src/ui/controllers/config.ts +219 -0
  195. package/ui/src/ui/controllers/control-ui-bootstrap.test.ts +82 -0
  196. package/ui/src/ui/controllers/control-ui-bootstrap.ts +49 -0
  197. package/ui/src/ui/controllers/cron-filters.test.ts +81 -0
  198. package/ui/src/ui/controllers/cron.test.ts +1070 -0
  199. package/ui/src/ui/controllers/cron.ts +921 -0
  200. package/ui/src/ui/controllers/debug.ts +60 -0
  201. package/ui/src/ui/controllers/devices.ts +159 -0
  202. package/ui/src/ui/controllers/exec-approval.ts +100 -0
  203. package/ui/src/ui/controllers/exec-approvals.ts +170 -0
  204. package/ui/src/ui/controllers/logs.ts +147 -0
  205. package/ui/src/ui/controllers/nodes.ts +32 -0
  206. package/ui/src/ui/controllers/presence.ts +37 -0
  207. package/ui/src/ui/controllers/sessions.test.ts +104 -0
  208. package/ui/src/ui/controllers/sessions.ts +127 -0
  209. package/ui/src/ui/controllers/skills.ts +157 -0
  210. package/ui/src/ui/controllers/usage.node.test.ts +181 -0
  211. package/ui/src/ui/controllers/usage.ts +315 -0
  212. package/ui/src/ui/data/moonshot-kimi-k2.ts +45 -0
  213. package/ui/src/ui/device-auth.ts +73 -0
  214. package/ui/src/ui/device-identity.ts +112 -0
  215. package/ui/src/ui/external-link.test.ts +18 -0
  216. package/ui/src/ui/external-link.ts +19 -0
  217. package/ui/src/ui/focus-mode.browser.test.ts +39 -0
  218. package/ui/src/ui/format.test.ts +101 -0
  219. package/ui/src/ui/format.ts +60 -0
  220. package/ui/src/ui/gateway.ts +360 -0
  221. package/ui/src/ui/icons.ts +256 -0
  222. package/ui/src/ui/markdown.test.ts +85 -0
  223. package/ui/src/ui/markdown.ts +139 -0
  224. package/ui/src/ui/navigation.browser.test.ts +188 -0
  225. package/ui/src/ui/navigation.test.ts +189 -0
  226. package/ui/src/ui/navigation.ts +165 -0
  227. package/ui/src/ui/open-external-url.test.ts +108 -0
  228. package/ui/src/ui/open-external-url.ts +73 -0
  229. package/ui/src/ui/presenter.ts +85 -0
  230. package/ui/src/ui/storage.node.test.ts +63 -0
  231. package/ui/src/ui/storage.ts +99 -0
  232. package/ui/src/ui/test-helpers/app-mount.ts +27 -0
  233. package/ui/src/ui/text-direction.test.ts +24 -0
  234. package/ui/src/ui/text-direction.ts +30 -0
  235. package/ui/src/ui/theme-transition.ts +109 -0
  236. package/ui/src/ui/theme.ts +16 -0
  237. package/ui/src/ui/tool-display.ts +159 -0
  238. package/ui/src/ui/types/chat-types.ts +44 -0
  239. package/ui/src/ui/types.ts +627 -0
  240. package/ui/src/ui/ui-types.ts +54 -0
  241. package/ui/src/ui/usage-helpers.node.test.ts +43 -0
  242. package/ui/src/ui/usage-helpers.ts +321 -0
  243. package/ui/src/ui/usage-types.ts +22 -0
  244. package/ui/src/ui/uuid.test.ts +41 -0
  245. package/ui/src/ui/uuid.ts +57 -0
  246. package/ui/src/ui/views/agents-panels-status-files.ts +461 -0
  247. package/ui/src/ui/views/agents-panels-tools-skills.browser.test.ts +102 -0
  248. package/ui/src/ui/views/agents-panels-tools-skills.ts +537 -0
  249. package/ui/src/ui/views/agents-utils.test.ts +100 -0
  250. package/ui/src/ui/views/agents-utils.ts +502 -0
  251. package/ui/src/ui/views/agents.ts +499 -0
  252. package/ui/src/ui/views/channel-config-extras.ts +49 -0
  253. package/ui/src/ui/views/channels.config.ts +155 -0
  254. package/ui/src/ui/views/channels.discord.ts +65 -0
  255. package/ui/src/ui/views/channels.googlechat.ts +79 -0
  256. package/ui/src/ui/views/channels.imessage.ts +65 -0
  257. package/ui/src/ui/views/channels.nostr-profile-form.ts +321 -0
  258. package/ui/src/ui/views/channels.nostr.ts +237 -0
  259. package/ui/src/ui/views/channels.shared.ts +38 -0
  260. package/ui/src/ui/views/channels.signal.ts +69 -0
  261. package/ui/src/ui/views/channels.slack.ts +65 -0
  262. package/ui/src/ui/views/channels.telegram.ts +120 -0
  263. package/ui/src/ui/views/channels.ts +325 -0
  264. package/ui/src/ui/views/channels.types.ts +62 -0
  265. package/ui/src/ui/views/channels.whatsapp.ts +118 -0
  266. package/ui/src/ui/views/chat-image-open.browser.test.ts +70 -0
  267. package/ui/src/ui/views/chat.test.ts +227 -0
  268. package/ui/src/ui/views/chat.ts +616 -0
  269. package/ui/src/ui/views/config-form.analyze.ts +267 -0
  270. package/ui/src/ui/views/config-form.node.ts +1073 -0
  271. package/ui/src/ui/views/config-form.render.ts +478 -0
  272. package/ui/src/ui/views/config-form.search.node.test.ts +69 -0
  273. package/ui/src/ui/views/config-form.shared.ts +96 -0
  274. package/ui/src/ui/views/config-form.ts +4 -0
  275. package/ui/src/ui/views/config-search.node.test.ts +50 -0
  276. package/ui/src/ui/views/config-search.ts +92 -0
  277. package/ui/src/ui/views/config.browser.test.ts +233 -0
  278. package/ui/src/ui/views/config.ts +820 -0
  279. package/ui/src/ui/views/cron.test.ts +741 -0
  280. package/ui/src/ui/views/cron.ts +1758 -0
  281. package/ui/src/ui/views/debug.ts +151 -0
  282. package/ui/src/ui/views/exec-approval.ts +89 -0
  283. package/ui/src/ui/views/gateway-url-confirmation.ts +40 -0
  284. package/ui/src/ui/views/instances.ts +89 -0
  285. package/ui/src/ui/views/logs.ts +155 -0
  286. package/ui/src/ui/views/markdown-sidebar.ts +40 -0
  287. package/ui/src/ui/views/nodes-exec-approvals.ts +617 -0
  288. package/ui/src/ui/views/nodes-shared.ts +67 -0
  289. package/ui/src/ui/views/nodes.ts +485 -0
  290. package/ui/src/ui/views/overview-hints.ts +16 -0
  291. package/ui/src/ui/views/overview.node.test.ts +39 -0
  292. package/ui/src/ui/views/overview.ts +361 -0
  293. package/ui/src/ui/views/sessions.test.ts +81 -0
  294. package/ui/src/ui/views/sessions.ts +321 -0
  295. package/ui/src/ui/views/skills-grouping.ts +40 -0
  296. package/ui/src/ui/views/skills-shared.ts +52 -0
  297. package/ui/src/ui/views/skills.ts +192 -0
  298. package/ui/src/ui/views/usage-metrics.ts +578 -0
  299. package/ui/src/ui/views/usage-query.ts +277 -0
  300. package/ui/src/ui/views/usage-render-details.test.ts +136 -0
  301. package/ui/src/ui/views/usage-render-details.ts +1083 -0
  302. package/ui/src/ui/views/usage-render-overview.ts +796 -0
  303. package/ui/src/ui/views/usage-styles/usageStyles-part1.ts +701 -0
  304. package/ui/src/ui/views/usage-styles/usageStyles-part2.ts +702 -0
  305. package/ui/src/ui/views/usage-styles/usageStyles-part3.ts +551 -0
  306. package/ui/src/ui/views/usage.ts +836 -0
  307. package/ui/src/ui/views/usageStyles.ts +5 -0
  308. package/ui/src/ui/views/usageTypes.ts +105 -0
  309. package/ui/vite.config.ts +43 -0
  310. package/ui/vitest.config.ts +15 -0
  311. package/ui/vitest.node.config.ts +10 -0
  312. package/dist/plugin-sdk/deliver-runtime-BFdqklJM.js +0 -32
  313. package/dist/plugin-sdk/deps-send-discord.runtime-DuqpYwU0.js +0 -23
  314. package/dist/plugin-sdk/deps-send-imessage.runtime-CZ2rS8Lb.js +0 -22
  315. package/dist/plugin-sdk/deps-send-signal.runtime-BdqiWhIh.js +0 -21
  316. package/dist/plugin-sdk/deps-send-slack.runtime-04s36qiC.js +0 -19
  317. package/dist/plugin-sdk/deps-send-telegram.runtime-LE5tkPvr.js +0 -24
  318. package/dist/plugin-sdk/deps-send-whatsapp.runtime-Bz57lobC.js +0 -57
  319. package/dist/plugin-sdk/image-runtime-B8twoubs.js +0 -25
  320. package/dist/plugin-sdk/manager-runtime-CMeLwose.js +0 -15
  321. package/dist/plugin-sdk/pi-model-discovery-runtime-D8CJhtJY.js +0 -8
  322. package/dist/plugin-sdk/runtime-whatsapp-login.runtime-SkO91TZH.js +0 -10
  323. package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-B0VWK5hm.js +0 -19
  324. package/dist/plugin-sdk/slash-commands.runtime-DS6vCNSL.js +0 -13
  325. package/dist/plugin-sdk/slash-dispatch.runtime-BXrxb2wd.js +0 -52
  326. package/dist/plugin-sdk/slash-skill-commands.runtime-Bd6qQ2oT.js +0 -16
  327. package/dist/plugin-sdk/subagent-registry-runtime-1uwQbuXj.js +0 -52
  328. package/dist/plugin-sdk/web-B74yhL2N.js +0 -56
@@ -0,0 +1,796 @@
1
+ import { html, nothing } from "lit";
2
+ import { formatDurationCompact } from "../../../../src/infra/format-time/format-duration.ts";
3
+ import {
4
+ formatCost,
5
+ formatDayLabel,
6
+ formatFullDate,
7
+ formatTokens,
8
+ UsageInsightStats,
9
+ } from "./usage-metrics.ts";
10
+ import {
11
+ UsageAggregates,
12
+ UsageColumnId,
13
+ UsageSessionEntry,
14
+ UsageTotals,
15
+ CostDailyEntry,
16
+ } from "./usageTypes.ts";
17
+
18
+ function pct(part: number, total: number): number {
19
+ if (total === 0) {
20
+ return 0;
21
+ }
22
+ return (part / total) * 100;
23
+ }
24
+
25
+ function getCostBreakdown(totals: UsageTotals) {
26
+ // Use actual costs from API data (already aggregated in backend)
27
+ const totalCost = totals.totalCost || 0;
28
+
29
+ return {
30
+ input: {
31
+ tokens: totals.input,
32
+ cost: totals.inputCost || 0,
33
+ pct: pct(totals.inputCost || 0, totalCost),
34
+ },
35
+ output: {
36
+ tokens: totals.output,
37
+ cost: totals.outputCost || 0,
38
+ pct: pct(totals.outputCost || 0, totalCost),
39
+ },
40
+ cacheRead: {
41
+ tokens: totals.cacheRead,
42
+ cost: totals.cacheReadCost || 0,
43
+ pct: pct(totals.cacheReadCost || 0, totalCost),
44
+ },
45
+ cacheWrite: {
46
+ tokens: totals.cacheWrite,
47
+ cost: totals.cacheWriteCost || 0,
48
+ pct: pct(totals.cacheWriteCost || 0, totalCost),
49
+ },
50
+ totalCost,
51
+ };
52
+ }
53
+
54
+ function renderFilterChips(
55
+ selectedDays: string[],
56
+ selectedHours: number[],
57
+ selectedSessions: string[],
58
+ sessions: UsageSessionEntry[],
59
+ onClearDays: () => void,
60
+ onClearHours: () => void,
61
+ onClearSessions: () => void,
62
+ onClearFilters: () => void,
63
+ ) {
64
+ const hasFilters =
65
+ selectedDays.length > 0 || selectedHours.length > 0 || selectedSessions.length > 0;
66
+ if (!hasFilters) {
67
+ return nothing;
68
+ }
69
+
70
+ const selectedSession =
71
+ selectedSessions.length === 1 ? sessions.find((s) => s.key === selectedSessions[0]) : null;
72
+ const sessionsLabel = selectedSession
73
+ ? (selectedSession.label || selectedSession.key).slice(0, 20) +
74
+ ((selectedSession.label || selectedSession.key).length > 20 ? "…" : "")
75
+ : selectedSessions.length === 1
76
+ ? selectedSessions[0].slice(0, 8) + "…"
77
+ : `${selectedSessions.length} sessions`;
78
+ const sessionsFullName = selectedSession
79
+ ? selectedSession.label || selectedSession.key
80
+ : selectedSessions.length === 1
81
+ ? selectedSessions[0]
82
+ : selectedSessions.join(", ");
83
+
84
+ const daysLabel = selectedDays.length === 1 ? selectedDays[0] : `${selectedDays.length} days`;
85
+ const hoursLabel =
86
+ selectedHours.length === 1 ? `${selectedHours[0]}:00` : `${selectedHours.length} hours`;
87
+
88
+ return html`
89
+ <div class="active-filters">
90
+ ${
91
+ selectedDays.length > 0
92
+ ? html`
93
+ <div class="filter-chip">
94
+ <span class="filter-chip-label">Days: ${daysLabel}</span>
95
+ <button class="filter-chip-remove" @click=${onClearDays} title="Remove filter">×</button>
96
+ </div>
97
+ `
98
+ : nothing
99
+ }
100
+ ${
101
+ selectedHours.length > 0
102
+ ? html`
103
+ <div class="filter-chip">
104
+ <span class="filter-chip-label">Hours: ${hoursLabel}</span>
105
+ <button class="filter-chip-remove" @click=${onClearHours} title="Remove filter">×</button>
106
+ </div>
107
+ `
108
+ : nothing
109
+ }
110
+ ${
111
+ selectedSessions.length > 0
112
+ ? html`
113
+ <div class="filter-chip" title="${sessionsFullName}">
114
+ <span class="filter-chip-label">Session: ${sessionsLabel}</span>
115
+ <button class="filter-chip-remove" @click=${onClearSessions} title="Remove filter">×</button>
116
+ </div>
117
+ `
118
+ : nothing
119
+ }
120
+ ${
121
+ (selectedDays.length > 0 || selectedHours.length > 0) && selectedSessions.length > 0
122
+ ? html`
123
+ <button class="btn btn-sm filter-clear-btn" @click=${onClearFilters}>
124
+ Clear All
125
+ </button>
126
+ `
127
+ : nothing
128
+ }
129
+ </div>
130
+ `;
131
+ }
132
+
133
+ function renderDailyChartCompact(
134
+ daily: CostDailyEntry[],
135
+ selectedDays: string[],
136
+ chartMode: "tokens" | "cost",
137
+ dailyChartMode: "total" | "by-type",
138
+ onDailyChartModeChange: (mode: "total" | "by-type") => void,
139
+ onSelectDay: (day: string, shiftKey: boolean) => void,
140
+ ) {
141
+ if (!daily.length) {
142
+ return html`
143
+ <div class="daily-chart-compact">
144
+ <div class="sessions-panel-title">Daily Usage</div>
145
+ <div class="muted" style="padding: 20px; text-align: center">No data</div>
146
+ </div>
147
+ `;
148
+ }
149
+
150
+ const isTokenMode = chartMode === "tokens";
151
+ const values = daily.map((d) => (isTokenMode ? d.totalTokens : d.totalCost));
152
+ const maxValue = Math.max(...values, isTokenMode ? 1 : 0.0001);
153
+
154
+ // Calculate bar width based on number of days
155
+ const barMaxWidth = daily.length > 30 ? 12 : daily.length > 20 ? 18 : daily.length > 14 ? 24 : 32;
156
+ const showTotals = daily.length <= 14;
157
+
158
+ return html`
159
+ <div class="daily-chart-compact">
160
+ <div class="daily-chart-header">
161
+ <div class="chart-toggle small sessions-toggle">
162
+ <button
163
+ class="toggle-btn ${dailyChartMode === "total" ? "active" : ""}"
164
+ @click=${() => onDailyChartModeChange("total")}
165
+ >
166
+ Total
167
+ </button>
168
+ <button
169
+ class="toggle-btn ${dailyChartMode === "by-type" ? "active" : ""}"
170
+ @click=${() => onDailyChartModeChange("by-type")}
171
+ >
172
+ By Type
173
+ </button>
174
+ </div>
175
+ <div class="card-title">Daily ${isTokenMode ? "Token" : "Cost"} Usage</div>
176
+ </div>
177
+ <div class="daily-chart">
178
+ <div class="daily-chart-bars" style="--bar-max-width: ${barMaxWidth}px">
179
+ ${daily.map((d, idx) => {
180
+ const value = values[idx];
181
+ const heightPct = (value / maxValue) * 100;
182
+ const isSelected = selectedDays.includes(d.date);
183
+ const label = formatDayLabel(d.date);
184
+ // Shorter label for many days (just day number)
185
+ const shortLabel = daily.length > 20 ? String(parseInt(d.date.slice(8), 10)) : label;
186
+ const labelStyle = daily.length > 20 ? "font-size: 8px" : "";
187
+ const segments =
188
+ dailyChartMode === "by-type"
189
+ ? isTokenMode
190
+ ? [
191
+ { value: d.output, class: "output" },
192
+ { value: d.input, class: "input" },
193
+ { value: d.cacheWrite, class: "cache-write" },
194
+ { value: d.cacheRead, class: "cache-read" },
195
+ ]
196
+ : [
197
+ { value: d.outputCost ?? 0, class: "output" },
198
+ { value: d.inputCost ?? 0, class: "input" },
199
+ { value: d.cacheWriteCost ?? 0, class: "cache-write" },
200
+ { value: d.cacheReadCost ?? 0, class: "cache-read" },
201
+ ]
202
+ : [];
203
+ const breakdownLines =
204
+ dailyChartMode === "by-type"
205
+ ? isTokenMode
206
+ ? [
207
+ `Output ${formatTokens(d.output)}`,
208
+ `Input ${formatTokens(d.input)}`,
209
+ `Cache write ${formatTokens(d.cacheWrite)}`,
210
+ `Cache read ${formatTokens(d.cacheRead)}`,
211
+ ]
212
+ : [
213
+ `Output ${formatCost(d.outputCost ?? 0)}`,
214
+ `Input ${formatCost(d.inputCost ?? 0)}`,
215
+ `Cache write ${formatCost(d.cacheWriteCost ?? 0)}`,
216
+ `Cache read ${formatCost(d.cacheReadCost ?? 0)}`,
217
+ ]
218
+ : [];
219
+ const totalLabel = isTokenMode ? formatTokens(d.totalTokens) : formatCost(d.totalCost);
220
+ return html`
221
+ <div
222
+ class="daily-bar-wrapper ${isSelected ? "selected" : ""}"
223
+ @click=${(e: MouseEvent) => onSelectDay(d.date, e.shiftKey)}
224
+ >
225
+ ${
226
+ dailyChartMode === "by-type"
227
+ ? html`
228
+ <div
229
+ class="daily-bar"
230
+ style="height: ${heightPct.toFixed(1)}%; display: flex; flex-direction: column;"
231
+ >
232
+ ${(() => {
233
+ const total = segments.reduce((sum, seg) => sum + seg.value, 0) || 1;
234
+ return segments.map(
235
+ (seg) => html`
236
+ <div
237
+ class="cost-segment ${seg.class}"
238
+ style="height: ${(seg.value / total) * 100}%"
239
+ ></div>
240
+ `,
241
+ );
242
+ })()}
243
+ </div>
244
+ `
245
+ : html`
246
+ <div class="daily-bar" style="height: ${heightPct.toFixed(1)}%"></div>
247
+ `
248
+ }
249
+ ${showTotals ? html`<div class="daily-bar-total">${totalLabel}</div>` : nothing}
250
+ <div class="daily-bar-label" style="${labelStyle}">${shortLabel}</div>
251
+ <div class="daily-bar-tooltip">
252
+ <strong>${formatFullDate(d.date)}</strong><br />
253
+ ${formatTokens(d.totalTokens)} tokens<br />
254
+ ${formatCost(d.totalCost)}
255
+ ${
256
+ breakdownLines.length
257
+ ? html`${breakdownLines.map((line) => html`<div>${line}</div>`)}`
258
+ : nothing
259
+ }
260
+ </div>
261
+ </div>
262
+ `;
263
+ })}
264
+ </div>
265
+ </div>
266
+ </div>
267
+ `;
268
+ }
269
+
270
+ function renderCostBreakdownCompact(totals: UsageTotals, mode: "tokens" | "cost") {
271
+ const breakdown = getCostBreakdown(totals);
272
+ const isTokenMode = mode === "tokens";
273
+ const totalTokens = totals.totalTokens || 1;
274
+ const tokenPcts = {
275
+ output: pct(totals.output, totalTokens),
276
+ input: pct(totals.input, totalTokens),
277
+ cacheWrite: pct(totals.cacheWrite, totalTokens),
278
+ cacheRead: pct(totals.cacheRead, totalTokens),
279
+ };
280
+
281
+ return html`
282
+ <div class="cost-breakdown cost-breakdown-compact">
283
+ <div class="cost-breakdown-header">${isTokenMode ? "Tokens" : "Cost"} by Type</div>
284
+ <div class="cost-breakdown-bar">
285
+ <div class="cost-segment output" style="width: ${(isTokenMode ? tokenPcts.output : breakdown.output.pct).toFixed(1)}%"
286
+ title="Output: ${isTokenMode ? formatTokens(totals.output) : formatCost(breakdown.output.cost)}"></div>
287
+ <div class="cost-segment input" style="width: ${(isTokenMode ? tokenPcts.input : breakdown.input.pct).toFixed(1)}%"
288
+ title="Input: ${isTokenMode ? formatTokens(totals.input) : formatCost(breakdown.input.cost)}"></div>
289
+ <div class="cost-segment cache-write" style="width: ${(isTokenMode ? tokenPcts.cacheWrite : breakdown.cacheWrite.pct).toFixed(1)}%"
290
+ title="Cache Write: ${isTokenMode ? formatTokens(totals.cacheWrite) : formatCost(breakdown.cacheWrite.cost)}"></div>
291
+ <div class="cost-segment cache-read" style="width: ${(isTokenMode ? tokenPcts.cacheRead : breakdown.cacheRead.pct).toFixed(1)}%"
292
+ title="Cache Read: ${isTokenMode ? formatTokens(totals.cacheRead) : formatCost(breakdown.cacheRead.cost)}"></div>
293
+ </div>
294
+ <div class="cost-breakdown-legend">
295
+ <span class="legend-item"><span class="legend-dot output"></span>Output ${isTokenMode ? formatTokens(totals.output) : formatCost(breakdown.output.cost)}</span>
296
+ <span class="legend-item"><span class="legend-dot input"></span>Input ${isTokenMode ? formatTokens(totals.input) : formatCost(breakdown.input.cost)}</span>
297
+ <span class="legend-item"><span class="legend-dot cache-write"></span>Cache Write ${isTokenMode ? formatTokens(totals.cacheWrite) : formatCost(breakdown.cacheWrite.cost)}</span>
298
+ <span class="legend-item"><span class="legend-dot cache-read"></span>Cache Read ${isTokenMode ? formatTokens(totals.cacheRead) : formatCost(breakdown.cacheRead.cost)}</span>
299
+ </div>
300
+ <div class="cost-breakdown-total">
301
+ Total: ${isTokenMode ? formatTokens(totals.totalTokens) : formatCost(totals.totalCost)}
302
+ </div>
303
+ </div>
304
+ `;
305
+ }
306
+
307
+ function renderInsightList(
308
+ title: string,
309
+ items: Array<{ label: string; value: string; sub?: string }>,
310
+ emptyLabel: string,
311
+ ) {
312
+ return html`
313
+ <div class="usage-insight-card">
314
+ <div class="usage-insight-title">${title}</div>
315
+ ${
316
+ items.length === 0
317
+ ? html`<div class="muted">${emptyLabel}</div>`
318
+ : html`
319
+ <div class="usage-list">
320
+ ${items.map(
321
+ (item) => html`
322
+ <div class="usage-list-item">
323
+ <span>${item.label}</span>
324
+ <span class="usage-list-value">
325
+ <span>${item.value}</span>
326
+ ${item.sub ? html`<span class="usage-list-sub">${item.sub}</span>` : nothing}
327
+ </span>
328
+ </div>
329
+ `,
330
+ )}
331
+ </div>
332
+ `
333
+ }
334
+ </div>
335
+ `;
336
+ }
337
+
338
+ function renderPeakErrorList(
339
+ title: string,
340
+ items: Array<{ label: string; value: string; sub?: string }>,
341
+ emptyLabel: string,
342
+ ) {
343
+ return html`
344
+ <div class="usage-insight-card">
345
+ <div class="usage-insight-title">${title}</div>
346
+ ${
347
+ items.length === 0
348
+ ? html`<div class="muted">${emptyLabel}</div>`
349
+ : html`
350
+ <div class="usage-error-list">
351
+ ${items.map(
352
+ (item) => html`
353
+ <div class="usage-error-row">
354
+ <div class="usage-error-date">${item.label}</div>
355
+ <div class="usage-error-rate">${item.value}</div>
356
+ ${item.sub ? html`<div class="usage-error-sub">${item.sub}</div>` : nothing}
357
+ </div>
358
+ `,
359
+ )}
360
+ </div>
361
+ `
362
+ }
363
+ </div>
364
+ `;
365
+ }
366
+
367
+ function renderUsageInsights(
368
+ totals: UsageTotals | null,
369
+ aggregates: UsageAggregates,
370
+ stats: UsageInsightStats,
371
+ showCostHint: boolean,
372
+ errorHours: Array<{ label: string; value: string; sub?: string }>,
373
+ sessionCount: number,
374
+ totalSessions: number,
375
+ ) {
376
+ if (!totals) {
377
+ return nothing;
378
+ }
379
+
380
+ const avgTokens = aggregates.messages.total
381
+ ? Math.round(totals.totalTokens / aggregates.messages.total)
382
+ : 0;
383
+ const avgCost = aggregates.messages.total ? totals.totalCost / aggregates.messages.total : 0;
384
+ const cacheBase = totals.input + totals.cacheRead;
385
+ const cacheHitRate = cacheBase > 0 ? totals.cacheRead / cacheBase : 0;
386
+ const cacheHitLabel = cacheBase > 0 ? `${(cacheHitRate * 100).toFixed(1)}%` : "—";
387
+ const errorRatePct = stats.errorRate * 100;
388
+ const throughputLabel =
389
+ stats.throughputTokensPerMin !== undefined
390
+ ? `${formatTokens(Math.round(stats.throughputTokensPerMin))} tok/min`
391
+ : "—";
392
+ const throughputCostLabel =
393
+ stats.throughputCostPerMin !== undefined
394
+ ? `${formatCost(stats.throughputCostPerMin, 4)} / min`
395
+ : "—";
396
+ const avgDurationLabel =
397
+ stats.durationCount > 0
398
+ ? (formatDurationCompact(stats.avgDurationMs, { spaced: true }) ?? "—")
399
+ : "—";
400
+ const cacheHint = "Cache hit rate = cache read / (input + cache read). Higher is better.";
401
+ const errorHint = "Error rate = errors / total messages. Lower is better.";
402
+ const throughputHint = "Throughput shows tokens per minute over active time. Higher is better.";
403
+ const tokensHint = "Average tokens per message in this range.";
404
+ const costHint = showCostHint
405
+ ? "Average cost per message when providers report costs. Cost data is missing for some or all sessions in this range."
406
+ : "Average cost per message when providers report costs.";
407
+
408
+ const errorDays = aggregates.daily
409
+ .filter((day) => day.messages > 0 && day.errors > 0)
410
+ .map((day) => {
411
+ const rate = day.errors / day.messages;
412
+ return {
413
+ label: formatDayLabel(day.date),
414
+ value: `${(rate * 100).toFixed(2)}%`,
415
+ sub: `${day.errors} errors · ${day.messages} msgs · ${formatTokens(day.tokens)}`,
416
+ rate,
417
+ };
418
+ })
419
+ .toSorted((a, b) => b.rate - a.rate)
420
+ .slice(0, 5)
421
+ .map(({ rate: _rate, ...rest }) => rest);
422
+
423
+ const topModels = aggregates.byModel.slice(0, 5).map((entry) => ({
424
+ label: entry.model ?? "unknown",
425
+ value: formatCost(entry.totals.totalCost),
426
+ sub: `${formatTokens(entry.totals.totalTokens)} · ${entry.count} msgs`,
427
+ }));
428
+ const topProviders = aggregates.byProvider.slice(0, 5).map((entry) => ({
429
+ label: entry.provider ?? "unknown",
430
+ value: formatCost(entry.totals.totalCost),
431
+ sub: `${formatTokens(entry.totals.totalTokens)} · ${entry.count} msgs`,
432
+ }));
433
+ const topTools = aggregates.tools.tools.slice(0, 6).map((tool) => ({
434
+ label: tool.name,
435
+ value: `${tool.count}`,
436
+ sub: "calls",
437
+ }));
438
+ const topAgents = aggregates.byAgent.slice(0, 5).map((entry) => ({
439
+ label: entry.agentId,
440
+ value: formatCost(entry.totals.totalCost),
441
+ sub: formatTokens(entry.totals.totalTokens),
442
+ }));
443
+ const topChannels = aggregates.byChannel.slice(0, 5).map((entry) => ({
444
+ label: entry.channel,
445
+ value: formatCost(entry.totals.totalCost),
446
+ sub: formatTokens(entry.totals.totalTokens),
447
+ }));
448
+
449
+ return html`
450
+ <section class="card" style="margin-top: 16px;">
451
+ <div class="card-title">Usage Overview</div>
452
+ <div class="usage-summary-grid">
453
+ <div class="usage-summary-card">
454
+ <div class="usage-summary-title">
455
+ Messages
456
+ <span class="usage-summary-hint" title="Total user + assistant messages in range.">?</span>
457
+ </div>
458
+ <div class="usage-summary-value">${aggregates.messages.total}</div>
459
+ <div class="usage-summary-sub">
460
+ ${aggregates.messages.user} user · ${aggregates.messages.assistant} assistant
461
+ </div>
462
+ </div>
463
+ <div class="usage-summary-card">
464
+ <div class="usage-summary-title">
465
+ Tool Calls
466
+ <span class="usage-summary-hint" title="Total tool call count across sessions.">?</span>
467
+ </div>
468
+ <div class="usage-summary-value">${aggregates.tools.totalCalls}</div>
469
+ <div class="usage-summary-sub">${aggregates.tools.uniqueTools} tools used</div>
470
+ </div>
471
+ <div class="usage-summary-card">
472
+ <div class="usage-summary-title">
473
+ Errors
474
+ <span class="usage-summary-hint" title="Total message/tool errors in range.">?</span>
475
+ </div>
476
+ <div class="usage-summary-value">${aggregates.messages.errors}</div>
477
+ <div class="usage-summary-sub">${aggregates.messages.toolResults} tool results</div>
478
+ </div>
479
+ <div class="usage-summary-card">
480
+ <div class="usage-summary-title">
481
+ Avg Tokens / Msg
482
+ <span class="usage-summary-hint" title=${tokensHint}>?</span>
483
+ </div>
484
+ <div class="usage-summary-value">${formatTokens(avgTokens)}</div>
485
+ <div class="usage-summary-sub">Across ${aggregates.messages.total || 0} messages</div>
486
+ </div>
487
+ <div class="usage-summary-card">
488
+ <div class="usage-summary-title">
489
+ Avg Cost / Msg
490
+ <span class="usage-summary-hint" title=${costHint}>?</span>
491
+ </div>
492
+ <div class="usage-summary-value">${formatCost(avgCost, 4)}</div>
493
+ <div class="usage-summary-sub">${formatCost(totals.totalCost)} total</div>
494
+ </div>
495
+ <div class="usage-summary-card">
496
+ <div class="usage-summary-title">
497
+ Sessions
498
+ <span class="usage-summary-hint" title="Distinct sessions in the range.">?</span>
499
+ </div>
500
+ <div class="usage-summary-value">${sessionCount}</div>
501
+ <div class="usage-summary-sub">of ${totalSessions} in range</div>
502
+ </div>
503
+ <div class="usage-summary-card">
504
+ <div class="usage-summary-title">
505
+ Throughput
506
+ <span class="usage-summary-hint" title=${throughputHint}>?</span>
507
+ </div>
508
+ <div class="usage-summary-value">${throughputLabel}</div>
509
+ <div class="usage-summary-sub">${throughputCostLabel}</div>
510
+ </div>
511
+ <div class="usage-summary-card">
512
+ <div class="usage-summary-title">
513
+ Error Rate
514
+ <span class="usage-summary-hint" title=${errorHint}>?</span>
515
+ </div>
516
+ <div class="usage-summary-value ${errorRatePct > 5 ? "bad" : errorRatePct > 1 ? "warn" : "good"}">${errorRatePct.toFixed(2)}%</div>
517
+ <div class="usage-summary-sub">
518
+ ${aggregates.messages.errors} errors · ${avgDurationLabel} avg session
519
+ </div>
520
+ </div>
521
+ <div class="usage-summary-card">
522
+ <div class="usage-summary-title">
523
+ Cache Hit Rate
524
+ <span class="usage-summary-hint" title=${cacheHint}>?</span>
525
+ </div>
526
+ <div class="usage-summary-value ${cacheHitRate > 0.6 ? "good" : cacheHitRate > 0.3 ? "warn" : "bad"}">${cacheHitLabel}</div>
527
+ <div class="usage-summary-sub">
528
+ ${formatTokens(totals.cacheRead)} cached · ${formatTokens(cacheBase)} prompt
529
+ </div>
530
+ </div>
531
+ </div>
532
+ <div class="usage-insights-grid">
533
+ ${renderInsightList("Top Models", topModels, "No model data")}
534
+ ${renderInsightList("Top Providers", topProviders, "No provider data")}
535
+ ${renderInsightList("Top Tools", topTools, "No tool calls")}
536
+ ${renderInsightList("Top Agents", topAgents, "No agent data")}
537
+ ${renderInsightList("Top Channels", topChannels, "No channel data")}
538
+ ${renderPeakErrorList("Peak Error Days", errorDays, "No error data")}
539
+ ${renderPeakErrorList("Peak Error Hours", errorHours, "No error data")}
540
+ </div>
541
+ </section>
542
+ `;
543
+ }
544
+
545
+ function renderSessionsCard(
546
+ sessions: UsageSessionEntry[],
547
+ selectedSessions: string[],
548
+ selectedDays: string[],
549
+ isTokenMode: boolean,
550
+ sessionSort: "tokens" | "cost" | "recent" | "messages" | "errors",
551
+ sessionSortDir: "asc" | "desc",
552
+ recentSessions: string[],
553
+ sessionsTab: "all" | "recent",
554
+ onSelectSession: (key: string, shiftKey: boolean) => void,
555
+ onSessionSortChange: (sort: "tokens" | "cost" | "recent" | "messages" | "errors") => void,
556
+ onSessionSortDirChange: (dir: "asc" | "desc") => void,
557
+ onSessionsTabChange: (tab: "all" | "recent") => void,
558
+ visibleColumns: UsageColumnId[],
559
+ totalSessions: number,
560
+ onClearSessions: () => void,
561
+ ) {
562
+ const showColumn = (id: UsageColumnId) => visibleColumns.includes(id);
563
+ const formatSessionListLabel = (s: UsageSessionEntry): string => {
564
+ const raw = s.label || s.key;
565
+ // Agent session keys often include a token query param; remove it for readability.
566
+ if (raw.startsWith("agent:") && raw.includes("?token=")) {
567
+ return raw.slice(0, raw.indexOf("?token="));
568
+ }
569
+ return raw;
570
+ };
571
+ const copySessionName = async (s: UsageSessionEntry) => {
572
+ const text = formatSessionListLabel(s);
573
+ try {
574
+ await navigator.clipboard.writeText(text);
575
+ } catch {
576
+ // Best effort; clipboard can fail on insecure contexts or denied permission.
577
+ }
578
+ };
579
+
580
+ const buildSessionMeta = (s: UsageSessionEntry): string[] => {
581
+ const parts: string[] = [];
582
+ if (showColumn("channel") && s.channel) {
583
+ parts.push(`channel:${s.channel}`);
584
+ }
585
+ if (showColumn("agent") && s.agentId) {
586
+ parts.push(`agent:${s.agentId}`);
587
+ }
588
+ if (showColumn("provider") && (s.modelProvider || s.providerOverride)) {
589
+ parts.push(`provider:${s.modelProvider ?? s.providerOverride}`);
590
+ }
591
+ if (showColumn("model") && s.model) {
592
+ parts.push(`model:${s.model}`);
593
+ }
594
+ if (showColumn("messages") && s.usage?.messageCounts) {
595
+ parts.push(`msgs:${s.usage.messageCounts.total}`);
596
+ }
597
+ if (showColumn("tools") && s.usage?.toolUsage) {
598
+ parts.push(`tools:${s.usage.toolUsage.totalCalls}`);
599
+ }
600
+ if (showColumn("errors") && s.usage?.messageCounts) {
601
+ parts.push(`errors:${s.usage.messageCounts.errors}`);
602
+ }
603
+ if (showColumn("duration") && s.usage?.durationMs) {
604
+ parts.push(`dur:${formatDurationCompact(s.usage.durationMs, { spaced: true }) ?? "—"}`);
605
+ }
606
+ return parts;
607
+ };
608
+
609
+ // Helper to get session value (filtered by days if selected)
610
+ const getSessionValue = (s: UsageSessionEntry): number => {
611
+ const usage = s.usage;
612
+ if (!usage) {
613
+ return 0;
614
+ }
615
+
616
+ // If days are selected and session has daily breakdown, compute filtered total
617
+ if (selectedDays.length > 0 && usage.dailyBreakdown && usage.dailyBreakdown.length > 0) {
618
+ const filteredDays = usage.dailyBreakdown.filter((d) => selectedDays.includes(d.date));
619
+ return isTokenMode
620
+ ? filteredDays.reduce((sum, d) => sum + d.tokens, 0)
621
+ : filteredDays.reduce((sum, d) => sum + d.cost, 0);
622
+ }
623
+
624
+ // Otherwise use total
625
+ return isTokenMode ? (usage.totalTokens ?? 0) : (usage.totalCost ?? 0);
626
+ };
627
+
628
+ const sortedSessions = [...sessions].toSorted((a, b) => {
629
+ switch (sessionSort) {
630
+ case "recent":
631
+ return (b.updatedAt ?? 0) - (a.updatedAt ?? 0);
632
+ case "messages":
633
+ return (b.usage?.messageCounts?.total ?? 0) - (a.usage?.messageCounts?.total ?? 0);
634
+ case "errors":
635
+ return (b.usage?.messageCounts?.errors ?? 0) - (a.usage?.messageCounts?.errors ?? 0);
636
+ case "cost":
637
+ return getSessionValue(b) - getSessionValue(a);
638
+ case "tokens":
639
+ default:
640
+ return getSessionValue(b) - getSessionValue(a);
641
+ }
642
+ });
643
+ const sortedWithDir = sessionSortDir === "asc" ? sortedSessions.toReversed() : sortedSessions;
644
+
645
+ const totalValue = sortedWithDir.reduce((sum, session) => sum + getSessionValue(session), 0);
646
+ const avgValue = sortedWithDir.length ? totalValue / sortedWithDir.length : 0;
647
+ const totalErrors = sortedWithDir.reduce(
648
+ (sum, session) => sum + (session.usage?.messageCounts?.errors ?? 0),
649
+ 0,
650
+ );
651
+
652
+ const renderSessionBarRow = (s: UsageSessionEntry, isSelected: boolean) => {
653
+ const value = getSessionValue(s);
654
+ const displayLabel = formatSessionListLabel(s);
655
+ const meta = buildSessionMeta(s);
656
+ return html`
657
+ <div
658
+ class="session-bar-row ${isSelected ? "selected" : ""}"
659
+ @click=${(e: MouseEvent) => onSelectSession(s.key, e.shiftKey)}
660
+ title="${s.key}"
661
+ >
662
+ <div class="session-bar-label">
663
+ <div class="session-bar-title">${displayLabel}</div>
664
+ ${meta.length > 0 ? html`<div class="session-bar-meta">${meta.join(" · ")}</div>` : nothing}
665
+ </div>
666
+ <div class="session-bar-track" style="display: none;"></div>
667
+ <div class="session-bar-actions">
668
+ <button
669
+ class="session-copy-btn"
670
+ title="Copy session name"
671
+ @click=${(e: MouseEvent) => {
672
+ e.stopPropagation();
673
+ void copySessionName(s);
674
+ }}
675
+ >
676
+ Copy
677
+ </button>
678
+ <div class="session-bar-value">${isTokenMode ? formatTokens(value) : formatCost(value)}</div>
679
+ </div>
680
+ </div>
681
+ `;
682
+ };
683
+
684
+ const selectedSet = new Set(selectedSessions);
685
+ const selectedEntries = sortedWithDir.filter((s) => selectedSet.has(s.key));
686
+ const selectedCount = selectedEntries.length;
687
+ const sessionMap = new Map(sortedWithDir.map((s) => [s.key, s]));
688
+ const recentEntries = recentSessions
689
+ .map((key) => sessionMap.get(key))
690
+ .filter((entry): entry is UsageSessionEntry => Boolean(entry));
691
+
692
+ return html`
693
+ <div class="card sessions-card">
694
+ <div class="sessions-card-header">
695
+ <div class="card-title">Sessions</div>
696
+ <div class="sessions-card-count">
697
+ ${sessions.length} shown${totalSessions !== sessions.length ? ` · ${totalSessions} total` : ""}
698
+ </div>
699
+ </div>
700
+ <div class="sessions-card-meta">
701
+ <div class="sessions-card-stats">
702
+ <span>${isTokenMode ? formatTokens(avgValue) : formatCost(avgValue)} avg</span>
703
+ <span>${totalErrors} errors</span>
704
+ </div>
705
+ <div class="chart-toggle small">
706
+ <button
707
+ class="toggle-btn ${sessionsTab === "all" ? "active" : ""}"
708
+ @click=${() => onSessionsTabChange("all")}
709
+ >
710
+ All
711
+ </button>
712
+ <button
713
+ class="toggle-btn ${sessionsTab === "recent" ? "active" : ""}"
714
+ @click=${() => onSessionsTabChange("recent")}
715
+ >
716
+ Recently viewed
717
+ </button>
718
+ </div>
719
+ <label class="sessions-sort">
720
+ <span>Sort</span>
721
+ <select
722
+ @change=${(e: Event) => onSessionSortChange((e.target as HTMLSelectElement).value as typeof sessionSort)}
723
+ >
724
+ <option value="cost" ?selected=${sessionSort === "cost"}>Cost</option>
725
+ <option value="errors" ?selected=${sessionSort === "errors"}>Errors</option>
726
+ <option value="messages" ?selected=${sessionSort === "messages"}>Messages</option>
727
+ <option value="recent" ?selected=${sessionSort === "recent"}>Recent</option>
728
+ <option value="tokens" ?selected=${sessionSort === "tokens"}>Tokens</option>
729
+ </select>
730
+ </label>
731
+ <button
732
+ class="btn btn-sm sessions-action-btn icon"
733
+ @click=${() => onSessionSortDirChange(sessionSortDir === "desc" ? "asc" : "desc")}
734
+ title=${sessionSortDir === "desc" ? "Descending" : "Ascending"}
735
+ >
736
+ ${sessionSortDir === "desc" ? "↓" : "↑"}
737
+ </button>
738
+ ${
739
+ selectedCount > 0
740
+ ? html`
741
+ <button class="btn btn-sm sessions-action-btn sessions-clear-btn" @click=${onClearSessions}>
742
+ Clear Selection
743
+ </button>
744
+ `
745
+ : nothing
746
+ }
747
+ </div>
748
+ ${
749
+ sessionsTab === "recent"
750
+ ? recentEntries.length === 0
751
+ ? html`
752
+ <div class="muted" style="padding: 20px; text-align: center">No recent sessions</div>
753
+ `
754
+ : html`
755
+ <div class="session-bars" style="max-height: 220px; margin-top: 6px;">
756
+ ${recentEntries.map((s) => renderSessionBarRow(s, selectedSet.has(s.key)))}
757
+ </div>
758
+ `
759
+ : sessions.length === 0
760
+ ? html`
761
+ <div class="muted" style="padding: 20px; text-align: center">No sessions in range</div>
762
+ `
763
+ : html`
764
+ <div class="session-bars">
765
+ ${sortedWithDir
766
+ .slice(0, 50)
767
+ .map((s) => renderSessionBarRow(s, selectedSet.has(s.key)))}
768
+ ${sessions.length > 50 ? html`<div class="muted" style="padding: 8px; text-align: center; font-size: 11px;">+${sessions.length - 50} more</div>` : nothing}
769
+ </div>
770
+ `
771
+ }
772
+ ${
773
+ selectedCount > 1
774
+ ? html`
775
+ <div style="margin-top: 10px;">
776
+ <div class="sessions-card-count">Selected (${selectedCount})</div>
777
+ <div class="session-bars" style="max-height: 160px; margin-top: 6px;">
778
+ ${selectedEntries.map((s) => renderSessionBarRow(s, true))}
779
+ </div>
780
+ </div>
781
+ `
782
+ : nothing
783
+ }
784
+ </div>
785
+ `;
786
+ }
787
+
788
+ export {
789
+ renderCostBreakdownCompact,
790
+ renderDailyChartCompact,
791
+ renderFilterChips,
792
+ renderInsightList,
793
+ renderPeakErrorList,
794
+ renderSessionsCard,
795
+ renderUsageInsights,
796
+ };