@songsid/agend 2.3.0-beta.2 → 2.3.0-beta.20

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 (502) hide show
  1. package/README.md +7 -1
  2. package/README.zh-TW.md +7 -1
  3. package/dist/agent-cli-instructions.md +9 -2
  4. package/dist/agent-cli.js +63 -2
  5. package/dist/agent-cli.js.map +1 -1
  6. package/dist/agent-endpoint.js +4 -0
  7. package/dist/agent-endpoint.js.map +1 -1
  8. package/dist/agent-file-read.d.ts +4 -1
  9. package/dist/agent-file-read.js +189 -16
  10. package/dist/agent-file-read.js.map +1 -1
  11. package/dist/agent-whats-new.d.ts +11 -0
  12. package/dist/agent-whats-new.js +29 -5
  13. package/dist/agent-whats-new.js.map +1 -1
  14. package/dist/auth-api.d.ts +30 -1
  15. package/dist/auth-api.js +45 -13
  16. package/dist/auth-api.js.map +1 -1
  17. package/dist/authority/authority-state.d.ts +38 -0
  18. package/dist/authority/authority-state.js +114 -0
  19. package/dist/authority/authority-state.js.map +1 -0
  20. package/dist/authority/session-authorizations.d.ts +43 -0
  21. package/dist/authority/session-authorizations.js +102 -0
  22. package/dist/authority/session-authorizations.js.map +1 -0
  23. package/dist/authority/web-authority.d.ts +120 -0
  24. package/dist/authority/web-authority.js +157 -0
  25. package/dist/authority/web-authority.js.map +1 -0
  26. package/dist/backend/claude-code.d.ts +2 -2
  27. package/dist/backend/claude-code.js +18 -8
  28. package/dist/backend/claude-code.js.map +1 -1
  29. package/dist/backend/codex-metadata.d.ts +8 -0
  30. package/dist/backend/codex-metadata.js +74 -17
  31. package/dist/backend/codex-metadata.js.map +1 -1
  32. package/dist/backend/codex.js +15 -2
  33. package/dist/backend/codex.js.map +1 -1
  34. package/dist/backend/credential-profile.d.ts +2 -0
  35. package/dist/backend/credential-profile.js +2 -0
  36. package/dist/backend/credential-profile.js.map +1 -1
  37. package/dist/backend/types.d.ts +6 -0
  38. package/dist/backend/types.js +15 -0
  39. package/dist/backend/types.js.map +1 -1
  40. package/dist/backend-availability.d.ts +120 -0
  41. package/dist/backend-availability.js +396 -0
  42. package/dist/backend-availability.js.map +1 -0
  43. package/dist/backend-command.d.ts +23 -3
  44. package/dist/backend-command.js +25 -4
  45. package/dist/backend-command.js.map +1 -1
  46. package/dist/bug-report-cli.d.ts +30 -0
  47. package/dist/bug-report-cli.js +138 -0
  48. package/dist/bug-report-cli.js.map +1 -0
  49. package/dist/bug-report.d.ts +47 -0
  50. package/dist/bug-report.js +136 -0
  51. package/dist/bug-report.js.map +1 -0
  52. package/dist/bundled-skills.d.ts +14 -0
  53. package/dist/bundled-skills.js +29 -0
  54. package/dist/bundled-skills.js.map +1 -0
  55. package/dist/cache-ledger.d.ts +16 -3
  56. package/dist/cache-ledger.js +33 -13
  57. package/dist/cache-ledger.js.map +1 -1
  58. package/dist/cache-service.d.ts +55 -3
  59. package/dist/cache-service.js +152 -16
  60. package/dist/cache-service.js.map +1 -1
  61. package/dist/channel/adapters/discord-client-retirement.d.ts +5 -0
  62. package/dist/channel/adapters/discord-client-retirement.js +57 -0
  63. package/dist/channel/adapters/discord-client-retirement.js.map +1 -0
  64. package/dist/channel/adapters/discord.d.ts +9 -0
  65. package/dist/channel/adapters/discord.js +49 -4
  66. package/dist/channel/adapters/discord.js.map +1 -1
  67. package/dist/channel/mcp-tools.js +22 -11
  68. package/dist/channel/mcp-tools.js.map +1 -1
  69. package/dist/channel/types.d.ts +6 -0
  70. package/dist/classic-channel-manager.d.ts +10 -5
  71. package/dist/classic-channel-manager.js +12 -9
  72. package/dist/classic-channel-manager.js.map +1 -1
  73. package/dist/cli.js +224 -111
  74. package/dist/cli.js.map +1 -1
  75. package/dist/config-validator.d.ts +6 -0
  76. package/dist/config-validator.js +129 -3
  77. package/dist/config-validator.js.map +1 -1
  78. package/dist/config.d.ts +13 -1
  79. package/dist/config.js +14 -5
  80. package/dist/config.js.map +1 -1
  81. package/dist/cross-instance-notice.d.ts +7 -0
  82. package/dist/cross-instance-notice.js +10 -0
  83. package/dist/cross-instance-notice.js.map +1 -1
  84. package/dist/daemon.d.ts +24 -1
  85. package/dist/daemon.js +151 -19
  86. package/dist/daemon.js.map +1 -1
  87. package/dist/decision-list.d.ts +25 -0
  88. package/dist/decision-list.js +30 -0
  89. package/dist/decision-list.js.map +1 -0
  90. package/dist/delivery-outbox.d.ts +10 -0
  91. package/dist/delivery-outbox.js +17 -1
  92. package/dist/delivery-outbox.js.map +1 -1
  93. package/dist/diagnostic-redaction.d.ts +10 -0
  94. package/dist/diagnostic-redaction.js +167 -0
  95. package/dist/diagnostic-redaction.js.map +1 -0
  96. package/dist/fleet-context.d.ts +10 -0
  97. package/dist/fleet-manager.d.ts +505 -15
  98. package/dist/fleet-manager.js +1870 -204
  99. package/dist/fleet-manager.js.map +1 -1
  100. package/dist/fleet-process-handlers.d.ts +18 -0
  101. package/dist/fleet-process-handlers.js +69 -0
  102. package/dist/fleet-process-handlers.js.map +1 -0
  103. package/dist/fleet-traffic.d.ts +74 -0
  104. package/dist/fleet-traffic.js +196 -0
  105. package/dist/fleet-traffic.js.map +1 -0
  106. package/dist/general-knowledge/skills/bug-report/SKILL.md +31 -0
  107. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +17 -6
  108. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
  109. package/dist/general-knowledge/skills/fleet-config/SKILL.md +2 -0
  110. package/dist/general-knowledge/skills/multi-channel/SKILL.md +3 -2
  111. package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
  112. package/dist/general-knowledge/skills/scheduling/SKILL.md +4 -0
  113. package/dist/general-knowledge/skills/web-appearance/SKILL.md +190 -47
  114. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +545 -0
  115. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
  116. package/dist/human-delivery-receipts.d.ts +24 -1
  117. package/dist/human-delivery-receipts.js +64 -11
  118. package/dist/human-delivery-receipts.js.map +1 -1
  119. package/dist/instance-config-impact.js +7 -1
  120. package/dist/instance-config-impact.js.map +1 -1
  121. package/dist/instance-lifecycle.d.ts +10 -1
  122. package/dist/instance-lifecycle.js +79 -18
  123. package/dist/instance-lifecycle.js.map +1 -1
  124. package/dist/instance-names.d.ts +4 -0
  125. package/dist/instance-names.js +12 -0
  126. package/dist/instance-names.js.map +1 -0
  127. package/dist/instructions.d.ts +12 -1
  128. package/dist/instructions.js +13 -2
  129. package/dist/instructions.js.map +1 -1
  130. package/dist/locale.js +173 -12
  131. package/dist/locale.js.map +1 -1
  132. package/dist/log-steps.d.ts +1 -0
  133. package/dist/log-steps.js +15 -0
  134. package/dist/log-steps.js.map +1 -1
  135. package/dist/login-controller.d.ts +45 -0
  136. package/dist/login-controller.js +101 -5
  137. package/dist/login-controller.js.map +1 -1
  138. package/dist/ls-attach-resolve.d.ts +96 -0
  139. package/dist/ls-attach-resolve.js +180 -0
  140. package/dist/ls-attach-resolve.js.map +1 -0
  141. package/dist/ls-rows.d.ts +12 -0
  142. package/dist/ls-rows.js +82 -3
  143. package/dist/ls-rows.js.map +1 -1
  144. package/dist/memory-hogs.d.ts +27 -0
  145. package/dist/memory-hogs.js +124 -0
  146. package/dist/memory-hogs.js.map +1 -0
  147. package/dist/needs-you.d.ts +10 -0
  148. package/dist/needs-you.js +2 -1
  149. package/dist/needs-you.js.map +1 -1
  150. package/dist/outbound-handlers.d.ts +11 -0
  151. package/dist/outbound-handlers.js +139 -11
  152. package/dist/outbound-handlers.js.map +1 -1
  153. package/dist/outbound-schemas.d.ts +200 -4
  154. package/dist/outbound-schemas.js +124 -11
  155. package/dist/outbound-schemas.js.map +1 -1
  156. package/dist/public-web-gateway.js +17 -4
  157. package/dist/public-web-gateway.js.map +1 -1
  158. package/dist/public-web-link.d.ts +9 -0
  159. package/dist/public-web-link.js +8 -0
  160. package/dist/public-web-link.js.map +1 -1
  161. package/dist/quickstart-api.d.ts +19 -1
  162. package/dist/quickstart-api.js +94 -4
  163. package/dist/quickstart-api.js.map +1 -1
  164. package/dist/reply-buttons-controller.d.ts +4 -0
  165. package/dist/reply-buttons-controller.js +2 -2
  166. package/dist/reply-buttons-controller.js.map +1 -1
  167. package/dist/restart-progress.d.ts +80 -1
  168. package/dist/restart-progress.js +306 -21
  169. package/dist/restart-progress.js.map +1 -1
  170. package/dist/rollout-index.d.ts +5 -0
  171. package/dist/rollout-index.js +26 -5
  172. package/dist/rollout-index.js.map +1 -1
  173. package/dist/scheduler/db.d.ts +40 -13
  174. package/dist/scheduler/db.js +123 -89
  175. package/dist/scheduler/db.js.map +1 -1
  176. package/dist/scheduler/task-board-text.d.ts +26 -0
  177. package/dist/scheduler/task-board-text.js +135 -0
  178. package/dist/scheduler/task-board-text.js.map +1 -0
  179. package/dist/scheduler/task-board.d.ts +650 -0
  180. package/dist/scheduler/task-board.js +1938 -0
  181. package/dist/scheduler/task-board.js.map +1 -0
  182. package/dist/scheduler/types.d.ts +6 -1
  183. package/dist/search/search-docs.d.ts +58 -0
  184. package/dist/search/search-docs.js +63 -0
  185. package/dist/search/search-docs.js.map +1 -0
  186. package/dist/search/search-index.d.ts +141 -0
  187. package/dist/search/search-index.js +440 -0
  188. package/dist/search/search-index.js.map +1 -0
  189. package/dist/search/search-store.d.ts +122 -0
  190. package/dist/search/search-store.js +236 -0
  191. package/dist/search/search-store.js.map +1 -0
  192. package/dist/search/search-worker.d.ts +1 -0
  193. package/dist/search/search-worker.js +163 -0
  194. package/dist/search/search-worker.js.map +1 -0
  195. package/dist/service-installer.js +12 -0
  196. package/dist/service-installer.js.map +1 -1
  197. package/dist/settings-api.d.ts +23 -0
  198. package/dist/settings-api.js +164 -3
  199. package/dist/settings-api.js.map +1 -1
  200. package/dist/settings-change.js +104 -0
  201. package/dist/settings-change.js.map +1 -1
  202. package/dist/settings-confirmation.d.ts +3 -1
  203. package/dist/settings-confirmation.js +2 -0
  204. package/dist/settings-confirmation.js.map +1 -1
  205. package/dist/settings-effect.js +34 -3
  206. package/dist/settings-effect.js.map +1 -1
  207. package/dist/settings-http-confirmation.d.ts +11 -0
  208. package/dist/settings-http-confirmation.js +10 -0
  209. package/dist/settings-http-confirmation.js.map +1 -1
  210. package/dist/setup-form.d.ts +1 -1
  211. package/dist/setup-form.js +23 -7
  212. package/dist/setup-form.js.map +1 -1
  213. package/dist/spawn-gate.d.ts +1 -0
  214. package/dist/spawn-gate.js +13 -3
  215. package/dist/spawn-gate.js.map +1 -1
  216. package/dist/task-board-tool.d.ts +25 -0
  217. package/dist/task-board-tool.js +154 -0
  218. package/dist/task-board-tool.js.map +1 -0
  219. package/dist/task-board-waker.d.ts +44 -0
  220. package/dist/task-board-waker.js +128 -0
  221. package/dist/task-board-waker.js.map +1 -0
  222. package/dist/tmux-exec-helper.d.ts +94 -0
  223. package/dist/tmux-exec-helper.js +407 -0
  224. package/dist/tmux-exec-helper.js.map +1 -0
  225. package/dist/tmux-manager.d.ts +5 -0
  226. package/dist/tmux-manager.js +42 -11
  227. package/dist/tmux-manager.js.map +1 -1
  228. package/dist/tool-permissions.d.ts +0 -7
  229. package/dist/tool-permissions.js +44 -5
  230. package/dist/tool-permissions.js.map +1 -1
  231. package/dist/topic-commands.d.ts +17 -1
  232. package/dist/topic-commands.js +45 -19
  233. package/dist/topic-commands.js.map +1 -1
  234. package/dist/tunnel/cloudflared.js +37 -2
  235. package/dist/tunnel/cloudflared.js.map +1 -1
  236. package/dist/tunnel/types.d.ts +3 -1
  237. package/dist/tunnel/types.js.map +1 -1
  238. package/dist/types.d.ts +63 -1
  239. package/dist/ui/app-effects.js +90 -32
  240. package/dist/ui/backend-signin.js +297 -0
  241. package/dist/ui/chat-ambient.js +307 -0
  242. package/dist/ui/chat-effect-posters.js +8 -8
  243. package/dist/ui/chat-effects.js +110 -15
  244. package/dist/ui/chat-msg-actions.js +176 -0
  245. package/dist/ui/chat-render.js +21 -3
  246. package/dist/ui/chat-store.js +64 -12
  247. package/dist/ui/chat-strings.js +85 -8
  248. package/dist/ui/chat-thread.js +204 -21
  249. package/dist/ui/effect-trial-controller.js +13 -8
  250. package/dist/ui/effect-trial-panel.js +19 -16
  251. package/dist/ui/effects-waker.js +70 -0
  252. package/dist/ui/fleet-canvas-layout.js +117 -0
  253. package/dist/ui/fleet-canvas.js +555 -0
  254. package/dist/ui/fleet-org.js +27 -12
  255. package/dist/ui/fleet-route-scene.js +207 -0
  256. package/dist/ui/fleet-route-worker.js +21 -0
  257. package/dist/ui/fleet-route.js +88 -0
  258. package/dist/ui/fleet-task-card.js +436 -0
  259. package/dist/ui/fleet-task-common.js +448 -0
  260. package/dist/ui/fleet-tasks.js +535 -0
  261. package/dist/ui/fleet-traffic.js +359 -0
  262. package/dist/ui/fleet-usage.js +435 -0
  263. package/dist/ui/panel-chat.js +264 -66
  264. package/dist/ui/panel-details.js +2 -1
  265. package/dist/ui/panel-fleet.js +65 -83
  266. package/dist/ui/panel-needs.js +4 -2
  267. package/dist/ui/panel-settings.js +56 -11
  268. package/dist/ui/preview.js +3 -2
  269. package/dist/ui/search-dialog.js +349 -0
  270. package/dist/ui/settings-appearance.js +44 -19
  271. package/dist/ui/settings-apply.js +3 -3
  272. package/dist/ui/settings-catalog.js +92 -0
  273. package/dist/ui/settings-confirm.js +7 -2
  274. package/dist/ui/settings-dialogs.js +40 -12
  275. package/dist/ui/settings-effects.js +4 -3
  276. package/dist/ui/settings-guests.js +117 -0
  277. package/dist/ui/settings-learn.js +25 -0
  278. package/dist/ui/settings-model.js +15 -6
  279. package/dist/ui/settings-mods.js +23 -9
  280. package/dist/ui/settings-owner-effects.js +15 -13
  281. package/dist/ui/settings-strings.js +122 -42
  282. package/dist/ui/settings-web-authority.js +91 -0
  283. package/dist/ui/shared/agend-auth.js +1 -1
  284. package/dist/ui/shared/app-i18n.js +11 -2
  285. package/dist/ui/shared/app-prefs.js +17 -4
  286. package/dist/ui/shared/app-route.js +19 -1
  287. package/dist/ui/shared/app-session.js +2 -0
  288. package/dist/ui/shared/app-shell.js +174 -30
  289. package/dist/ui/shared/app-store.js +26 -1
  290. package/dist/ui/shared/app-stream.js +5 -1
  291. package/dist/ui/shared/app-themes.js +140 -7
  292. package/dist/ui/shared/app.css +704 -20
  293. package/dist/ui/shared/app.js +26 -15
  294. package/dist/ui/shared/card-strings.js +8 -0
  295. package/dist/ui/shared/effect-app-runtime.js +68 -19
  296. package/dist/ui/shared/effect-chat-runtime.js +16 -3
  297. package/dist/ui/shared/effect-frame.js +26 -7
  298. package/dist/ui/shared/effect-geometry.js +24 -9
  299. package/dist/ui/shared/effect-owner-source.js +8 -1
  300. package/dist/ui/shared/effect-policy.js +21 -7
  301. package/dist/ui/shared/effect-profiler.js +31 -9
  302. package/dist/ui/shared/effect-registry.js +97 -50
  303. package/dist/ui/shared/effect-stock-snapshot.js +9 -0
  304. package/dist/ui/shared/effect-stock-trust.js +62 -10
  305. package/dist/ui/shared/effect-trial-shadow.js +62 -0
  306. package/dist/ui/shared/effect-trial-source.js +1 -1
  307. package/dist/ui/shared/effect-trial-stage.js +54 -31
  308. package/dist/ui/shared/effect-trial.css +40 -0
  309. package/dist/ui/shared/fx-motion.js +67 -0
  310. package/dist/ui/shared/identity-card.js +100 -11
  311. package/dist/ui/shared/identity-color.js +14 -10
  312. package/dist/ui/shared/instance-nav.js +179 -23
  313. package/dist/ui/shared/owner-css.js +30 -1
  314. package/dist/ui/shared/panel-view.js +24 -4
  315. package/dist/ui/shared/tokens.css +9 -3
  316. package/dist/ui/shared/ui-icons.js +2 -0
  317. package/dist/ui/shared/ui-menu.js +20 -7
  318. package/dist/ui/shared/ui-toast.js +16 -3
  319. package/dist/ui/shared/usage-dialog.js +34 -3
  320. package/dist/ui/shared/view-strings.js +72 -0
  321. package/dist/ui/shared/web-authorize.js +198 -0
  322. package/dist/ui/web-terminal/terminal.css +4 -4
  323. package/dist/ui/web-terminal/terminal.html +1 -1
  324. package/dist/update-launch.d.ts +9 -0
  325. package/dist/update-launch.js +38 -12
  326. package/dist/update-launch.js.map +1 -1
  327. package/dist/usage/format-rich.js +6 -3
  328. package/dist/usage/format-rich.js.map +1 -1
  329. package/dist/usage/i18n-keys.d.ts +1 -1
  330. package/dist/usage/i18n-keys.js +2 -1
  331. package/dist/usage/i18n-keys.js.map +1 -1
  332. package/dist/usage/i18n.d.ts +13 -1
  333. package/dist/usage/i18n.js +29 -0
  334. package/dist/usage/i18n.js.map +1 -1
  335. package/dist/usage/providers.d.ts +81 -2
  336. package/dist/usage/providers.js +240 -38
  337. package/dist/usage/providers.js.map +1 -1
  338. package/dist/usage/usage-api.d.ts +5 -1
  339. package/dist/usage/usage-api.js +184 -58
  340. package/dist/usage/usage-api.js.map +1 -1
  341. package/dist/usage-report.d.ts +158 -0
  342. package/dist/usage-report.js +226 -0
  343. package/dist/usage-report.js.map +1 -0
  344. package/dist/usage-rollup.d.ts +51 -0
  345. package/dist/usage-rollup.js +168 -0
  346. package/dist/usage-rollup.js.map +1 -0
  347. package/dist/user-systemd.d.ts +24 -0
  348. package/dist/user-systemd.js +75 -0
  349. package/dist/user-systemd.js.map +1 -0
  350. package/dist/view-api.d.ts +39 -0
  351. package/dist/view-api.js +69 -6
  352. package/dist/view-api.js.map +1 -1
  353. package/dist/web-ambient.d.ts +72 -0
  354. package/dist/web-ambient.js +161 -0
  355. package/dist/web-ambient.js.map +1 -0
  356. package/dist/web-api.d.ts +98 -14
  357. package/dist/web-api.js +1027 -83
  358. package/dist/web-api.js.map +1 -1
  359. package/dist/web-appearance-policy.d.ts +4 -6
  360. package/dist/web-appearance-policy.js +5 -12
  361. package/dist/web-appearance-policy.js.map +1 -1
  362. package/dist/web-appearance-tools.d.ts +3 -0
  363. package/dist/web-appearance-tools.js +22 -3
  364. package/dist/web-appearance-tools.js.map +1 -1
  365. package/dist/web-appearance-view.d.ts +27 -2
  366. package/dist/web-appearance-view.js +129 -14
  367. package/dist/web-appearance-view.js.map +1 -1
  368. package/dist/web-appearance.d.ts +17 -3
  369. package/dist/web-appearance.js +85 -8
  370. package/dist/web-appearance.js.map +1 -1
  371. package/dist/web-asset-parts.d.ts +2 -2
  372. package/dist/web-asset-parts.js +3 -3
  373. package/dist/web-asset-parts.js.map +1 -1
  374. package/dist/web-auth.js +3 -1
  375. package/dist/web-auth.js.map +1 -1
  376. package/dist/web-backend-login.d.ts +130 -0
  377. package/dist/web-backend-login.js +225 -0
  378. package/dist/web-backend-login.js.map +1 -0
  379. package/dist/web-card.d.ts +14 -0
  380. package/dist/web-card.js +40 -6
  381. package/dist/web-card.js.map +1 -1
  382. package/dist/web-channel-echo.d.ts +7 -1
  383. package/dist/web-channel-echo.js +6 -2
  384. package/dist/web-channel-echo.js.map +1 -1
  385. package/dist/web-chat-history.d.ts +83 -0
  386. package/dist/web-chat-history.js +137 -5
  387. package/dist/web-chat-history.js.map +1 -1
  388. package/dist/web-commands.d.ts +6 -1
  389. package/dist/web-commands.js +1 -1
  390. package/dist/web-commands.js.map +1 -1
  391. package/dist/web-effect-bootstrap.d.ts +12 -0
  392. package/dist/web-effect-bootstrap.js +49 -0
  393. package/dist/web-effect-bootstrap.js.map +1 -0
  394. package/dist/web-effect-bundle.js +46 -1
  395. package/dist/web-effect-bundle.js.map +1 -1
  396. package/dist/web-effect-playback.d.ts +30 -0
  397. package/dist/web-effect-playback.js +71 -0
  398. package/dist/web-effect-playback.js.map +1 -0
  399. package/dist/web-effect-profiles.d.ts +4 -4
  400. package/dist/web-effect-service.d.ts +11 -4
  401. package/dist/web-effect-service.js +25 -4
  402. package/dist/web-effect-service.js.map +1 -1
  403. package/dist/web-effect-tools.d.ts +1 -0
  404. package/dist/web-effect-tools.js +4 -4
  405. package/dist/web-effect-tools.js.map +1 -1
  406. package/dist/web-effect-trials.d.ts +2 -2
  407. package/dist/web-effect-trials.js +3 -3
  408. package/dist/web-effect-trials.js.map +1 -1
  409. package/dist/web-effect-upload.js +1 -1
  410. package/dist/web-effect-upload.js.map +1 -1
  411. package/dist/web-effect-worker.d.ts +1 -1
  412. package/dist/web-effect-worker.js +2 -2
  413. package/dist/web-effect-worker.js.map +1 -1
  414. package/dist/web-effect.d.ts +1 -1
  415. package/dist/web-guest-access.d.ts +126 -0
  416. package/dist/web-guest-access.js +238 -0
  417. package/dist/web-guest-access.js.map +1 -0
  418. package/dist/web-guest-rooms.d.ts +82 -0
  419. package/dist/web-guest-rooms.js +135 -0
  420. package/dist/web-guest-rooms.js.map +1 -0
  421. package/dist/web-host-guard.d.ts +11 -0
  422. package/dist/web-host-guard.js +11 -0
  423. package/dist/web-host-guard.js.map +1 -1
  424. package/dist/web-identity-color.d.ts +1 -1
  425. package/dist/web-identity-color.js +2 -2
  426. package/dist/web-identity-color.js.map +1 -1
  427. package/dist/web-identity-palette.d.ts +1 -1
  428. package/dist/web-identity-palette.js +2 -2
  429. package/dist/web-identity-palette.js.map +1 -1
  430. package/dist/web-image-meta.d.ts +13 -0
  431. package/dist/web-image-meta.js +62 -0
  432. package/dist/web-image-meta.js.map +1 -1
  433. package/dist/web-libavoid.d.ts +14 -0
  434. package/dist/web-libavoid.js +46 -0
  435. package/dist/web-libavoid.js.map +1 -0
  436. package/dist/web-login.d.ts +47 -0
  437. package/dist/web-login.js +115 -0
  438. package/dist/web-login.js.map +1 -1
  439. package/dist/web-mod-agent.d.ts +1 -1
  440. package/dist/web-mod-agent.js +7 -5
  441. package/dist/web-mod-agent.js.map +1 -1
  442. package/dist/web-mod-bundle.d.ts +9 -1
  443. package/dist/web-mod-bundle.js +62 -3
  444. package/dist/web-mod-bundle.js.map +1 -1
  445. package/dist/web-mod-export.d.ts +9 -0
  446. package/dist/web-mod-export.js +21 -0
  447. package/dist/web-mod-export.js.map +1 -1
  448. package/dist/web-mod-store.d.ts +7 -1
  449. package/dist/web-mod-store.js +19 -6
  450. package/dist/web-mod-store.js.map +1 -1
  451. package/dist/web-owner-css-store.d.ts +44 -1
  452. package/dist/web-owner-css-store.js +70 -9
  453. package/dist/web-owner-css-store.js.map +1 -1
  454. package/dist/web-prefs.d.ts +14 -0
  455. package/dist/web-prefs.js +37 -4
  456. package/dist/web-prefs.js.map +1 -1
  457. package/dist/web-preview.d.ts +6 -2
  458. package/dist/web-preview.js +4 -2
  459. package/dist/web-preview.js.map +1 -1
  460. package/dist/web-returning-device.d.ts +41 -0
  461. package/dist/web-returning-device.js +57 -5
  462. package/dist/web-returning-device.js.map +1 -1
  463. package/dist/web-session-authorization.d.ts +132 -0
  464. package/dist/web-session-authorization.js +193 -0
  465. package/dist/web-session-authorization.js.map +1 -0
  466. package/dist/web-session.d.ts +70 -10
  467. package/dist/web-session.js +137 -22
  468. package/dist/web-session.js.map +1 -1
  469. package/dist/web-settings-patch.d.ts +49 -2
  470. package/dist/web-settings-patch.js +145 -1
  471. package/dist/web-settings-patch.js.map +1 -1
  472. package/dist/web-settings-tools.d.ts +94 -0
  473. package/dist/web-settings-tools.js +301 -0
  474. package/dist/web-settings-tools.js.map +1 -0
  475. package/dist/web-shell-routes.d.ts +1 -1
  476. package/dist/web-shell-routes.js +1 -1
  477. package/dist/web-shell-routes.js.map +1 -1
  478. package/dist/web-terminal-http.js +15 -2
  479. package/dist/web-terminal-http.js.map +1 -1
  480. package/dist/web-theme-store.d.ts +39 -0
  481. package/dist/web-theme-store.js +122 -11
  482. package/dist/web-theme-store.js.map +1 -1
  483. package/dist/web-theme.d.ts +31 -0
  484. package/dist/web-theme.js +99 -2
  485. package/dist/web-theme.js.map +1 -1
  486. package/dist/web-upload.d.ts +13 -0
  487. package/dist/web-upload.js +39 -3
  488. package/dist/web-upload.js.map +1 -1
  489. package/dist/windows-shortcuts.d.ts +78 -0
  490. package/dist/windows-shortcuts.js +203 -0
  491. package/dist/windows-shortcuts.js.map +1 -0
  492. package/dist/workflow-templates/default.md +4 -1
  493. package/dist/yaml-shared-nodes.d.ts +25 -0
  494. package/dist/yaml-shared-nodes.js +185 -0
  495. package/dist/yaml-shared-nodes.js.map +1 -0
  496. package/launcher/agend-install.cjs +553 -0
  497. package/launcher/install-admission.cjs +4 -39
  498. package/launcher/postinstall.cjs +14 -12
  499. package/package.json +4 -2
  500. package/scripts/preinstall-guard.cjs +2 -5
  501. package/templates/windows-shortcuts.ps1 +181 -0
  502. package/launcher/old-updater-note.cjs +0 -48
