openclaw-multi-auto 1.3.7 → 1.3.9

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 (414) hide show
  1. package/dist/{accounts-L9ByEpnP.js → accounts-C9HcPI9h.js} +2 -2
  2. package/dist/{accounts-BOzyfwW4.js → accounts-C_lW3Ag9.js} +2 -2
  3. package/dist/{accounts-yfBeCZtS.js → accounts-Tgelvk0C.js} +17 -17
  4. package/dist/{active-listener-D1yqT1cw.js → active-listener-BEdprTkn.js} +2 -2
  5. package/dist/{api-key-rotation-DtsNS2Nb.js → api-key-rotation-BJpKWXy0.js} +2 -2
  6. package/dist/{audio-preflight-DpxQCpsA.js → audio-preflight-BMvgEQ5j.js} +32 -32
  7. package/dist/{audio-transcription-runner-28fcRNNi.js → audio-transcription-runner-gLFfz8fr.js} +12 -12
  8. package/dist/{audit-membership-runtime-DWyHWAHM.js → audit-membership-runtime-Dntemq07.js} +4 -4
  9. package/dist/build-info.json +3 -3
  10. package/dist/bundled/boot-md/handler.js +51 -51
  11. package/dist/bundled/bootstrap-extra-files/handler.js +6 -6
  12. package/dist/bundled/command-logger/handler.js +2 -2
  13. package/dist/bundled/session-memory/handler.js +51 -51
  14. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  15. package/dist/{channel-activity-xHOMiarp.js → channel-activity-BDnjYF7B.js} +3 -3
  16. package/dist/{chrome-DwizpzOC.js → chrome-DxxEKrY7.js} +18 -18
  17. package/dist/{commands-registry-V1zZ5pPC.js → commands-registry-D5qXbFJn.js} +4 -4
  18. package/dist/control-ui/apple-touch-icon.png +0 -0
  19. package/dist/control-ui/assets/de-Bm0iuKxz.js +2 -0
  20. package/dist/control-ui/assets/de-Bm0iuKxz.js.map +1 -0
  21. package/dist/control-ui/assets/index-7XbUBC_m.js +8380 -0
  22. package/dist/control-ui/assets/index-7XbUBC_m.js.map +1 -0
  23. package/dist/control-ui/assets/index-E0j6Tkrc.css +1 -0
  24. package/dist/control-ui/assets/pt-BR-C2uaHesk.js +2 -0
  25. package/dist/control-ui/assets/pt-BR-C2uaHesk.js.map +1 -0
  26. package/dist/control-ui/assets/zh-CN-CqPGpAps.js +2 -0
  27. package/dist/control-ui/assets/zh-CN-CqPGpAps.js.map +1 -0
  28. package/dist/control-ui/assets/zh-TW-Cyl5GDQh.js +2 -0
  29. package/dist/control-ui/assets/zh-TW-Cyl5GDQh.js.map +1 -0
  30. package/dist/control-ui/favicon-32.png +0 -0
  31. package/dist/control-ui/favicon.ico +0 -0
  32. package/dist/control-ui/favicon.svg +22 -0
  33. package/dist/control-ui/index.html +17 -0
  34. package/dist/{deliver-D4o6VIur.js → deliver-DbdywYJE.js} +21 -21
  35. package/dist/deliver-runtime-BFs7iAZF.js +36 -0
  36. package/dist/deps-send-discord.runtime-DZUccI6Z.js +26 -0
  37. package/dist/deps-send-imessage.runtime-CF3OpoqY.js +25 -0
  38. package/dist/deps-send-signal.runtime-Cw4-ozeO.js +24 -0
  39. package/dist/deps-send-slack.runtime-BDsDhS1P.js +22 -0
  40. package/dist/deps-send-telegram.runtime-D_4xVasO.js +27 -0
  41. package/dist/deps-send-whatsapp.runtime-DK8jqd14.js +60 -0
  42. package/dist/{diagnostic-Bn4PZjMZ.js → diagnostic-Co6Kghr-.js} +2 -2
  43. package/dist/{errors-CCLeFWAg.js → errors-xt401nuk.js} +1 -1
  44. package/dist/{fetch-BlJWzEP6.js → fetch-DuraYswo.js} +5 -5
  45. package/dist/{fetch-guard-ChYBwfiy.js → fetch-guard-DWr0d00H.js} +2 -2
  46. package/dist/{frontmatter-CvaMP376.js → frontmatter-BkTfEZ93.js} +3 -3
  47. package/dist/{fs-safe-0jAo_Whb.js → fs-safe-CTYUrIgQ.js} +4 -4
  48. package/dist/{github-copilot-token-D13V9YBz.js → github-copilot-token-BDioPmd6.js} +7 -7
  49. package/dist/{image-Bbn53mzj.js → image-eT7Y-nP5.js} +6 -6
  50. package/dist/{image-ops-CehkHxmW.js → image-ops-BuUnEOE0.js} +2 -2
  51. package/dist/image-runtime-BcAK3n8a.js +29 -0
  52. package/dist/{ir-DAP-B-Xw.js → ir-B83looB-.js} +8 -8
  53. package/dist/{legacy-names-TyzbVqa_.js → legacy-names-DOC03BkU.js} +1 -1
  54. package/dist/llm-slug-generator.js +51 -51
  55. package/dist/{logger-DMZQQtxK.js → logger-BfjWMCSD.js} +7 -7
  56. package/dist/{login-DiCctRo1.js → login-CrIwcrVI.js} +5 -5
  57. package/dist/{login-qr-MUbXgjtd.js → login-qr-BpPDZdl_.js} +10 -10
  58. package/dist/{manager-BW_NSIMl.js → manager-1bvuGrNR.js} +13 -13
  59. package/dist/manager-runtime-FO1Sx3W8.js +18 -0
  60. package/dist/{model-selection-idoqPmw0.js → model-selection-Dna0Gz1k.js} +43 -43
  61. package/dist/{outbound-C2kanETZ.js → outbound-ChDjtuD6.js} +6 -6
  62. package/dist/{outbound-attachment-DBrYWX8h.js → outbound-attachment-DqHlD21U.js} +2 -2
  63. package/dist/{path-alias-guards-DqXRZmsL.js → path-alias-guards-BzvdLvTI.js} +1 -1
  64. package/dist/{paths-CCxysrzL.js → paths-Bkr-BCxW.js} +4 -4
  65. package/dist/{paths-C6TxBCvO.js → paths-Cvc9EM8Y.js} +5 -5
  66. package/dist/{pi-embedded-BaGj07T0.js → pi-embedded-BQQa91aA.js} +158 -158
  67. package/dist/{pi-embedded-helpers-wy0DZvx1.js → pi-embedded-helpers-CLXm10bV.js} +52 -52
  68. package/dist/{plugin-sdk/pi-model-discovery-v-XPUOOf.js → pi-model-discovery-Dymwdjt0.js} +2 -2
  69. package/dist/pi-model-discovery-runtime-BeY4EUPp.js +11 -0
  70. package/dist/{pi-tools.before-tool-call.runtime-BuLxSyx9.js → pi-tools.before-tool-call.runtime-Cwab_5W1.js} +9 -9
  71. package/dist/plugin-sdk/index.js +50 -50
  72. package/dist/plugin-sdk/mattermost.js +3 -3
  73. package/dist/plugin-sdk/signal.js +2 -2
  74. package/dist/plugin-sdk/zalo.js +2 -2
  75. package/dist/{plugins-CWkRQYDj.js → plugins-4Rj4OjLY.js} +11 -11
  76. package/dist/{proxy-env-Cq5gdrbj.js → proxy-env-DlmzDx8x.js} +1 -1
  77. package/dist/{proxy-fetch-CCjEYbFm.js → proxy-fetch-B2pEfjbR.js} +1 -1
  78. package/dist/{pw-ai-Cl1Lc7RC.js → pw-ai-DNMjFMqH.js} +14 -14
  79. package/dist/{qmd-manager-BsYsO9Ii.js → qmd-manager-BtIKUaO9.js} +10 -10
  80. package/dist/{query-expansion-DtLc3wjL.js → query-expansion-CX-1fS52.js} +6 -6
  81. package/dist/{plugin-sdk/redact-DjVX-1N3.js → redact-COik8ET1.js} +1 -1
  82. package/dist/{run-with-concurrency-D_ZpbgEG.js → run-with-concurrency-BgYfgkXT.js} +4 -4
  83. package/dist/runtime-whatsapp-login.runtime-DUb55byQ.js +13 -0
  84. package/dist/runtime-whatsapp-outbound.runtime-Bii_xSfI.js +22 -0
  85. package/dist/{send-Dx2RkUOZ.js → send-6lz6rNVP.js} +6 -6
  86. package/dist/{send-vmONuVgL.js → send-BHTiZcH3.js} +26 -26
  87. package/dist/{send-Bj776ESJ.js → send-L7gRiwyd.js} +7 -7
  88. package/dist/{send-DcxmcFi_.js → send-PE6cwoTe.js} +8 -8
  89. package/dist/{send-BQERFNyo.js → send-dfu6_rgf.js} +5 -5
  90. package/dist/{session-A4QhBRvH.js → session-D8ImowSs.js} +8 -8
  91. package/dist/{skill-commands-CMzBZKG2.js → skill-commands-DNqJ-kwn.js} +9 -9
  92. package/dist/{skills-CE_iqvM5.js → skills-7ODkHQYp.js} +22 -22
  93. package/dist/slash-commands.runtime-CVw6566g.js +16 -0
  94. package/dist/slash-dispatch.runtime-B9Ygtzi4.js +56 -0
  95. package/dist/slash-skill-commands.runtime-DxZ4z5h6.js +20 -0
  96. package/dist/{store--eR1R_UX.js → store-D89wDcz9.js} +2 -2
  97. package/dist/subagent-registry-runtime-DL1Wv7nA.js +56 -0
  98. package/dist/{subsystem-Di1z8l0Z.js → subsystem-B45WV3qB.js} +14 -14
  99. package/dist/{tables-d739Y1xW.js → tables-mE4cJBN2.js} +1 -1
  100. package/dist/{plugin-sdk/target-errors-Blia4S69.js → target-errors-mnlwhAjP.js} +2 -2
  101. package/dist/{thinking-DXYisHiZ.js → thinking-BeGmb5k6.js} +7 -7
  102. package/dist/{tokens-DxnY9ui_.js → tokens-q32vI39c.js} +1 -1
  103. package/dist/{tool-images-2cBx1W8h.js → tool-images-RZdHiZcG.js} +2 -2
  104. package/dist/{web-CzWRVmFt.js → web-Btj-e8kN.js} +55 -55
  105. package/dist/{whatsapp-actions-iEArE_Ez.js → whatsapp-actions-BHbJJyqw.js} +21 -21
  106. package/dist/{workspace-CUVC6GX1.js → workspace-U-DyR64O.js} +20 -20
  107. package/package.json +7 -5
  108. package/scripts/create-instance.sh +66 -27
  109. package/scripts/install-maca.sh +1 -1
  110. package/scripts/npm_publish.sh +5 -1
  111. package/ui/index.html +16 -0
  112. package/ui/node_modules/.bin/jiti +21 -0
  113. package/ui/node_modules/.bin/lessc +21 -0
  114. package/ui/node_modules/.bin/marked +21 -0
  115. package/ui/node_modules/.bin/playwright +21 -0
  116. package/ui/node_modules/.bin/sass +21 -0
  117. package/ui/node_modules/.bin/tsx +21 -0
  118. package/ui/node_modules/.bin/vite +21 -0
  119. package/ui/node_modules/.bin/vitest +21 -0
  120. package/ui/node_modules/.bin/yaml +21 -0
  121. package/ui/package.json +27 -0
  122. package/ui/public/apple-touch-icon.png +0 -0
  123. package/ui/public/favicon-32.png +0 -0
  124. package/ui/public/favicon.ico +0 -0
  125. package/ui/public/favicon.svg +22 -0
  126. package/ui/src/css.d.ts +1 -0
  127. package/ui/src/i18n/index.ts +3 -0
  128. package/ui/src/i18n/lib/lit-controller.ts +22 -0
  129. package/ui/src/i18n/lib/registry.ts +64 -0
  130. package/ui/src/i18n/lib/translate.ts +123 -0
  131. package/ui/src/i18n/lib/types.ts +9 -0
  132. package/ui/src/i18n/locales/de.ts +129 -0
  133. package/ui/src/i18n/locales/en.ts +337 -0
  134. package/ui/src/i18n/locales/pt-BR.ts +128 -0
  135. package/ui/src/i18n/locales/zh-CN.ts +330 -0
  136. package/ui/src/i18n/locales/zh-TW.ts +125 -0
  137. package/ui/src/i18n/test/translate.test.ts +56 -0
  138. package/ui/src/main.ts +2 -0
  139. package/ui/src/styles/base.css +385 -0
  140. package/ui/src/styles/chat/grouped.css +300 -0
  141. package/ui/src/styles/chat/layout.css +481 -0
  142. package/ui/src/styles/chat/sidebar.css +117 -0
  143. package/ui/src/styles/chat/text.css +146 -0
  144. package/ui/src/styles/chat/tool-cards.css +202 -0
  145. package/ui/src/styles/chat.css +5 -0
  146. package/ui/src/styles/components.css +2612 -0
  147. package/ui/src/styles/config.css +1658 -0
  148. package/ui/src/styles/layout.css +621 -0
  149. package/ui/src/styles/layout.mobile.css +374 -0
  150. package/ui/src/styles.css +5 -0
  151. package/ui/src/ui/__screenshots__/config-form.browser.test.ts/config-form-renderer-flags-unsupported-unions-1.png +0 -0
  152. package/ui/src/ui/__screenshots__/config-form.browser.test.ts/config-form-renderer-renders-inputs-and-patches-values-1.png +0 -0
  153. package/ui/src/ui/__screenshots__/config-form.browser.test.ts/config-form-renderer-renders-union-literals-as-select-options-1.png +0 -0
  154. package/ui/src/ui/__screenshots__/navigation.browser.test.ts/control-UI-routing-auto-scrolls-chat-history-to-the-latest-message-1.png +0 -0
  155. package/ui/src/ui/app-channels.ts +279 -0
  156. package/ui/src/ui/app-chat.ts +266 -0
  157. package/ui/src/ui/app-defaults.ts +50 -0
  158. package/ui/src/ui/app-events.ts +5 -0
  159. package/ui/src/ui/app-gateway.node.test.ts +229 -0
  160. package/ui/src/ui/app-gateway.ts +349 -0
  161. package/ui/src/ui/app-lifecycle.node.test.ts +44 -0
  162. package/ui/src/ui/app-lifecycle.ts +109 -0
  163. package/ui/src/ui/app-polling.ts +69 -0
  164. package/ui/src/ui/app-render-usage-tab.ts +273 -0
  165. package/ui/src/ui/app-render.helpers.node.test.ts +286 -0
  166. package/ui/src/ui/app-render.helpers.ts +574 -0
  167. package/ui/src/ui/app-render.ts +1168 -0
  168. package/ui/src/ui/app-scroll.test.ts +275 -0
  169. package/ui/src/ui/app-scroll.ts +179 -0
  170. package/ui/src/ui/app-settings.test.ts +70 -0
  171. package/ui/src/ui/app-settings.ts +440 -0
  172. package/ui/src/ui/app-tool-stream.node.test.ts +139 -0
  173. package/ui/src/ui/app-tool-stream.ts +455 -0
  174. package/ui/src/ui/app-view-state.ts +321 -0
  175. package/ui/src/ui/app.ts +621 -0
  176. package/ui/src/ui/assistant-identity.ts +23 -0
  177. package/ui/src/ui/chat/constants.ts +12 -0
  178. package/ui/src/ui/chat/copy-as-markdown.ts +97 -0
  179. package/ui/src/ui/chat/grouped-render.ts +287 -0
  180. package/ui/src/ui/chat/message-extract.test.ts +64 -0
  181. package/ui/src/ui/chat/message-extract.ts +122 -0
  182. package/ui/src/ui/chat/message-normalizer.test.ts +179 -0
  183. package/ui/src/ui/chat/message-normalizer.ts +101 -0
  184. package/ui/src/ui/chat/tool-cards.ts +156 -0
  185. package/ui/src/ui/chat/tool-helpers.test.ts +141 -0
  186. package/ui/src/ui/chat/tool-helpers.ts +37 -0
  187. package/ui/src/ui/chat-event-reload.test.ts +47 -0
  188. package/ui/src/ui/chat-event-reload.ts +16 -0
  189. package/ui/src/ui/chat-markdown.browser.test.ts +37 -0
  190. package/ui/src/ui/components/resizable-divider.ts +110 -0
  191. package/ui/src/ui/config-form.browser.test.ts +443 -0
  192. package/ui/src/ui/controllers/agent-files.ts +126 -0
  193. package/ui/src/ui/controllers/agent-identity.ts +59 -0
  194. package/ui/src/ui/controllers/agent-skills.ts +33 -0
  195. package/ui/src/ui/controllers/agents.test.ts +61 -0
  196. package/ui/src/ui/controllers/agents.ts +64 -0
  197. package/ui/src/ui/controllers/assistant-identity.ts +34 -0
  198. package/ui/src/ui/controllers/channels.ts +94 -0
  199. package/ui/src/ui/controllers/channels.types.ts +15 -0
  200. package/ui/src/ui/controllers/chat.test.ts +568 -0
  201. package/ui/src/ui/controllers/chat.ts +318 -0
  202. package/ui/src/ui/controllers/config/form-coerce.ts +160 -0
  203. package/ui/src/ui/controllers/config/form-utils.node.test.ts +455 -0
  204. package/ui/src/ui/controllers/config/form-utils.ts +90 -0
  205. package/ui/src/ui/controllers/config.test.ts +289 -0
  206. package/ui/src/ui/controllers/config.ts +219 -0
  207. package/ui/src/ui/controllers/control-ui-bootstrap.test.ts +82 -0
  208. package/ui/src/ui/controllers/control-ui-bootstrap.ts +49 -0
  209. package/ui/src/ui/controllers/cron-filters.test.ts +81 -0
  210. package/ui/src/ui/controllers/cron.test.ts +1070 -0
  211. package/ui/src/ui/controllers/cron.ts +921 -0
  212. package/ui/src/ui/controllers/debug.ts +60 -0
  213. package/ui/src/ui/controllers/devices.ts +159 -0
  214. package/ui/src/ui/controllers/exec-approval.ts +100 -0
  215. package/ui/src/ui/controllers/exec-approvals.ts +170 -0
  216. package/ui/src/ui/controllers/logs.ts +147 -0
  217. package/ui/src/ui/controllers/nodes.ts +32 -0
  218. package/ui/src/ui/controllers/presence.ts +37 -0
  219. package/ui/src/ui/controllers/sessions.test.ts +104 -0
  220. package/ui/src/ui/controllers/sessions.ts +127 -0
  221. package/ui/src/ui/controllers/skills.ts +157 -0
  222. package/ui/src/ui/controllers/usage.node.test.ts +181 -0
  223. package/ui/src/ui/controllers/usage.ts +315 -0
  224. package/ui/src/ui/data/moonshot-kimi-k2.ts +45 -0
  225. package/ui/src/ui/device-auth.ts +73 -0
  226. package/ui/src/ui/device-identity.ts +112 -0
  227. package/ui/src/ui/external-link.test.ts +18 -0
  228. package/ui/src/ui/external-link.ts +19 -0
  229. package/ui/src/ui/focus-mode.browser.test.ts +39 -0
  230. package/ui/src/ui/format.test.ts +101 -0
  231. package/ui/src/ui/format.ts +60 -0
  232. package/ui/src/ui/gateway.ts +360 -0
  233. package/ui/src/ui/icons.ts +256 -0
  234. package/ui/src/ui/markdown.test.ts +85 -0
  235. package/ui/src/ui/markdown.ts +139 -0
  236. package/ui/src/ui/navigation.browser.test.ts +188 -0
  237. package/ui/src/ui/navigation.test.ts +189 -0
  238. package/ui/src/ui/navigation.ts +165 -0
  239. package/ui/src/ui/open-external-url.test.ts +108 -0
  240. package/ui/src/ui/open-external-url.ts +73 -0
  241. package/ui/src/ui/presenter.ts +85 -0
  242. package/ui/src/ui/storage.node.test.ts +63 -0
  243. package/ui/src/ui/storage.ts +99 -0
  244. package/ui/src/ui/test-helpers/app-mount.ts +27 -0
  245. package/ui/src/ui/text-direction.test.ts +24 -0
  246. package/ui/src/ui/text-direction.ts +30 -0
  247. package/ui/src/ui/theme-transition.ts +109 -0
  248. package/ui/src/ui/theme.ts +16 -0
  249. package/ui/src/ui/tool-display.ts +159 -0
  250. package/ui/src/ui/types/chat-types.ts +44 -0
  251. package/ui/src/ui/types.ts +627 -0
  252. package/ui/src/ui/ui-types.ts +54 -0
  253. package/ui/src/ui/usage-helpers.node.test.ts +43 -0
  254. package/ui/src/ui/usage-helpers.ts +321 -0
  255. package/ui/src/ui/usage-types.ts +22 -0
  256. package/ui/src/ui/uuid.test.ts +41 -0
  257. package/ui/src/ui/uuid.ts +57 -0
  258. package/ui/src/ui/views/agents-panels-status-files.ts +461 -0
  259. package/ui/src/ui/views/agents-panels-tools-skills.browser.test.ts +102 -0
  260. package/ui/src/ui/views/agents-panels-tools-skills.ts +537 -0
  261. package/ui/src/ui/views/agents-utils.test.ts +100 -0
  262. package/ui/src/ui/views/agents-utils.ts +502 -0
  263. package/ui/src/ui/views/agents.ts +499 -0
  264. package/ui/src/ui/views/channel-config-extras.ts +49 -0
  265. package/ui/src/ui/views/channels.config.ts +155 -0
  266. package/ui/src/ui/views/channels.discord.ts +65 -0
  267. package/ui/src/ui/views/channels.googlechat.ts +79 -0
  268. package/ui/src/ui/views/channels.imessage.ts +65 -0
  269. package/ui/src/ui/views/channels.nostr-profile-form.ts +321 -0
  270. package/ui/src/ui/views/channels.nostr.ts +237 -0
  271. package/ui/src/ui/views/channels.shared.ts +38 -0
  272. package/ui/src/ui/views/channels.signal.ts +69 -0
  273. package/ui/src/ui/views/channels.slack.ts +65 -0
  274. package/ui/src/ui/views/channels.telegram.ts +120 -0
  275. package/ui/src/ui/views/channels.ts +325 -0
  276. package/ui/src/ui/views/channels.types.ts +62 -0
  277. package/ui/src/ui/views/channels.whatsapp.ts +118 -0
  278. package/ui/src/ui/views/chat-image-open.browser.test.ts +70 -0
  279. package/ui/src/ui/views/chat.test.ts +227 -0
  280. package/ui/src/ui/views/chat.ts +616 -0
  281. package/ui/src/ui/views/config-form.analyze.ts +267 -0
  282. package/ui/src/ui/views/config-form.node.ts +1073 -0
  283. package/ui/src/ui/views/config-form.render.ts +478 -0
  284. package/ui/src/ui/views/config-form.search.node.test.ts +69 -0
  285. package/ui/src/ui/views/config-form.shared.ts +96 -0
  286. package/ui/src/ui/views/config-form.ts +4 -0
  287. package/ui/src/ui/views/config-search.node.test.ts +50 -0
  288. package/ui/src/ui/views/config-search.ts +92 -0
  289. package/ui/src/ui/views/config.browser.test.ts +233 -0
  290. package/ui/src/ui/views/config.ts +820 -0
  291. package/ui/src/ui/views/cron.test.ts +741 -0
  292. package/ui/src/ui/views/cron.ts +1758 -0
  293. package/ui/src/ui/views/debug.ts +151 -0
  294. package/ui/src/ui/views/exec-approval.ts +89 -0
  295. package/ui/src/ui/views/gateway-url-confirmation.ts +40 -0
  296. package/ui/src/ui/views/instances.ts +89 -0
  297. package/ui/src/ui/views/logs.ts +155 -0
  298. package/ui/src/ui/views/markdown-sidebar.ts +40 -0
  299. package/ui/src/ui/views/nodes-exec-approvals.ts +617 -0
  300. package/ui/src/ui/views/nodes-shared.ts +67 -0
  301. package/ui/src/ui/views/nodes.ts +485 -0
  302. package/ui/src/ui/views/overview-hints.ts +16 -0
  303. package/ui/src/ui/views/overview.node.test.ts +39 -0
  304. package/ui/src/ui/views/overview.ts +361 -0
  305. package/ui/src/ui/views/sessions.test.ts +81 -0
  306. package/ui/src/ui/views/sessions.ts +321 -0
  307. package/ui/src/ui/views/skills-grouping.ts +40 -0
  308. package/ui/src/ui/views/skills-shared.ts +52 -0
  309. package/ui/src/ui/views/skills.ts +192 -0
  310. package/ui/src/ui/views/usage-metrics.ts +578 -0
  311. package/ui/src/ui/views/usage-query.ts +277 -0
  312. package/ui/src/ui/views/usage-render-details.test.ts +136 -0
  313. package/ui/src/ui/views/usage-render-details.ts +1083 -0
  314. package/ui/src/ui/views/usage-render-overview.ts +796 -0
  315. package/ui/src/ui/views/usage-styles/usageStyles-part1.ts +701 -0
  316. package/ui/src/ui/views/usage-styles/usageStyles-part2.ts +702 -0
  317. package/ui/src/ui/views/usage-styles/usageStyles-part3.ts +551 -0
  318. package/ui/src/ui/views/usage.ts +836 -0
  319. package/ui/src/ui/views/usageStyles.ts +5 -0
  320. package/ui/src/ui/views/usageTypes.ts +105 -0
  321. package/ui/vite.config.ts +43 -0
  322. package/ui/vitest.config.ts +15 -0
  323. package/ui/vitest.node.config.ts +10 -0
  324. package/dist/deliver-runtime-P-G3bPjW.js +0 -36
  325. package/dist/deps-send-discord.runtime-DnbhTFX9.js +0 -26
  326. package/dist/deps-send-imessage.runtime-BOiQ6mDx.js +0 -25
  327. package/dist/deps-send-signal.runtime-CTcl388M.js +0 -24
  328. package/dist/deps-send-slack.runtime-CCqBz4Kg.js +0 -22
  329. package/dist/deps-send-telegram.runtime-DGSKTCpH.js +0 -27
  330. package/dist/deps-send-whatsapp.runtime-CJkTHkah.js +0 -60
  331. package/dist/image-runtime-CVv2ra9J.js +0 -29
  332. package/dist/manager-runtime-BN6VevdC.js +0 -18
  333. package/dist/pi-model-discovery-BGgOlX8N.js +0 -134
  334. package/dist/pi-model-discovery-runtime-Bwmi4Ev8.js +0 -11
  335. package/dist/plugin-sdk/accounts-CJWOBzwB.js +0 -35
  336. package/dist/plugin-sdk/accounts-DP1-L-QS.js +0 -288
  337. package/dist/plugin-sdk/accounts-DZhWlEg3.js +0 -46
  338. package/dist/plugin-sdk/active-listener-B_sLJTXM.js +0 -50
  339. package/dist/plugin-sdk/api-key-rotation-BRE4X2tf.js +0 -181
  340. package/dist/plugin-sdk/audio-preflight-DGEUDxxR.js +0 -69
  341. package/dist/plugin-sdk/audio-transcription-runner-DkoPNPYt.js +0 -2176
  342. package/dist/plugin-sdk/audit-membership-runtime-DSBHHw7o.js +0 -58
  343. package/dist/plugin-sdk/channel-activity-F3d0yUwy.js +0 -94
  344. package/dist/plugin-sdk/channel-web-QF7EpjeP.js +0 -2256
  345. package/dist/plugin-sdk/chrome-BXoCyCkY.js +0 -2415
  346. package/dist/plugin-sdk/commands-registry-t7cXBTfN.js +0 -1125
  347. package/dist/plugin-sdk/config-BkEnz2Po.js +0 -17913
  348. package/dist/plugin-sdk/deliver-B6AG_l67.js +0 -1694
  349. package/dist/plugin-sdk/deliver-runtime-D585kJZc.js +0 -32
  350. package/dist/plugin-sdk/deps-send-discord.runtime-a_OKY2js.js +0 -23
  351. package/dist/plugin-sdk/deps-send-imessage.runtime-Baxy9TD4.js +0 -22
  352. package/dist/plugin-sdk/deps-send-signal.runtime-BwXoCrFl.js +0 -21
  353. package/dist/plugin-sdk/deps-send-slack.runtime-CLmKjgso.js +0 -19
  354. package/dist/plugin-sdk/deps-send-telegram.runtime-BKfdBKnZ.js +0 -24
  355. package/dist/plugin-sdk/deps-send-whatsapp.runtime-BOTwkbx_.js +0 -57
  356. package/dist/plugin-sdk/diagnostic-CsP-lEkI.js +0 -319
  357. package/dist/plugin-sdk/errors-DaiAM-yU.js +0 -54
  358. package/dist/plugin-sdk/fetch-guard-DETCcJzQ.js +0 -156
  359. package/dist/plugin-sdk/fs-safe-B8y811FR.js +0 -352
  360. package/dist/plugin-sdk/image-DjTEkYZE.js +0 -2310
  361. package/dist/plugin-sdk/image-ops-BSiMpAw4.js +0 -584
  362. package/dist/plugin-sdk/image-runtime-6xPp8m5a.js +0 -25
  363. package/dist/plugin-sdk/ir-DQ7_HbvK.js +0 -1296
  364. package/dist/plugin-sdk/local-roots-BUP4YBmR.js +0 -186
  365. package/dist/plugin-sdk/logger-CZY9KIoY.js +0 -1163
  366. package/dist/plugin-sdk/login-BxEKLlCo.js +0 -57
  367. package/dist/plugin-sdk/login-qr-BQIpMPr9.js +0 -320
  368. package/dist/plugin-sdk/manager-I6KbPihW.js +0 -3917
  369. package/dist/plugin-sdk/manager-runtime-CFfYYWIQ.js +0 -15
  370. package/dist/plugin-sdk/outbound-NS6UHnB6.js +0 -212
  371. package/dist/plugin-sdk/outbound-attachment-Dy6fyf6H.js +0 -19
  372. package/dist/plugin-sdk/path-alias-guards-DBjLbIX_.js +0 -43
  373. package/dist/plugin-sdk/paths-vTM3Lh3X.js +0 -166
  374. package/dist/plugin-sdk/pi-embedded-helpers-1R1gu7eX.js +0 -9627
  375. package/dist/plugin-sdk/pi-model-discovery-runtime-Do9o-dUd.js +0 -8
  376. package/dist/plugin-sdk/pi-tools.before-tool-call.runtime-D4sFsIks.js +0 -354
  377. package/dist/plugin-sdk/plugins-DeBZB9l_.js +0 -864
  378. package/dist/plugin-sdk/proxy-fetch-ChxOhWF4.js +0 -38
  379. package/dist/plugin-sdk/pw-ai-DEOmCSSC.js +0 -1938
  380. package/dist/plugin-sdk/qmd-manager-HyYKoEch.js +0 -1448
  381. package/dist/plugin-sdk/query-expansion-CeyKUeDW.js +0 -1011
  382. package/dist/plugin-sdk/reply-DAo_Jt8K.js +0 -97916
  383. package/dist/plugin-sdk/resolve-outbound-target-B42qgQS9.js +0 -40
  384. package/dist/plugin-sdk/run-with-concurrency-Bt_ks0Qa.js +0 -1994
  385. package/dist/plugin-sdk/runtime-whatsapp-login.runtime-B6W989eF.js +0 -10
  386. package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-c_GDFy37.js +0 -19
  387. package/dist/plugin-sdk/send-CQpMudwO.js +0 -2587
  388. package/dist/plugin-sdk/send-DQHLzVyO.js +0 -414
  389. package/dist/plugin-sdk/send-DTB24bEF.js +0 -3135
  390. package/dist/plugin-sdk/send-DfHadjZ_.js +0 -503
  391. package/dist/plugin-sdk/send-XXlW2iny.js +0 -540
  392. package/dist/plugin-sdk/session-6TF6MyaC.js +0 -169
  393. package/dist/plugin-sdk/skill-commands-CkGeFUMl.js +0 -342
  394. package/dist/plugin-sdk/skills-CBkHBYPq.js +0 -1428
  395. package/dist/plugin-sdk/slash-commands.runtime-CxliuGaP.js +0 -13
  396. package/dist/plugin-sdk/slash-dispatch.runtime-DFaeYlJQ.js +0 -52
  397. package/dist/plugin-sdk/slash-skill-commands.runtime-0M0OLCxq.js +0 -16
  398. package/dist/plugin-sdk/ssrf-cFtplYtS.js +0 -202
  399. package/dist/plugin-sdk/store-5nyxY3WU.js +0 -81
  400. package/dist/plugin-sdk/subagent-registry-runtime-DCtmDwna.js +0 -52
  401. package/dist/plugin-sdk/tables-C47P4GTN.js +0 -55
  402. package/dist/plugin-sdk/thinking-Bo2eosVa.js +0 -1206
  403. package/dist/plugin-sdk/tokens-DgNRBwIg.js +0 -52
  404. package/dist/plugin-sdk/tool-images-Gk_-0y2N.js +0 -274
  405. package/dist/plugin-sdk/web-CVxZbXyH.js +0 -56
  406. package/dist/plugin-sdk/whatsapp-actions-Bw0H9g-n.js +0 -80
  407. package/dist/redact-ClbcYG1J.js +0 -319
  408. package/dist/runtime-whatsapp-login.runtime-IeylZEl4.js +0 -13
  409. package/dist/runtime-whatsapp-outbound.runtime-ClBRuLsq.js +0 -22
  410. package/dist/slash-commands.runtime-Cpn2tYW4.js +0 -16
  411. package/dist/slash-dispatch.runtime-DoBAQBU5.js +0 -56
  412. package/dist/slash-skill-commands.runtime-DKMvvdDW.js +0 -20
  413. package/dist/subagent-registry-runtime-ppWS3tVu.js +0 -56
  414. package/dist/target-errors-CBI2Ga0y.js +0 -195