@@ -0,0 +1,435 @@
1
+ // #2122: Fleet → Usage (/ui/fleet/usage) — each agent's tokens, requests, delivered messages and their API-equivalent
2
+ // at list price over a range of days, ranked; a daily trend, a calendar heatmap and a CSV of the same answer. Behind the
3
+ // session gate (/ui/js/), imported by the Fleet panel. Read-only: it changes nothing.
4
+ //
5
+ // One GET /ui/usage-report?… per opening and per choice of range or grouping (a person's navigation); no timer re-reads
6
+ // it (#1374). Every amount says what it is: "API-equivalent at list price — not money spent" (design §3).
7
+ import { html, useEffect, useLayoutEffect, useMemo, useRef, useState } from "/assets/app-html.js";
8
+ import { t, register } from "/assets/app-i18n.js";
9
+ import { Empty, ErrorState, Skeleton } from "/assets/ui-states.js";
10
+ import { Icon } from "/assets/ui-icons.js";
11
+ import { money, tokens } from "./fleet-cache.js";
12
+
13
+ register("fusage", {
14
+ en: {
15
+ range: "Range", today: "Today", r7d: "7 days", month: "This month", all: "All", custom: "Custom", from: "From", to: "To", apply: "Apply",
16
+ group: "Group by", g_instance: "Agent", g_backend: "Backend", g_model: "Model", g_profile: "Credential profile", g_team: "Team (as configured now)",
17
+ csv: "Download CSV", basis: "API-equivalent at list price (checked {0}) — not money spent. Subscriptions are not billed per token.",
18
+ amount: "API-equivalent", input: "Input", output: "Output", read: "Cache read", write: "Cache write", total: "Total tokens", requests: "Requests", messages: "Messages",
19
+ notPriced: "not priced", partial: "partial", noTeam: "(no team)", unknown: "unknown",
20
+ f_cache_write_not_reported: "cache writes not reported (Codex) — an estimate", f_not_priced: "not priced: {0}", f_source_unknown: "read before backend and profile were recorded",
21
+ scanning: "Scanning — totals may still grow.", updated: "Updated {0} · read every 10 min", since: "Collected since {0}", notYet: "Not collected yet",
22
+ refresh: "Refresh",
23
+ notMeasured: "Not measured", nm_credit_billed: "Kiro is credit-billed and records no tokens", nm_unsupported: "AgEnD can't read this CLI's usage yet", nm_no_data: "nothing read yet",
24
+ shared: "shares its working directory with {0}; their transcripts can't be told apart",
25
+ empty: "Nothing recorded yet", emptyHint: "Usage is read from Claude Code and Codex transcripts, from when the fleet started.",
26
+ blocked: "Not offered over the public link", blockedHint: "Usage is offered over the public link only when \"Usage report over the public link\" is on in Settings → Web — on the dashboard reached directly, on the computer running AgEnD.",
27
+ openSettings: "Open Settings → Web on that computer",
28
+ partialDays: "Daily detail from {0}: the earlier days in this range are too many rows to send. The rows and totals above cover the whole range; the trend, the heatmap and the CSV start at {0}.",
29
+ csvFrom: "Download CSV (from {0})",
30
+ noDaily: "No daily detail for this range: even its latest day has more rows than one answer carries, and a part of a day is never shown as the day. The rows and totals above are complete; the trend, the heatmap and the CSV need the daily detail and are not shown.",
31
+ zoneChanged: "The fleet's timezone changed: these days were made in {0}. A day keeps the zone it was made in, so a day made in two shows each part (see the heatmap). Days from now on are in {1}.",
32
+ dayTokens: "{0} tokens", partNotPriced: "part not priced",
33
+ trendUnpriced: "Not priced, so not in these bars: {0}. Their tokens are in the rows and the heatmap.",
34
+ off: "Usage is turned off", offHint: "web.usage_panel: false hides it.",
35
+ trend: "Daily trend", trendTokens: "tokens (nothing here is priced)", others: "Others", heatmap: "Daily totals",
36
+ h_idle: "{0}: read, nothing used", h_gap: "{0}: not collected", h_day: "{0}: {1}", keyGap: "not collected", keyIdle: "read, nothing used", keyUsed: "used (stronger: more tokens)", scale: "Tallest day: {0}", earlier: "← Scroll for earlier days",
37
+ trendLabel: "Daily {0} for the top {1} and others, {2} to {3}.", trendLabelAll: "Daily {0} for {1} agents, {2} to {3}.", heatLabel: "{0} days; {1} with usage, {2} not collected.",
38
+ zoneNote: "Days in {0}, whole hours.", teamsNote: "Teams as configured now.",
39
+ noRows: "Nothing in this range.",
40
+ },
41
+ "zh-TW": {
42
+ range: "期間", today: "今天", r7d: "7 天", month: "本月", all: "全部", custom: "自訂", from: "從", to: "到", apply: "套用",
43
+ group: "分組", g_instance: "Agent", g_backend: "Backend", g_model: "模型", g_profile: "憑證 profile", g_team: "Team(依目前設定)",
44
+ csv: "下載 CSV", basis: "依牌價換算的 API 等值(查證日期 {0})——不是實際花費。訂閱方案不按 token 計費。",
45
+ amount: "API 等值", input: "輸入", output: "輸出", read: "快取讀取", write: "快取寫入", total: "token 總計", requests: "請求數", messages: "訊息數",
46
+ notPriced: "未定價", partial: "部分", noTeam: "(不屬於任何 team)", unknown: "未知",
47
+ f_cache_write_not_reported: "未回報快取寫入(Codex)——是估算", f_not_priced: "未定價:{0}", f_source_unknown: "讀取時尚未記錄 backend 與 profile",
48
+ scanning: "掃描中——總數可能還會增加。", updated: "更新於 {0} · 每 10 分鐘讀取一次", since: "自 {0} 起收集", notYet: "尚未收集",
49
+ refresh: "重新整理",
50
+ notMeasured: "未量測", nm_credit_billed: "Kiro 以 credit 計費,不記錄 token", nm_unsupported: "AgEnD 目前還讀不到這個 CLI 的用量", nm_no_data: "尚未讀到任何資料",
51
+ shared: "與 {0} 共用工作目錄,無法分辨各自的 transcript",
52
+ empty: "還沒有任何紀錄", emptyHint: "用量從 Claude Code 與 Codex 的 transcript 讀取,自 fleet 啟動起計。",
53
+ blocked: "公開連結上不提供", blockedHint: "只有在設定 → 網頁裡開啟「透過公開連結提供用量報告」時,公開連結才會提供用量;這個設定要在執行 AgEnD 的那台電腦上、直接開啟的儀表板裡變更。",
54
+ openSettings: "在那台電腦上開啟設定 → 網頁",
55
+ partialDays: "每日明細從 {0} 起:這段期間更早的日子資料列太多,無法一次送出。上方的各列與總計涵蓋整段期間;趨勢、熱度圖與 CSV 則從 {0} 開始。",
56
+ csvFrom: "下載 CSV(從 {0} 起)",
57
+ noDaily: "這段期間沒有每日明細:連最近的一天,資料列都超過一次回答能帶的數量,而不完整的一天絕不會被當成那一天顯示。上方的各列與總計是完整的;趨勢、熱度圖與 CSV 需要每日明細,所以不顯示。",
58
+ zoneChanged: "fleet 的時區曾經變更:這些日子分別依 {0} 計算。每一天保留它當初計算時的時區,所以跨兩個時區的日子會分別列出各部分(見熱度圖)。之後的日子依 {1} 計算。",
59
+ dayTokens: "{0} 個 token", partNotPriced: "部分未定價",
60
+ trendUnpriced: "未定價,所以不在這些長條裡:{0}。它們的 token 列在各列與熱度圖中。",
61
+ off: "用量已關閉", offHint: "已設定 web.usage_panel: false。",
62
+ trend: "每日趨勢", trendTokens: "token(這裡沒有任何已定價的用量)", others: "其他", heatmap: "每日總計",
63
+ h_idle: "{0}:已讀取,沒有用量", h_gap: "{0}:未收集", h_day: "{0}:{1}", keyGap: "未收集", keyIdle: "已讀取,沒有用量", keyUsed: "有用量(顏色越濃,token 越多)", scale: "最高的一天:{0}", earlier: "← 往左捲動可看較早的日子",
64
+ trendLabel: "前 {1} 名與其他的每日{0},{2} 至 {3}。", trendLabelAll: "{1} 個 agent 的每日{0},{2} 至 {3}。", heatLabel: "{0} 天;{1} 天有用量,{2} 天未收集。",
65
+ zoneNote: "日期依 {0},以整點小時計。", teamsNote: "Team 依目前設定。",
66
+ noRows: "這段期間沒有任何紀錄。",
67
+ },
68
+ });
69
+ const tu = (k, ...v) => t(`fusage.${k}`, ...v);
70
+
71
+ export const USAGE_RANGES = ["today", "7d", "month", "all", "custom"];
72
+ export const USAGE_GROUPS = ["instance", "backend", "model", "profile", "team"];
73
+ const TOP = 5;
74
+ const DATE = /^\d{4}-\d{2}-\d{2}$/;
75
+ const n = (v) => Number(v || 0).toLocaleString("en-US");
76
+
77
+ /** The query for a choice of range and grouping. */
78
+ export function usageQuery({ range, group, from, to }) {
79
+ const q = new URLSearchParams({ range, group });
80
+ if (range === "custom") { q.set("from", from); q.set("to", to); }
81
+ return `/ui/usage-report?${q}`;
82
+ }
83
+
84
+ /** A row's or a day's amount as shown: the API-equivalent, or why there is none. */
85
+ export function amountText(r) {
86
+ if (r.amount === null || r.amount === undefined) return tu("notPriced");
87
+ return `${money(r.amount)}${r.priced === "partial" ? ` (${tu("partial")})` : ""}`;
88
+ }
89
+ /** A group key as shown: "" is no team, "unknown" says what it is. */
90
+ export function keyText(group, key) {
91
+ if (group === "team" && key === "") return tu("noTeam");
92
+ if (key === "unknown") return tu("unknown");
93
+ return key;
94
+ }
95
+ const writes = (r) => (r.cacheWrite5m || 0) + (r.cacheWrite1h || 0) + (r.cacheWrite || 0);
96
+ function flagText(f, r) {
97
+ if (f === "not_priced") return tu("f_not_priced", (r.unpriced || []).join(", "));
98
+ return ["cache_write_not_reported", "source_unknown"].includes(f) ? tu(`f_${f}`) : f;
99
+ }
100
+
101
+ // ── CSV (design §4): the daily rows of this same answer; messages on their own rows (they are per agent, not per model).
102
+ const CSV_COLUMNS = ["date", "zone", "instance", "backend", "model", "tier", "profile", "input", "output", "cache_read", "cache_write_5m", "cache_write_1h", "cache_write", "requests", "messages", "amount", "basis", "flags"];
103
+ /** One CSV field: quoted when it must be, and never read as a formula by a spreadsheet (=, +, -, @, tab, CR first). */
104
+ export function csvField(v) {
105
+ let s = v === null || v === undefined ? "" : String(v);
106
+ if (/^[=+\-@\t\r]/.test(s)) s = `'${s}`;
107
+ return /[",\n\r]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
108
+ }
109
+ export function usageCsv(report) {
110
+ const lines = [CSV_COLUMNS.join(",")];
111
+ for (const d of report.daily || []) {
112
+ lines.push([d.date, d.zone, d.instance, d.backend, d.model, d.tier, d.profile, d.input, d.output, d.cacheRead, d.cacheWrite5m, d.cacheWrite1h, d.cacheWrite,
113
+ d.requests, "", d.amount === null ? "" : d.amount.toFixed(6), report.basis, (d.flags || []).join(" ")].map(csvField).join(","));
114
+ }
115
+ for (const m of report.messagesDaily || []) {
116
+ lines.push([m.date, report.zone, m.instance, "", "", "", "", "", "", "", "", "", "", "", m.count, "", report.basis, ""].map(csvField).join(","));
117
+ }
118
+ return `${lines.join("\r\n")}\r\n`;
119
+ }
120
+ function download(text, name) {
121
+ const url = URL.createObjectURL(new Blob([text], { type: "text/csv;charset=utf-8" }));
122
+ const a = document.createElement("a"); a.href = url; a.download = name; a.className = "offscreen";
123
+ document.body.appendChild(a); a.click(); a.remove();
124
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
125
+ }
126
+
127
+ // ── Days: the range's local dates, which of them were read (coverage, §5.5), and what each one holds.
128
+ const shift = (date, k) => new Date(Date.UTC(+date.slice(0, 4), +date.slice(5, 7) - 1, +date.slice(8, 10) + k)).toISOString().slice(0, 10);
129
+ /** The local dates from `from` to `to` (inclusive), at most `max`, the latest kept. */
130
+ export function datesBetween(from, to, max = 400) {
131
+ if (!DATE.test(from) || !DATE.test(to) || from > to) return [];
132
+ const out = [];
133
+ for (let d = to; d >= from && out.length < max; d = shift(d, -1)) out.unshift(d);
134
+ return out;
135
+ }
136
+ /** The local dates in `zone` that some coverage interval reaches (hour by hour, as days are made, §5.3). */
137
+ export function coveredDates(coverage, zone) {
138
+ let fmt;
139
+ try { fmt = new Intl.DateTimeFormat("en-CA", { timeZone: zone, year: "numeric", month: "2-digit", day: "2-digit" }); } catch { fmt = new Intl.DateTimeFormat("en-CA", { timeZone: "UTC", year: "numeric", month: "2-digit", day: "2-digit" }); }
140
+ const out = new Set();
141
+ for (const c of coverage || []) for (const [a, b] of c.intervals || []) {
142
+ for (let h = Math.floor(a / 3_600_000) * 3_600_000; h <= b; h += 3_600_000) out.add(fmt.format(new Date(h)));
143
+ }
144
+ return out;
145
+ }
146
+ /** Intensity 1–4 for a day's value against the range's non-zero days (quartiles, as tokscale's heatmap); 0 for none. */
147
+ export function levels(values) {
148
+ const nz = values.filter((v) => v > 0).sort((a, b) => a - b);
149
+ const q = (p) => nz[Math.max(0, Math.ceil(p * nz.length) - 1)];
150
+ const cut = nz.length ? [q(0.25), q(0.5), q(0.75)] : [];
151
+ return (v) => (!(v > 0) ? 0 : v <= cut[0] ? 1 : v <= cut[1] ? 2 : v <= cut[2] ? 3 : 4);
152
+ }
153
+
154
+ const tokensOf = (d) => d.input + d.output + d.cacheRead + d.cacheWrite5m + d.cacheWrite1h + d.cacheWrite;
155
+ /**
156
+ * Each day of the answer: its tokens (whether it was used at all), the priced part of its amount, whether part of it is
157
+ * not priced, and its parts by zone (a day folded before the fleet's timezone changed keeps its own zone, §5.3).
158
+ */
159
+ export function dayTotals(report) {
160
+ const m = new Map();
161
+ for (const d of report.daily || []) {
162
+ let e = m.get(d.date);
163
+ if (!e) { e = { tokens: 0, amount: 0, unpriced: false, zones: new Map() }; m.set(d.date, e); }
164
+ const t = tokensOf(d);
165
+ e.tokens += t;
166
+ if (d.amount === null) e.unpriced = true; else e.amount += d.amount;
167
+ const z = e.zones.get(d.zone) || { tokens: 0, amount: 0 };
168
+ z.tokens += t; z.amount += d.amount || 0; e.zones.set(d.zone, z);
169
+ }
170
+ return m;
171
+ }
172
+ /** A day in words: its tokens, its API-equivalent where priced, what is not priced, and each zone's part. */
173
+ export function dayText(date, e) {
174
+ const parts = [tu("dayTokens", tokens(e.tokens))];
175
+ if (e.amount > 0) parts.push(money(e.amount));
176
+ if (e.unpriced) parts.push(tu(e.amount > 0 ? "partNotPriced" : "notPriced"));
177
+ const zones = e.zones.size > 1 ? ` (${[...e.zones].map(([z, p]) => `${z}: ${tokens(p.tokens)}`).join(", ")})` : "";
178
+ return tu("h_day", date, parts.join(" · ") + zones);
179
+ }
180
+ /** The zones the answer's days were made in, when they are not all the fleet's current one (§5.3). */
181
+ export function zonesChanged(report) {
182
+ const zones = [...new Set((report.daily || []).map((d) => d.zone))];
183
+ return zones.length > 1 || (zones.length === 1 && zones[0] !== report.zone) ? zones : null;
184
+ }
185
+
186
+ /** Estimated pixel width of one character in the 9 px `.us-axis` label. */
187
+ const LABEL_CHAR_W = 5.5;
188
+
189
+ /**
190
+ * The 0-based indices of dates that should receive an x-axis label.
191
+ * Keeps the first (0) and last (n-1) when they are far enough apart not to
192
+ * overlap. When first and last are too close (n-1 < step), only the last
193
+ * (most recent) is shown — overlap-free takes precedence over keeping both
194
+ * ends. Intermediate labels are thinned so that no two consecutive shown
195
+ * labels are closer than `labelWidthPx` pixels apart.
196
+ *
197
+ * @param {number} n Number of data points (dates).
198
+ * @param {number} slotPx Pixels per slot (bar + gap).
199
+ * @param {number} labelWidthPx Estimated rendered width of one label.
200
+ * @returns {Set<number>}
201
+ */
202
+ export function thinXLabels(n, slotPx, labelWidthPx) {
203
+ if (n <= 0) return new Set();
204
+ if (n === 1) return new Set([0]);
205
+ // Minimum slot steps between label centres (+ 2 px breathing room).
206
+ const step = Math.max(1, Math.ceil((labelWidthPx + 2) / slotPx));
207
+ // If first and last are too close to both be shown without overlap, keep
208
+ // only the last (most recent). The caller's "always first+last" contract
209
+ // is relaxed here: overlap-free takes precedence.
210
+ if (n - 1 < step) return new Set([n - 1]);
211
+ const idx = [0];
212
+ // Add intermediates that are at least `step` slots away from n-1.
213
+ for (let i = step; i < n - step; i += step) idx.push(i);
214
+ idx.push(n - 1); // always include last
215
+ return new Set(idx);
216
+ }
217
+
218
+ /** The trend's series: the top `TOP` agents by the measure over the range, then "others"; one value per date. */
219
+ export function trendSeries(report, dates) {
220
+ const priced = report.totals && report.totals.amount !== null && report.totals.amount > 0;
221
+ const measure = (d) => (priced ? d.amount || 0 : d.input + d.output + d.cacheRead + d.cacheWrite5m + d.cacheWrite1h + d.cacheWrite);
222
+ const byAgent = new Map();
223
+ for (const d of report.daily || []) byAgent.set(d.instance, (byAgent.get(d.instance) || 0) + measure(d));
224
+ const ranked = [...byAgent.entries()].filter(([, v]) => v > 0).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
225
+ const top = ranked.slice(0, TOP).map(([k]) => k);
226
+ const names = ranked.length > TOP ? [...top, tu("others")] : top;
227
+ const at = new Map(dates.map((d, i) => [d, i]));
228
+ const values = names.map(() => dates.map(() => 0));
229
+ for (const d of report.daily || []) {
230
+ const i = at.get(d.date);
231
+ if (i === undefined) continue;
232
+ const s = top.indexOf(d.instance);
233
+ values[s >= 0 ? s : names.length - 1][i] += measure(d);
234
+ }
235
+ return { priced, names, values, totals: names.map((_, s) => values[s].reduce((a, b) => a + b, 0)) };
236
+ }
237
+ const fmtMeasure = (priced, v) => (priced ? money(v) : tokens(v));
238
+
239
+ /**
240
+ * A chart wider than its box opens at its latest days (the right end), as a person reads a trend, and says that the
241
+ * earlier days are a scroll away.
242
+ */
243
+ function useLatestFirst(deps) {
244
+ const ref = useRef(null);
245
+ const [wide, setWide] = useState(false);
246
+ useLayoutEffect(() => {
247
+ const el = ref.current, over = !!el && el.scrollWidth > el.clientWidth;
248
+ if (over) el.scrollLeft = el.scrollWidth;
249
+ setWide(over);
250
+ }, deps);
251
+ return [ref, wide];
252
+ }
253
+ const EarlierHint = ({ wide }) => (wide ? html`<p class="note us-earlier">${tu("earlier")}</p>` : null);
254
+
255
+ function Trend({ report, dates }) {
256
+ const s = useMemo(() => trendSeries(report, dates), [report, dates]);
257
+ const [scroller, wide] = useLatestFirst([report, dates]);
258
+ if (!s.names.length || !dates.length) return null;
259
+ // A value scale on the left (the day with the most, and zero), the bars after it.
260
+ const slot = 14, left = 52, right = 24, W = Math.max(320, left + dates.length * slot + right), H = 170, top = 16, base = 146;
261
+ const sums = dates.map((_, i) => s.values.reduce((a, v) => a + v[i], 0));
262
+ const max = Math.max(...sums, 0) || 1;
263
+ const what = s.priced ? tu("amount") : tu("trendTokens"), others = s.names.length > TOP;
264
+ const label = others ? tu("trendLabel", what, TOP, dates[0], dates[dates.length - 1]) : tu("trendLabelAll", what, s.names.length, dates[0], dates[dates.length - 1]);
265
+ // Thin x-axis labels: "MM-DD" (5 chars) × LABEL_CHAR_W ≈ 27.5 px at 9 px sans-serif.
266
+ const shownLabels = thinXLabels(dates.length, slot, 5 * LABEL_CHAR_W);
267
+ return html`<section class="us-trend card" aria-labelledby="us-trend-h">
268
+ <h3 id="us-trend-h">${tu("trend")} <span class="note">· ${s.priced ? tu("amount") : tu("trendTokens")}</span></h3>
269
+ <p class="note us-scale">${tu("scale", fmtMeasure(s.priced, max))}</p>
270
+ <div class="us-scroll" ref=${scroller}><svg class="us-chart" viewBox=${`0 0 ${W} ${H}`} width=${W} height=${H} role="img" aria-label=${label}>
271
+ <line class="us-grid" x1=${left - 4} x2=${W} y1=${top} y2=${top} /><line class="us-base" x1=${left - 4} x2=${W} y1=${base} y2=${base} />
272
+ <text class="us-axis" x=${left - 8} y=${top + 3} text-anchor="end">${fmtMeasure(s.priced, max)}</text>
273
+ <text class="us-axis" x=${left - 8} y=${base + 3} text-anchor="end">0</text>
274
+ ${dates.map((date, i) => {
275
+ let y = base;
276
+ return html`<g key=${date}>${s.values.map((v, k) => {
277
+ const h = v[i] > 0 ? Math.max(1, ((base - top) * v[i]) / max) : 0;
278
+ y -= h;
279
+ return h ? html`<rect key=${k} class=${`us-s${k}`} x=${left + i * slot + 2} y=${y} width=${slot - 4} height=${h}><title>${`${date} · ${s.names[k]}: ${fmtMeasure(s.priced, v[i])}`}</title></rect>` : null;
280
+ })}${shownLabels.has(i) ? html`<text class="us-axis us-xlabel" x=${left + i * slot + slot / 2} y=${H - 6} text-anchor="middle">${date.slice(5)}</text>` : null}</g>`;
281
+ })}
282
+ </svg></div>
283
+ <${EarlierHint} wide=${wide} />
284
+ ${s.priced && (report.totals.unpriced || []).length ? html`<p class="note us-unpriced">${tu("trendUnpriced", report.totals.unpriced.join(", "))}</p>` : null}
285
+ <ul class="us-legend">${s.names.map((name, k) => html`<li key=${name}><span class=${`us-sw us-s${k}`} aria-hidden="true"></span><span class="mono">${name}</span> <span class="note">${fmtMeasure(s.priced, s.totals[k])}</span></li>`)}</ul>
286
+ </section>`;
287
+ }
288
+
289
+ function Heatmap({ report, dates }) {
290
+ const covered = useMemo(() => coveredDates(report.coverage, report.zone), [report]);
291
+ // Whether a day was used, and how much, is its tokens: a day used only by a model not in the price table is used,
292
+ // never "nothing used" (its title says it is not priced).
293
+ const byDate = useMemo(() => dayTotals(report), [report]);
294
+ const [scroller, wide] = useLatestFirst([report, dates]);
295
+ if (!dates.length) return null;
296
+ const level = levels(dates.map((d) => byDate.get(d)?.tokens || 0));
297
+ // Weeks as columns, Monday first (a date's weekday from its own Y-M-D: no zone involved).
298
+ const dow = (d) => (new Date(`${d}T00:00:00Z`).getUTCDay() + 6) % 7;
299
+ // Weeks as columns under a month row (its short name where a month begins).
300
+ const lead = dow(dates[0]), cell = 16, head = 14, W = (Math.ceil((lead + dates.length) / 7)) * cell + 2, H = head + 7 * cell + 2;
301
+ let used = 0, gaps = 0;
302
+ const months = [];
303
+ const cells = dates.map((d, i) => {
304
+ const e = byDate.get(d), v = e ? e.tokens : 0, k = lead + i, x = Math.floor(k / 7) * cell + 1, y = head + (k % 7) * cell + 1;
305
+ if (i === 0 || d.endsWith("-01")) months.push(html`<text key=${`m${d}`} class="us-axis" x=${x} y=${10}>${new Date(`${d}T12:00:00Z`).toLocaleDateString([], { month: "short", timeZone: "UTC" })}</text>`);
306
+ const state = v > 0 ? `l${level(v)}` : covered.has(d) ? "idle" : "gap";
307
+ if (v > 0) used++; else if (state === "gap") gaps++;
308
+ const title = v > 0 ? dayText(d, e) : state === "idle" ? tu("h_idle", d) : tu("h_gap", d);
309
+ return html`<rect key=${d} class=${`us-h ${state}`} x=${x} y=${y} width=${cell - 3} height=${cell - 3} rx="2"><title>${title}</title></rect>`;
310
+ });
311
+ return html`<section class="us-heat card" aria-labelledby="us-heat-h">
312
+ <h3 id="us-heat-h">${tu("heatmap")}</h3>
313
+ <div class="us-scroll" ref=${scroller}><svg class="us-heatmap" viewBox=${`0 0 ${W} ${H}`} width=${W} height=${H} role="img" aria-label=${tu("heatLabel", dates.length, used, gaps)}>${months}${cells}</svg></div>
314
+ <${EarlierHint} wide=${wide} />
315
+ <p class="note us-heat-key"><span class="us-h-sw gap" aria-hidden="true"></span>${tu("keyGap")} · <span class="us-h-sw idle" aria-hidden="true"></span>${tu("keyIdle")} · <span class="us-h-sw l4" aria-hidden="true"></span>${tu("keyUsed")}</p>
316
+ </section>`;
317
+ }
318
+
319
+ function Row({ r, group, max, measure }) {
320
+ const share = max > 0 ? Math.max(0.01, measure(r) / max) : 0;
321
+ const meta = [r.backends.join(", "), r.models.join(", "), r.profiles.join(", ")].filter(Boolean).join(" · ");
322
+ return html`<tr class="us-row">
323
+ <th scope="row" class="us-name"><span class="mono">${keyText(group, r.key)}</span>
324
+ <svg class="us-bar" aria-hidden="true"><rect class="us-bar-bg" width="100%" height="6" rx="3" /><rect class="us-bar-fg" width=${`${Math.round(share * 100)}%`} height="6" rx="3" /></svg>
325
+ ${group !== "instance" || meta ? html`<span class="us-meta">${meta}</span>` : null}
326
+ ${r.flags.length ? html`<span class="us-flags">${r.flags.map((f) => html`<span key=${f} class="ch-tag">${flagText(f, r)}</span>`)}</span>` : null}</th>
327
+ <td data-label=${tu("amount")} class=${`num${r.amount === null ? " muted" : ""}`}>${amountText(r)}</td>
328
+ <td data-label=${tu("input")} class="num">${tokens(r.input)}</td>
329
+ <td data-label=${tu("output")} class="num">${tokens(r.output)}</td>
330
+ <td data-label=${tu("read")} class="num">${tokens(r.cacheRead)}</td>
331
+ <td data-label=${tu("write")} class="num">${r.flags.includes("cache_write_not_reported") && writes(r) === 0 ? "—" : tokens(writes(r))}</td>
332
+ <td data-label=${tu("total")} class="num">${tokens(r.total)}</td>
333
+ <td data-label=${tu("requests")} class="num">${n(r.requests)}</td>
334
+ <td data-label=${tu("messages")} class="num">${r.messages === null ? "—" : n(r.messages)}</td>
335
+ </tr>`;
336
+ }
337
+
338
+ /** The tab's body for one answer of GET /ui/usage-report. */
339
+ export function UsageReport({ report, choice, setChoice, refresh, fetchedAt }) {
340
+ const group = report.group; // what this answer is grouped by (the choice may be on its way)
341
+ const [draft, setDraft] = useState({ from: choice.from || report.range.from, to: choice.to || report.range.to });
342
+ const dates = useMemo(() => {
343
+ const first = report.range.from === "0000-01-01"
344
+ ? [...(report.daily || []).map((d) => d.date), ...(report.messagesDaily || []).map((m) => m.date)].sort()[0] || report.range.to
345
+ : report.range.from;
346
+ // Daily detail cut to the latest whole days (too many rows): the charts start where it starts.
347
+ // No daily detail at all (not even the latest day fits): no dates — no chart drawn from a part of a day.
348
+ if (report.dailyTruncated && !report.dailyFrom) return [];
349
+ return datesBetween(report.dailyTruncated && report.dailyFrom > first ? report.dailyFrom : first, report.range.to);
350
+ }, [report]);
351
+ const noDaily = report.dailyTruncated && !report.dailyFrom;
352
+ const zones = zonesChanged(report);
353
+ const csvFrom = report.dailyTruncated ? report.dailyFrom : report.range.kind === "all" ? "all" : report.range.from;
354
+ const rows = report.rows.filter((r) => r.total > 0 || (r.messages || 0) > 0);
355
+ const anyAmount = rows.some((r) => r.amount !== null && r.amount > 0);
356
+ const measure = (r) => (anyAmount ? r.amount || 0 : r.total);
357
+ const max = Math.max(0, ...rows.map(measure));
358
+ const since = (report.coverage || []).flatMap((c) => c.intervals || []).reduce((m, [a]) => Math.min(m, a), Infinity);
359
+ // Nothing at all to show yet: never collected, or nothing in everything collected.
360
+ const empty = !rows.length && (report.range.kind === "all" || !Number.isFinite(since));
361
+ return html`<div class="us">
362
+ <div class="us-bar-top">
363
+ <div class="seg-inline" role="group" aria-label=${tu("range")}>${USAGE_RANGES.map((k) => html`<button key=${k} type="button"
364
+ class=${`btn btn-sm${k === choice.range ? " btn-primary" : ""}`} aria-pressed=${k === choice.range ? "true" : "false"}
365
+ onClick=${() => (k === "custom" ? setChoice({ ...choice, range: "custom", from: draft.from, to: draft.to }) : setChoice({ ...choice, range: k }))}>${tu(k === "7d" ? "r7d" : k)}</button>`)}</div>
366
+ <label class="us-group">${tu("group")} <select value=${choice.group} onChange=${(e) => setChoice({ ...choice, group: e.target.value })}>
367
+ ${USAGE_GROUPS.map((g) => html`<option key=${g} value=${g}>${tu(`g_${g}`)}</option>`)}</select></label>
368
+ <button type="button" class="btn btn-sm us-csv" disabled=${noDaily} title=${noDaily ? tu("noDaily") : undefined} onClick=${() => download(usageCsv(report), `agend-usage-${csvFrom}-${report.range.to}.csv`)}>
369
+ <${Icon} name="download" size=${14} />${report.dailyTruncated && !noDaily ? tu("csvFrom", report.dailyFrom) : tu("csv")}</button>
370
+ </div>
371
+ ${choice.range === "custom" ? html`<form class="us-custom" onSubmit=${(e) => { e.preventDefault(); if (DATE.test(draft.from) && DATE.test(draft.to) && draft.from <= draft.to) setChoice({ ...choice, ...draft }); }}>
372
+ <label>${tu("from")} <input type="date" value=${draft.from} max=${draft.to} onInput=${(e) => setDraft({ ...draft, from: e.target.value })} /></label>
373
+ <label>${tu("to")} <input type="date" value=${draft.to} min=${draft.from} onInput=${(e) => setDraft({ ...draft, to: e.target.value })} /></label>
374
+ <button type="submit" class="btn btn-sm">${tu("apply")}</button></form>` : null}
375
+ <p class="us-basis" role="note">${tu("basis", report.pricesChecked)}</p>
376
+ ${noDaily ? html`<p class="us-partial" role="status">${tu("noDaily")}</p>` : report.dailyTruncated ? html`<p class="us-partial" role="status">${tu("partialDays", report.dailyFrom)}</p>` : null}
377
+ ${zones ? html`<p class="us-partial" role="status">${tu("zoneChanged", zones.join(", "), report.zone)}</p>` : null}
378
+ <p class="note us-fresh">${tu("updated", new Date(fetchedAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }))} · ${Number.isFinite(since) ? tu("since", new Date(since).toLocaleDateString()) : tu("notYet")} · ${tu("zoneNote", report.zone)}${group === "team" ? ` ${tu("teamsNote")}` : ""}
379
+ ${report.scanning.active && (!report.scanning.caughtUp || report.scanning.pendingBytes > 0) ? html` <span class="us-scan" role="status">${tu("scanning")} <button type="button" class="btn btn-sm" onClick=${refresh}>${tu("refresh")}</button></span>` : null}</p>
380
+ ${empty ? html`<${Empty} icon="chart" title=${tu("empty")} hint=${tu("emptyHint")} />` : html`
381
+ <dl class="us-totals card">
382
+ <div class="ch-metric"><dt>${tu("amount")}</dt><dd>${amountText(report.totals)}</dd></div>
383
+ <div class="ch-metric"><dt>${tu("total")}</dt><dd>${tokens(report.totals.total)}</dd></div>
384
+ <div class="ch-metric"><dt>${tu("requests")}</dt><dd>${n(report.totals.requests)}</dd></div>
385
+ <div class="ch-metric"><dt>${tu("messages")}</dt><dd>${report.totals.messages === null ? "—" : n(report.totals.messages)}</dd></div>
386
+ </dl>
387
+ ${rows.length ? html`<table class="us-table"><thead><tr><th scope="col">${tu(`g_${group}`)}</th><th scope="col" class="num">${tu("amount")}</th>
388
+ <th scope="col" class="num">${tu("input")}</th><th scope="col" class="num">${tu("output")}</th><th scope="col" class="num">${tu("read")}</th><th scope="col" class="num">${tu("write")}</th>
389
+ <th scope="col" class="num">${tu("total")}</th><th scope="col" class="num">${tu("requests")}</th><th scope="col" class="num">${tu("messages")}</th></tr></thead>
390
+ <tbody>${rows.map((r) => html`<${Row} key=${r.key} r=${r} group=${group} max=${max} measure=${measure} />`)}</tbody></table>`
391
+ : html`<p class="note">${tu("noRows")}</p>`}
392
+ <${Trend} report=${report} dates=${dates} />
393
+ <${Heatmap} report=${report} dates=${dates} />`}
394
+ ${report.notMeasured.length || report.shared.length ? html`<section class="us-rest" aria-labelledby="us-rest-h"><h3 id="us-rest-h">${tu("notMeasured")}</h3>
395
+ <ul class="rows">${report.notMeasured.map((m) => html`<li key=${m.name} class="row-item"><span class="grow strong mono">${m.name}</span><span class="muted">${m.backend} · ${tu(`nm_${m.reason}`)}</span></li>`)}
396
+ ${report.shared.map((m) => html`<li key=${m.name} class="row-item"><span class="grow strong mono">${m.name}</span><span class="muted">${m.backend} · ${tu("shared", m.with.join(", "))}</span></li>`)}</ul></section>` : null}
397
+ </div>`;
398
+ }
399
+
400
+ /**
401
+ * The tab: its own read (a refusal is a state the page explains, not an error): 403 over the public link without the
402
+ * key, 404 with web.usage_panel: false.
403
+ */
404
+ export function UsageTab({ lease }) {
405
+ const [choice, setChoice] = useState({ range: "7d", group: "instance", from: "", to: "" });
406
+ const [version, setVersion] = useState(0);
407
+ const [state, setState] = useState(null);
408
+ const seq = useRef(0);
409
+ const path = choice.range === "custom" && !(DATE.test(choice.from) && DATE.test(choice.to)) ? null : usageQuery(choice);
410
+ useEffect(() => {
411
+ if (!path) return;
412
+ const mine = ++seq.current;
413
+ (async () => {
414
+ try {
415
+ const r = await lease.fetch(path, { method: "GET", headers: { "Content-Type": "application/json" } });
416
+ if (!lease.current() || mine !== seq.current) return;
417
+ if (r.status === 403 || r.status === 404) {
418
+ const body = await r.json().catch(() => null);
419
+ if (!lease.current() || mine !== seq.current) return; // a newer read (or another page) owns the view
420
+ // Over the public link: where it is turned on — Settings → Web on the dashboard reached directly.
421
+ setState({ blocked: r.status === 403 ? "blocked" : "off", settings: body && typeof body.settings === "string" && /^http:\/\/localhost:\d+\/settings\/web$/.test(body.settings) ? body.settings : null });
422
+ return;
423
+ }
424
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
425
+ const d = await r.json();
426
+ if (lease.current() && mine === seq.current) setState(d && Array.isArray(d.rows) ? { data: d, at: Date.now() } : { error: true });
427
+ } catch { if (lease.current() && mine === seq.current) setState({ error: true }); }
428
+ })();
429
+ }, [lease, path, version]);
430
+ if (!state) return html`<${Skeleton} lines=${5} />`;
431
+ if (state.blocked) return html`<${Empty} icon=${state.blocked === "blocked" ? "globe" : "chart"} title=${tu(state.blocked)} hint=${tu(`${state.blocked}Hint`)}
432
+ action=${state.settings ? html`<a class="btn btn-primary us-fix" href=${state.settings}>${tu("openSettings")}</a>` : null} />`;
433
+ if (state.error) return html`<${ErrorState} onRetry=${() => setVersion((v) => v + 1)} />`;
434
+ return html`<${UsageReport} report=${state.data} fetchedAt=${state.at} choice=${choice} setChoice=${setChoice} refresh=${() => setVersion((v) => v + 1)} />`;
435
+ }