@@ -0,0 +1,836 @@
1
+ import { html, nothing } from "lit";
2
+ import { extractQueryTerms, filterSessionsByQuery } from "../usage-helpers.ts";
3
+ import {
4
+ buildAggregatesFromSessions,
5
+ buildPeakErrorHours,
6
+ buildUsageInsightStats,
7
+ formatCost,
8
+ formatIsoDate,
9
+ formatTokens,
10
+ getZonedHour,
11
+ renderUsageMosaic,
12
+ setToHourEnd,
13
+ } from "./usage-metrics.ts";
14
+ import {
15
+ addQueryToken,
16
+ applySuggestionToQuery,
17
+ buildDailyCsv,
18
+ buildQuerySuggestions,
19
+ buildSessionsCsv,
20
+ downloadTextFile,
21
+ normalizeQueryText,
22
+ removeQueryToken,
23
+ setQueryTokensForKey,
24
+ } from "./usage-query.ts";
25
+ import { renderEmptyDetailState, renderSessionDetailPanel } from "./usage-render-details.ts";
26
+ import {
27
+ renderCostBreakdownCompact,
28
+ renderDailyChartCompact,
29
+ renderFilterChips,
30
+ renderSessionsCard,
31
+ renderUsageInsights,
32
+ } from "./usage-render-overview.ts";
33
+ import { usageStylesString } from "./usageStyles.ts";
34
+ import {
35
+ SessionLogEntry,
36
+ SessionLogRole,
37
+ UsageColumnId,
38
+ UsageProps,
39
+ UsageSessionEntry,
40
+ UsageTotals,
41
+ } from "./usageTypes.ts";
42
+
43
+ export type { UsageColumnId, SessionLogEntry, SessionLogRole };
44
+
45
+ function createEmptyUsageTotals(): UsageTotals {
46
+ return {
47
+ input: 0,
48
+ output: 0,
49
+ cacheRead: 0,
50
+ cacheWrite: 0,
51
+ totalTokens: 0,
52
+ totalCost: 0,
53
+ inputCost: 0,
54
+ outputCost: 0,
55
+ cacheReadCost: 0,
56
+ cacheWriteCost: 0,
57
+ missingCostEntries: 0,
58
+ };
59
+ }
60
+
61
+ function addUsageTotals(
62
+ acc: UsageTotals,
63
+ usage: {
64
+ input: number;
65
+ output: number;
66
+ cacheRead: number;
67
+ cacheWrite: number;
68
+ totalTokens: number;
69
+ totalCost: number;
70
+ inputCost?: number;
71
+ outputCost?: number;
72
+ cacheReadCost?: number;
73
+ cacheWriteCost?: number;
74
+ missingCostEntries?: number;
75
+ },
76
+ ): UsageTotals {
77
+ acc.input += usage.input;
78
+ acc.output += usage.output;
79
+ acc.cacheRead += usage.cacheRead;
80
+ acc.cacheWrite += usage.cacheWrite;
81
+ acc.totalTokens += usage.totalTokens;
82
+ acc.totalCost += usage.totalCost;
83
+ acc.inputCost += usage.inputCost ?? 0;
84
+ acc.outputCost += usage.outputCost ?? 0;
85
+ acc.cacheReadCost += usage.cacheReadCost ?? 0;
86
+ acc.cacheWriteCost += usage.cacheWriteCost ?? 0;
87
+ acc.missingCostEntries += usage.missingCostEntries ?? 0;
88
+ return acc;
89
+ }
90
+
91
+ export function renderUsage(props: UsageProps) {
92
+ // Show loading skeleton if loading and no data yet
93
+ if (props.loading && !props.totals) {
94
+ // Use inline styles since main stylesheet hasn't loaded yet on initial render
95
+ return html`
96
+ <style>
97
+ @keyframes initial-spin {
98
+ to { transform: rotate(360deg); }
99
+ }
100
+ @keyframes initial-pulse {
101
+ 0%, 100% { opacity: 1; }
102
+ 50% { opacity: 0.7; }
103
+ }
104
+ </style>
105
+ <section class="card">
106
+ <div class="row" style="justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 12px;">
107
+ <div style="flex: 1; min-width: 250px;">
108
+ <div style="display: flex; align-items: center; gap: 10px; margin-bottom: 2px;">
109
+ <div class="card-title" style="margin: 0;">Token Usage</div>
110
+ <span style="
111
+ display: inline-flex;
112
+ align-items: center;
113
+ gap: 6px;
114
+ padding: 4px 10px;
115
+ background: rgba(255, 77, 77, 0.1);
116
+ border-radius: 4px;
117
+ font-size: 12px;
118
+ color: #ff4d4d;
119
+ ">
120
+ <span style="
121
+ width: 10px;
122
+ height: 10px;
123
+ border: 2px solid #ff4d4d;
124
+ border-top-color: transparent;
125
+ border-radius: 50%;
126
+ animation: initial-spin 0.6s linear infinite;
127
+ "></span>
128
+ Loading
129
+ </span>
130
+ </div>
131
+ </div>
132
+ <div style="display: flex; flex-direction: column; align-items: flex-end; gap: 8px;">
133
+ <div style="display: flex; gap: 8px; align-items: center;">
134
+ <input type="date" .value=${props.startDate} disabled style="padding: 6px 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--text); font-size: 13px; opacity: 0.6;" />
135
+ <span style="color: var(--muted);">to</span>
136
+ <input type="date" .value=${props.endDate} disabled style="padding: 6px 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--text); font-size: 13px; opacity: 0.6;" />
137
+ </div>
138
+ </div>
139
+ </div>
140
+ </section>
141
+ `;
142
+ }
143
+
144
+ const isTokenMode = props.chartMode === "tokens";
145
+ const hasQuery = props.query.trim().length > 0;
146
+ const hasDraftQuery = props.queryDraft.trim().length > 0;
147
+ // (intentionally no global Clear button in the header; chips + query clear handle this)
148
+
149
+ // Sort sessions by tokens or cost depending on mode
150
+ const sortedSessions = [...props.sessions].toSorted((a, b) => {
151
+ const valA = isTokenMode ? (a.usage?.totalTokens ?? 0) : (a.usage?.totalCost ?? 0);
152
+ const valB = isTokenMode ? (b.usage?.totalTokens ?? 0) : (b.usage?.totalCost ?? 0);
153
+ return valB - valA;
154
+ });
155
+
156
+ // Filter sessions by selected days
157
+ const dayFilteredSessions =
158
+ props.selectedDays.length > 0
159
+ ? sortedSessions.filter((s) => {
160
+ if (s.usage?.activityDates?.length) {
161
+ return s.usage.activityDates.some((d) => props.selectedDays.includes(d));
162
+ }
163
+ if (!s.updatedAt) {
164
+ return false;
165
+ }
166
+ const d = new Date(s.updatedAt);
167
+ const sessionDate = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
168
+ return props.selectedDays.includes(sessionDate);
169
+ })
170
+ : sortedSessions;
171
+
172
+ const sessionTouchesHours = (session: UsageSessionEntry, hours: number[]): boolean => {
173
+ if (hours.length === 0) {
174
+ return true;
175
+ }
176
+ const usage = session.usage;
177
+ const start = usage?.firstActivity ?? session.updatedAt;
178
+ const end = usage?.lastActivity ?? session.updatedAt;
179
+ if (!start || !end) {
180
+ return false;
181
+ }
182
+ const startMs = Math.min(start, end);
183
+ const endMs = Math.max(start, end);
184
+ let cursor = startMs;
185
+ while (cursor <= endMs) {
186
+ const date = new Date(cursor);
187
+ const hour = getZonedHour(date, props.timeZone);
188
+ if (hours.includes(hour)) {
189
+ return true;
190
+ }
191
+ const nextHour = setToHourEnd(date, props.timeZone);
192
+ const nextMs = Math.min(nextHour.getTime(), endMs);
193
+ cursor = nextMs + 1;
194
+ }
195
+ return false;
196
+ };
197
+
198
+ const hourFilteredSessions =
199
+ props.selectedHours.length > 0
200
+ ? dayFilteredSessions.filter((s) => sessionTouchesHours(s, props.selectedHours))
201
+ : dayFilteredSessions;
202
+
203
+ // Filter sessions by query (client-side)
204
+ const queryResult = filterSessionsByQuery(hourFilteredSessions, props.query);
205
+ const filteredSessions = queryResult.sessions;
206
+ const queryWarnings = queryResult.warnings;
207
+ const querySuggestions = buildQuerySuggestions(
208
+ props.queryDraft,
209
+ sortedSessions,
210
+ props.aggregates,
211
+ );
212
+ const queryTerms = extractQueryTerms(props.query);
213
+ const selectedValuesFor = (key: string): string[] => {
214
+ const normalized = normalizeQueryText(key);
215
+ return queryTerms
216
+ .filter((term) => normalizeQueryText(term.key ?? "") === normalized)
217
+ .map((term) => term.value)
218
+ .filter(Boolean);
219
+ };
220
+ const unique = (items: Array<string | undefined>) => {
221
+ const set = new Set<string>();
222
+ for (const item of items) {
223
+ if (item) {
224
+ set.add(item);
225
+ }
226
+ }
227
+ return Array.from(set);
228
+ };
229
+ const agentOptions = unique(sortedSessions.map((s) => s.agentId)).slice(0, 12);
230
+ const channelOptions = unique(sortedSessions.map((s) => s.channel)).slice(0, 12);
231
+ const providerOptions = unique([
232
+ ...sortedSessions.map((s) => s.modelProvider),
233
+ ...sortedSessions.map((s) => s.providerOverride),
234
+ ...(props.aggregates?.byProvider.map((entry) => entry.provider) ?? []),
235
+ ]).slice(0, 12);
236
+ const modelOptions = unique([
237
+ ...sortedSessions.map((s) => s.model),
238
+ ...(props.aggregates?.byModel.map((entry) => entry.model) ?? []),
239
+ ]).slice(0, 12);
240
+ const toolOptions = unique(props.aggregates?.tools.tools.map((tool) => tool.name) ?? []).slice(
241
+ 0,
242
+ 12,
243
+ );
244
+
245
+ // Get first selected session for detail view (timeseries, logs)
246
+ const primarySelectedEntry =
247
+ props.selectedSessions.length === 1
248
+ ? (props.sessions.find((s) => s.key === props.selectedSessions[0]) ??
249
+ filteredSessions.find((s) => s.key === props.selectedSessions[0]))
250
+ : null;
251
+
252
+ // Compute totals from sessions
253
+ const computeSessionTotals = (sessions: UsageSessionEntry[]): UsageTotals => {
254
+ return sessions.reduce(
255
+ (acc, s) => (s.usage ? addUsageTotals(acc, s.usage) : acc),
256
+ createEmptyUsageTotals(),
257
+ );
258
+ };
259
+
260
+ // Compute totals from daily data for selected days (more accurate than session totals)
261
+ const computeDailyTotals = (days: string[]): UsageTotals => {
262
+ const matchingDays = props.costDaily.filter((d) => days.includes(d.date));
263
+ return matchingDays.reduce((acc, day) => addUsageTotals(acc, day), createEmptyUsageTotals());
264
+ };
265
+
266
+ // Compute display totals and count based on filters
267
+ let displayTotals: UsageTotals | null;
268
+ let displaySessionCount: number;
269
+ const totalSessions = sortedSessions.length;
270
+
271
+ if (props.selectedSessions.length > 0) {
272
+ // Sessions selected - compute totals from selected sessions
273
+ const selectedSessionEntries = filteredSessions.filter((s) =>
274
+ props.selectedSessions.includes(s.key),
275
+ );
276
+ displayTotals = computeSessionTotals(selectedSessionEntries);
277
+ displaySessionCount = selectedSessionEntries.length;
278
+ } else if (props.selectedDays.length > 0 && props.selectedHours.length === 0) {
279
+ // Days selected - use daily aggregates for accurate per-day totals
280
+ displayTotals = computeDailyTotals(props.selectedDays);
281
+ displaySessionCount = filteredSessions.length;
282
+ } else if (props.selectedHours.length > 0) {
283
+ displayTotals = computeSessionTotals(filteredSessions);
284
+ displaySessionCount = filteredSessions.length;
285
+ } else if (hasQuery) {
286
+ displayTotals = computeSessionTotals(filteredSessions);
287
+ displaySessionCount = filteredSessions.length;
288
+ } else {
289
+ // No filters - show all
290
+ displayTotals = props.totals;
291
+ displaySessionCount = totalSessions;
292
+ }
293
+
294
+ const aggregateSessions =
295
+ props.selectedSessions.length > 0
296
+ ? filteredSessions.filter((s) => props.selectedSessions.includes(s.key))
297
+ : hasQuery || props.selectedHours.length > 0
298
+ ? filteredSessions
299
+ : props.selectedDays.length > 0
300
+ ? dayFilteredSessions
301
+ : sortedSessions;
302
+ const activeAggregates = buildAggregatesFromSessions(aggregateSessions, props.aggregates);
303
+
304
+ // Filter daily chart data if sessions are selected
305
+ const filteredDaily =
306
+ props.selectedSessions.length > 0
307
+ ? (() => {
308
+ const selectedEntries = filteredSessions.filter((s) =>
309
+ props.selectedSessions.includes(s.key),
310
+ );
311
+ const allActivityDates = new Set<string>();
312
+ for (const entry of selectedEntries) {
313
+ for (const date of entry.usage?.activityDates ?? []) {
314
+ allActivityDates.add(date);
315
+ }
316
+ }
317
+ return allActivityDates.size > 0
318
+ ? props.costDaily.filter((d) => allActivityDates.has(d.date))
319
+ : props.costDaily;
320
+ })()
321
+ : props.costDaily;
322
+
323
+ const insightStats = buildUsageInsightStats(aggregateSessions, displayTotals, activeAggregates);
324
+ const isEmpty = !props.loading && !props.totals && props.sessions.length === 0;
325
+ const hasMissingCost =
326
+ (displayTotals?.missingCostEntries ?? 0) > 0 ||
327
+ (displayTotals
328
+ ? displayTotals.totalTokens > 0 &&
329
+ displayTotals.totalCost === 0 &&
330
+ displayTotals.input +
331
+ displayTotals.output +
332
+ displayTotals.cacheRead +
333
+ displayTotals.cacheWrite >
334
+ 0
335
+ : false);
336
+ const datePresets = [
337
+ { label: "Today", days: 1 },
338
+ { label: "7d", days: 7 },
339
+ { label: "30d", days: 30 },
340
+ ];
341
+ const applyPreset = (days: number) => {
342
+ const end = new Date();
343
+ const start = new Date();
344
+ start.setDate(start.getDate() - (days - 1));
345
+ props.onStartDateChange(formatIsoDate(start));
346
+ props.onEndDateChange(formatIsoDate(end));
347
+ };
348
+ const renderFilterSelect = (key: string, label: string, options: string[]) => {
349
+ if (options.length === 0) {
350
+ return nothing;
351
+ }
352
+ const selected = selectedValuesFor(key);
353
+ const selectedSet = new Set(selected.map((value) => normalizeQueryText(value)));
354
+ const allSelected =
355
+ options.length > 0 && options.every((value) => selectedSet.has(normalizeQueryText(value)));
356
+ const selectedCount = selected.length;
357
+ return html`
358
+ <details
359
+ class="usage-filter-select"
360
+ @toggle=${(e: Event) => {
361
+ const el = e.currentTarget as HTMLDetailsElement;
362
+ if (!el.open) {
363
+ return;
364
+ }
365
+ const onClick = (ev: MouseEvent) => {
366
+ const path = ev.composedPath();
367
+ if (!path.includes(el)) {
368
+ el.open = false;
369
+ window.removeEventListener("click", onClick, true);
370
+ }
371
+ };
372
+ window.addEventListener("click", onClick, true);
373
+ }}
374
+ >
375
+ <summary>
376
+ <span>${label}</span>
377
+ ${
378
+ selectedCount > 0
379
+ ? html`<span class="usage-filter-badge">${selectedCount}</span>`
380
+ : html`
381
+ <span class="usage-filter-badge">All</span>
382
+ `
383
+ }
384
+ </summary>
385
+ <div class="usage-filter-popover">
386
+ <div class="usage-filter-actions">
387
+ <button
388
+ class="btn btn-sm"
389
+ @click=${(e: Event) => {
390
+ e.preventDefault();
391
+ e.stopPropagation();
392
+ props.onQueryDraftChange(setQueryTokensForKey(props.queryDraft, key, options));
393
+ }}
394
+ ?disabled=${allSelected}
395
+ >
396
+ Select All
397
+ </button>
398
+ <button
399
+ class="btn btn-sm"
400
+ @click=${(e: Event) => {
401
+ e.preventDefault();
402
+ e.stopPropagation();
403
+ props.onQueryDraftChange(setQueryTokensForKey(props.queryDraft, key, []));
404
+ }}
405
+ ?disabled=${selectedCount === 0}
406
+ >
407
+ Clear
408
+ </button>
409
+ </div>
410
+ <div class="usage-filter-options">
411
+ ${options.map((value) => {
412
+ const checked = selectedSet.has(normalizeQueryText(value));
413
+ return html`
414
+ <label class="usage-filter-option">
415
+ <input
416
+ type="checkbox"
417
+ .checked=${checked}
418
+ @change=${(e: Event) => {
419
+ const target = e.target as HTMLInputElement;
420
+ const token = `${key}:${value}`;
421
+ props.onQueryDraftChange(
422
+ target.checked
423
+ ? addQueryToken(props.queryDraft, token)
424
+ : removeQueryToken(props.queryDraft, token),
425
+ );
426
+ }}
427
+ />
428
+ <span>${value}</span>
429
+ </label>
430
+ `;
431
+ })}
432
+ </div>
433
+ </div>
434
+ </details>
435
+ `;
436
+ };
437
+ const exportStamp = formatIsoDate(new Date());
438
+
439
+ return html`
440
+ <style>${usageStylesString}</style>
441
+
442
+ <section class="usage-page-header">
443
+ <div class="usage-page-title">Usage</div>
444
+ <div class="usage-page-subtitle">See where tokens go, when sessions spike, and what drives cost.</div>
445
+ </section>
446
+
447
+ <section class="card usage-header ${props.headerPinned ? "pinned" : ""}">
448
+ <div class="usage-header-row">
449
+ <div class="usage-header-title">
450
+ <div class="card-title" style="margin: 0;">Filters</div>
451
+ ${
452
+ props.loading
453
+ ? html`
454
+ <span class="usage-refresh-indicator">Loading</span>
455
+ `
456
+ : nothing
457
+ }
458
+ ${
459
+ isEmpty
460
+ ? html`
461
+ <span class="usage-query-hint">Select a date range and click Refresh to load usage.</span>
462
+ `
463
+ : nothing
464
+ }
465
+ </div>
466
+ <div class="usage-header-metrics">
467
+ ${
468
+ displayTotals
469
+ ? html`
470
+ <span class="usage-metric-badge">
471
+ <strong>${formatTokens(displayTotals.totalTokens)}</strong> tokens
472
+ </span>
473
+ <span class="usage-metric-badge">
474
+ <strong>${formatCost(displayTotals.totalCost)}</strong> cost
475
+ </span>
476
+ <span class="usage-metric-badge">
477
+ <strong>${displaySessionCount}</strong>
478
+ session${displaySessionCount !== 1 ? "s" : ""}
479
+ </span>
480
+ `
481
+ : nothing
482
+ }
483
+ <button
484
+ class="usage-pin-btn ${props.headerPinned ? "active" : ""}"
485
+ title=${props.headerPinned ? "Unpin filters" : "Pin filters"}
486
+ @click=${props.onToggleHeaderPinned}
487
+ >
488
+ ${props.headerPinned ? "Pinned" : "Pin"}
489
+ </button>
490
+ <details
491
+ class="usage-export-menu"
492
+ @toggle=${(e: Event) => {
493
+ const el = e.currentTarget as HTMLDetailsElement;
494
+ if (!el.open) {
495
+ return;
496
+ }
497
+ const onClick = (ev: MouseEvent) => {
498
+ const path = ev.composedPath();
499
+ if (!path.includes(el)) {
500
+ el.open = false;
501
+ window.removeEventListener("click", onClick, true);
502
+ }
503
+ };
504
+ window.addEventListener("click", onClick, true);
505
+ }}
506
+ >
507
+ <summary class="usage-export-button">Export ▾</summary>
508
+ <div class="usage-export-popover">
509
+ <div class="usage-export-list">
510
+ <button
511
+ class="usage-export-item"
512
+ @click=${() =>
513
+ downloadTextFile(
514
+ `openclaw-usage-sessions-${exportStamp}.csv`,
515
+ buildSessionsCsv(filteredSessions),
516
+ "text/csv",
517
+ )}
518
+ ?disabled=${filteredSessions.length === 0}
519
+ >
520
+ Sessions CSV
521
+ </button>
522
+ <button
523
+ class="usage-export-item"
524
+ @click=${() =>
525
+ downloadTextFile(
526
+ `openclaw-usage-daily-${exportStamp}.csv`,
527
+ buildDailyCsv(filteredDaily),
528
+ "text/csv",
529
+ )}
530
+ ?disabled=${filteredDaily.length === 0}
531
+ >
532
+ Daily CSV
533
+ </button>
534
+ <button
535
+ class="usage-export-item"
536
+ @click=${() =>
537
+ downloadTextFile(
538
+ `openclaw-usage-${exportStamp}.json`,
539
+ JSON.stringify(
540
+ {
541
+ totals: displayTotals,
542
+ sessions: filteredSessions,
543
+ daily: filteredDaily,
544
+ aggregates: activeAggregates,
545
+ },
546
+ null,
547
+ 2,
548
+ ),
549
+ "application/json",
550
+ )}
551
+ ?disabled=${filteredSessions.length === 0 && filteredDaily.length === 0}
552
+ >
553
+ JSON
554
+ </button>
555
+ </div>
556
+ </div>
557
+ </details>
558
+ </div>
559
+ </div>
560
+ <div class="usage-header-row">
561
+ <div class="usage-controls">
562
+ ${renderFilterChips(
563
+ props.selectedDays,
564
+ props.selectedHours,
565
+ props.selectedSessions,
566
+ props.sessions,
567
+ props.onClearDays,
568
+ props.onClearHours,
569
+ props.onClearSessions,
570
+ props.onClearFilters,
571
+ )}
572
+ <div class="usage-presets">
573
+ ${datePresets.map(
574
+ (preset) => html`
575
+ <button class="btn btn-sm" @click=${() => applyPreset(preset.days)}>
576
+ ${preset.label}
577
+ </button>
578
+ `,
579
+ )}
580
+ </div>
581
+ <input
582
+ type="date"
583
+ .value=${props.startDate}
584
+ title="Start Date"
585
+ @change=${(e: Event) => props.onStartDateChange((e.target as HTMLInputElement).value)}
586
+ />
587
+ <span style="color: var(--muted);">to</span>
588
+ <input
589
+ type="date"
590
+ .value=${props.endDate}
591
+ title="End Date"
592
+ @change=${(e: Event) => props.onEndDateChange((e.target as HTMLInputElement).value)}
593
+ />
594
+ <select
595
+ title="Time zone"
596
+ .value=${props.timeZone}
597
+ @change=${(e: Event) =>
598
+ props.onTimeZoneChange((e.target as HTMLSelectElement).value as "local" | "utc")}
599
+ >
600
+ <option value="local">Local</option>
601
+ <option value="utc">UTC</option>
602
+ </select>
603
+ <div class="chart-toggle">
604
+ <button
605
+ class="toggle-btn ${isTokenMode ? "active" : ""}"
606
+ @click=${() => props.onChartModeChange("tokens")}
607
+ >
608
+ Tokens
609
+ </button>
610
+ <button
611
+ class="toggle-btn ${!isTokenMode ? "active" : ""}"
612
+ @click=${() => props.onChartModeChange("cost")}
613
+ >
614
+ Cost
615
+ </button>
616
+ </div>
617
+ <button
618
+ class="btn btn-sm usage-action-btn usage-primary-btn"
619
+ @click=${props.onRefresh}
620
+ ?disabled=${props.loading}
621
+ >
622
+ Refresh
623
+ </button>
624
+ </div>
625
+
626
+ </div>
627
+
628
+ <div style="margin-top: 12px;">
629
+ <div class="usage-query-bar">
630
+ <input
631
+ class="usage-query-input"
632
+ type="text"
633
+ .value=${props.queryDraft}
634
+ placeholder="Filter sessions (e.g. key:agent:main:cron* model:gpt-4o has:errors minTokens:2000)"
635
+ @input=${(e: Event) => props.onQueryDraftChange((e.target as HTMLInputElement).value)}
636
+ @keydown=${(e: KeyboardEvent) => {
637
+ if (e.key === "Enter") {
638
+ e.preventDefault();
639
+ props.onApplyQuery();
640
+ }
641
+ }}
642
+ />
643
+ <div class="usage-query-actions">
644
+ <button
645
+ class="btn btn-sm usage-action-btn usage-secondary-btn"
646
+ @click=${props.onApplyQuery}
647
+ ?disabled=${props.loading || (!hasDraftQuery && !hasQuery)}
648
+ >
649
+ Filter (client-side)
650
+ </button>
651
+ ${
652
+ hasDraftQuery || hasQuery
653
+ ? html`<button class="btn btn-sm usage-action-btn usage-secondary-btn" @click=${props.onClearQuery}>Clear</button>`
654
+ : nothing
655
+ }
656
+ <span class="usage-query-hint">
657
+ ${
658
+ hasQuery
659
+ ? `${filteredSessions.length} of ${totalSessions} sessions match`
660
+ : `${totalSessions} sessions in range`
661
+ }
662
+ </span>
663
+ </div>
664
+ </div>
665
+ <div class="usage-filter-row">
666
+ ${renderFilterSelect("agent", "Agent", agentOptions)}
667
+ ${renderFilterSelect("channel", "Channel", channelOptions)}
668
+ ${renderFilterSelect("provider", "Provider", providerOptions)}
669
+ ${renderFilterSelect("model", "Model", modelOptions)}
670
+ ${renderFilterSelect("tool", "Tool", toolOptions)}
671
+ <span class="usage-query-hint">
672
+ Tip: use filters or click bars to filter days.
673
+ </span>
674
+ </div>
675
+ ${
676
+ queryTerms.length > 0
677
+ ? html`
678
+ <div class="usage-query-chips">
679
+ ${queryTerms.map((term) => {
680
+ const label = term.raw;
681
+ return html`
682
+ <span class="usage-query-chip">
683
+ ${label}
684
+ <button
685
+ title="Remove filter"
686
+ @click=${() =>
687
+ props.onQueryDraftChange(removeQueryToken(props.queryDraft, label))}
688
+ >
689
+ ×
690
+ </button>
691
+ </span>
692
+ `;
693
+ })}
694
+ </div>
695
+ `
696
+ : nothing
697
+ }
698
+ ${
699
+ querySuggestions.length > 0
700
+ ? html`
701
+ <div class="usage-query-suggestions">
702
+ ${querySuggestions.map(
703
+ (suggestion) => html`
704
+ <button
705
+ class="usage-query-suggestion"
706
+ @click=${() =>
707
+ props.onQueryDraftChange(
708
+ applySuggestionToQuery(props.queryDraft, suggestion.value),
709
+ )}
710
+ >
711
+ ${suggestion.label}
712
+ </button>
713
+ `,
714
+ )}
715
+ </div>
716
+ `
717
+ : nothing
718
+ }
719
+ ${
720
+ queryWarnings.length > 0
721
+ ? html`
722
+ <div class="callout warning" style="margin-top: 8px;">
723
+ ${queryWarnings.join(" · ")}
724
+ </div>
725
+ `
726
+ : nothing
727
+ }
728
+ </div>
729
+
730
+ ${
731
+ props.error
732
+ ? html`<div class="callout danger" style="margin-top: 12px;">${props.error}</div>`
733
+ : nothing
734
+ }
735
+
736
+ ${
737
+ props.sessionsLimitReached
738
+ ? html`
739
+ <div class="callout warning" style="margin-top: 12px">
740
+ Showing first 1,000 sessions. Narrow date range for complete results.
741
+ </div>
742
+ `
743
+ : nothing
744
+ }
745
+ </section>
746
+
747
+ ${renderUsageInsights(
748
+ displayTotals,
749
+ activeAggregates,
750
+ insightStats,
751
+ hasMissingCost,
752
+ buildPeakErrorHours(aggregateSessions, props.timeZone),
753
+ displaySessionCount,
754
+ totalSessions,
755
+ )}
756
+
757
+ ${renderUsageMosaic(aggregateSessions, props.timeZone, props.selectedHours, props.onSelectHour)}
758
+
759
+ <!-- Two-column layout: Daily+Breakdown on left, Sessions on right -->
760
+ <div class="usage-grid">
761
+ <div class="usage-grid-left">
762
+ <div class="card usage-left-card">
763
+ ${renderDailyChartCompact(
764
+ filteredDaily,
765
+ props.selectedDays,
766
+ props.chartMode,
767
+ props.dailyChartMode,
768
+ props.onDailyChartModeChange,
769
+ props.onSelectDay,
770
+ )}
771
+ ${displayTotals ? renderCostBreakdownCompact(displayTotals, props.chartMode) : nothing}
772
+ </div>
773
+ </div>
774
+ <div class="usage-grid-right">
775
+ ${renderSessionsCard(
776
+ filteredSessions,
777
+ props.selectedSessions,
778
+ props.selectedDays,
779
+ isTokenMode,
780
+ props.sessionSort,
781
+ props.sessionSortDir,
782
+ props.recentSessions,
783
+ props.sessionsTab,
784
+ props.onSelectSession,
785
+ props.onSessionSortChange,
786
+ props.onSessionSortDirChange,
787
+ props.onSessionsTabChange,
788
+ props.visibleColumns,
789
+ totalSessions,
790
+ props.onClearSessions,
791
+ )}
792
+ </div>
793
+ </div>
794
+
795
+ <!-- Session Detail Panel (when selected) or Empty State -->
796
+ ${
797
+ primarySelectedEntry
798
+ ? renderSessionDetailPanel(
799
+ primarySelectedEntry,
800
+ props.timeSeries,
801
+ props.timeSeriesLoading,
802
+ props.timeSeriesMode,
803
+ props.onTimeSeriesModeChange,
804
+ props.timeSeriesBreakdownMode,
805
+ props.onTimeSeriesBreakdownChange,
806
+ props.timeSeriesCursorStart,
807
+ props.timeSeriesCursorEnd,
808
+ props.onTimeSeriesCursorRangeChange,
809
+ props.startDate,
810
+ props.endDate,
811
+ props.selectedDays,
812
+ props.sessionLogs,
813
+ props.sessionLogsLoading,
814
+ props.sessionLogsExpanded,
815
+ props.onToggleSessionLogsExpanded,
816
+ {
817
+ roles: props.logFilterRoles,
818
+ tools: props.logFilterTools,
819
+ hasTools: props.logFilterHasTools,
820
+ query: props.logFilterQuery,
821
+ },
822
+ props.onLogFilterRolesChange,
823
+ props.onLogFilterToolsChange,
824
+ props.onLogFilterHasToolsChange,
825
+ props.onLogFilterQueryChange,
826
+ props.onLogFilterClear,
827
+ props.contextExpanded,
828
+ props.onToggleContextExpanded,
829
+ props.onClearSessions,
830
+ )
831
+ : renderEmptyDetailState()
832
+ }
833
+ `;
834
+ }
835
+
836
+ // Exposed for Playwright/Vitest browser unit tests.