@songsid/agend 2.3.0-beta.1 → 2.3.0-beta.11

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 (357) hide show
  1. package/dist/agent-cli-instructions.md +5 -2
  2. package/dist/agent-cli.js +43 -7
  3. package/dist/agent-cli.js.map +1 -1
  4. package/dist/agent-endpoint.js +4 -0
  5. package/dist/agent-endpoint.js.map +1 -1
  6. package/dist/agent-file-read.d.ts +14 -6
  7. package/dist/agent-file-read.js +207 -19
  8. package/dist/agent-file-read.js.map +1 -1
  9. package/dist/agent-whats-new.d.ts +11 -0
  10. package/dist/agent-whats-new.js +29 -15
  11. package/dist/agent-whats-new.js.map +1 -1
  12. package/dist/auth-api.d.ts +30 -1
  13. package/dist/auth-api.js +45 -13
  14. package/dist/auth-api.js.map +1 -1
  15. package/dist/authority/authority-state.d.ts +38 -0
  16. package/dist/authority/authority-state.js +114 -0
  17. package/dist/authority/authority-state.js.map +1 -0
  18. package/dist/authority/session-authorizations.d.ts +43 -0
  19. package/dist/authority/session-authorizations.js +102 -0
  20. package/dist/authority/session-authorizations.js.map +1 -0
  21. package/dist/authority/web-authority.d.ts +120 -0
  22. package/dist/authority/web-authority.js +157 -0
  23. package/dist/authority/web-authority.js.map +1 -0
  24. package/dist/backend/claude-code.d.ts +2 -2
  25. package/dist/backend/claude-code.js +10 -5
  26. package/dist/backend/claude-code.js.map +1 -1
  27. package/dist/backend/types.d.ts +14 -0
  28. package/dist/backend/types.js +25 -0
  29. package/dist/backend/types.js.map +1 -1
  30. package/dist/backend-availability.d.ts +120 -0
  31. package/dist/backend-availability.js +396 -0
  32. package/dist/backend-availability.js.map +1 -0
  33. package/dist/backend-command.d.ts +21 -3
  34. package/dist/backend-command.js +23 -4
  35. package/dist/backend-command.js.map +1 -1
  36. package/dist/bundled-skills.d.ts +14 -0
  37. package/dist/bundled-skills.js +29 -0
  38. package/dist/bundled-skills.js.map +1 -0
  39. package/dist/cache-analysis.d.ts +47 -1
  40. package/dist/cache-analysis.js +68 -8
  41. package/dist/cache-analysis.js.map +1 -1
  42. package/dist/cache-ledger.d.ts +16 -3
  43. package/dist/cache-ledger.js +33 -13
  44. package/dist/cache-ledger.js.map +1 -1
  45. package/dist/cache-report-tool.d.ts +17 -0
  46. package/dist/cache-report-tool.js +40 -0
  47. package/dist/cache-report-tool.js.map +1 -0
  48. package/dist/cache-service.d.ts +70 -3
  49. package/dist/cache-service.js +178 -17
  50. package/dist/cache-service.js.map +1 -1
  51. package/dist/channel/adapters/discord.d.ts +7 -0
  52. package/dist/channel/adapters/discord.js +35 -0
  53. package/dist/channel/adapters/discord.js.map +1 -1
  54. package/dist/channel/mcp-tools.js +22 -10
  55. package/dist/channel/mcp-tools.js.map +1 -1
  56. package/dist/channel/types.d.ts +6 -0
  57. package/dist/classic-channel-manager.d.ts +10 -5
  58. package/dist/classic-channel-manager.js +12 -9
  59. package/dist/classic-channel-manager.js.map +1 -1
  60. package/dist/cli.js +130 -31
  61. package/dist/cli.js.map +1 -1
  62. package/dist/config-validator.js +103 -4
  63. package/dist/config-validator.js.map +1 -1
  64. package/dist/daemon.d.ts +7 -0
  65. package/dist/daemon.js +63 -16
  66. package/dist/daemon.js.map +1 -1
  67. package/dist/decision-list.d.ts +25 -0
  68. package/dist/decision-list.js +30 -0
  69. package/dist/decision-list.js.map +1 -0
  70. package/dist/delivery-outbox.d.ts +10 -0
  71. package/dist/delivery-outbox.js +12 -1
  72. package/dist/delivery-outbox.js.map +1 -1
  73. package/dist/fleet-context.d.ts +10 -0
  74. package/dist/fleet-manager.d.ts +417 -11
  75. package/dist/fleet-manager.js +1387 -109
  76. package/dist/fleet-manager.js.map +1 -1
  77. package/dist/fleet-traffic.d.ts +74 -0
  78. package/dist/fleet-traffic.js +196 -0
  79. package/dist/fleet-traffic.js.map +1 -0
  80. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +20 -5
  81. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
  82. package/dist/general-knowledge/skills/multi-channel/SKILL.md +3 -2
  83. package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
  84. package/dist/general-knowledge/skills/web-appearance/SKILL.md +257 -23
  85. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +478 -0
  86. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
  87. package/dist/human-delivery-receipts.d.ts +24 -1
  88. package/dist/human-delivery-receipts.js +64 -11
  89. package/dist/human-delivery-receipts.js.map +1 -1
  90. package/dist/instance-config-impact.js +3 -1
  91. package/dist/instance-config-impact.js.map +1 -1
  92. package/dist/instance-names.d.ts +4 -0
  93. package/dist/instance-names.js +12 -0
  94. package/dist/instance-names.js.map +1 -0
  95. package/dist/instructions.d.ts +12 -1
  96. package/dist/instructions.js +13 -2
  97. package/dist/instructions.js.map +1 -1
  98. package/dist/locale.js +100 -8
  99. package/dist/locale.js.map +1 -1
  100. package/dist/ls-attach-resolve.d.ts +95 -0
  101. package/dist/ls-attach-resolve.js +174 -0
  102. package/dist/ls-attach-resolve.js.map +1 -0
  103. package/dist/memory-hogs.d.ts +27 -0
  104. package/dist/memory-hogs.js +124 -0
  105. package/dist/memory-hogs.js.map +1 -0
  106. package/dist/outbound-handlers.d.ts +10 -0
  107. package/dist/outbound-handlers.js +122 -2
  108. package/dist/outbound-handlers.js.map +1 -1
  109. package/dist/outbound-schemas.d.ts +164 -4
  110. package/dist/outbound-schemas.js +99 -3
  111. package/dist/outbound-schemas.js.map +1 -1
  112. package/dist/public-web-gateway.js +8 -2
  113. package/dist/public-web-gateway.js.map +1 -1
  114. package/dist/public-web-link.d.ts +9 -0
  115. package/dist/public-web-link.js +8 -0
  116. package/dist/public-web-link.js.map +1 -1
  117. package/dist/quickstart-api.d.ts +10 -1
  118. package/dist/quickstart-api.js +12 -4
  119. package/dist/quickstart-api.js.map +1 -1
  120. package/dist/reply-buttons-controller.d.ts +4 -0
  121. package/dist/reply-buttons-controller.js +2 -2
  122. package/dist/reply-buttons-controller.js.map +1 -1
  123. package/dist/restart-progress.d.ts +65 -1
  124. package/dist/restart-progress.js +236 -10
  125. package/dist/restart-progress.js.map +1 -1
  126. package/dist/rollout-index.d.ts +5 -0
  127. package/dist/rollout-index.js +26 -5
  128. package/dist/rollout-index.js.map +1 -1
  129. package/dist/scheduler/scheduler.d.ts +12 -0
  130. package/dist/scheduler/scheduler.js +83 -10
  131. package/dist/scheduler/scheduler.js.map +1 -1
  132. package/dist/service-installer.d.ts +2 -1
  133. package/dist/service-installer.js +18 -3
  134. package/dist/service-installer.js.map +1 -1
  135. package/dist/settings-api.d.ts +19 -0
  136. package/dist/settings-api.js +137 -2
  137. package/dist/settings-api.js.map +1 -1
  138. package/dist/settings-change.js +104 -0
  139. package/dist/settings-change.js.map +1 -1
  140. package/dist/settings-effect.js +17 -1
  141. package/dist/settings-effect.js.map +1 -1
  142. package/dist/tmux-manager.d.ts +5 -0
  143. package/dist/tmux-manager.js +16 -0
  144. package/dist/tmux-manager.js.map +1 -1
  145. package/dist/tool-permissions.d.ts +4 -6
  146. package/dist/tool-permissions.js +53 -5
  147. package/dist/tool-permissions.js.map +1 -1
  148. package/dist/topic-commands.d.ts +7 -1
  149. package/dist/topic-commands.js +18 -4
  150. package/dist/topic-commands.js.map +1 -1
  151. package/dist/types.d.ts +48 -1
  152. package/dist/ui/app-effects.js +77 -31
  153. package/dist/ui/chat-ambient.js +307 -0
  154. package/dist/ui/chat-effect-posters.js +8 -8
  155. package/dist/ui/chat-effects.js +101 -13
  156. package/dist/ui/chat-render.js +21 -3
  157. package/dist/ui/chat-strings.js +59 -6
  158. package/dist/ui/chat-thread.js +159 -21
  159. package/dist/ui/effect-trial-controller.js +13 -8
  160. package/dist/ui/effect-trial-panel.js +19 -16
  161. package/dist/ui/fleet-cache.js +23 -1
  162. package/dist/ui/fleet-canvas-layout.js +111 -0
  163. package/dist/ui/fleet-canvas.js +525 -0
  164. package/dist/ui/fleet-org.js +27 -12
  165. package/dist/ui/fleet-traffic.js +343 -0
  166. package/dist/ui/fleet-usage.js +401 -0
  167. package/dist/ui/panel-chat.js +154 -49
  168. package/dist/ui/panel-details.js +2 -1
  169. package/dist/ui/panel-fleet.js +43 -17
  170. package/dist/ui/panel-settings.js +26 -8
  171. package/dist/ui/preview.js +3 -2
  172. package/dist/ui/settings-appearance.js +42 -18
  173. package/dist/ui/settings-catalog.js +92 -0
  174. package/dist/ui/settings-dialogs.js +40 -12
  175. package/dist/ui/settings-effects.js +4 -3
  176. package/dist/ui/settings-guests.js +117 -0
  177. package/dist/ui/settings-learn.js +25 -0
  178. package/dist/ui/settings-model.js +15 -6
  179. package/dist/ui/settings-mods.js +23 -9
  180. package/dist/ui/settings-owner-effects.js +9 -9
  181. package/dist/ui/settings-strings.js +120 -42
  182. package/dist/ui/settings-web-authority.js +91 -0
  183. package/dist/ui/shared/agend-auth.js +1 -1
  184. package/dist/ui/shared/app-i18n.js +4 -2
  185. package/dist/ui/shared/app-prefs.js +5 -4
  186. package/dist/ui/shared/app-route.js +19 -1
  187. package/dist/ui/shared/app-session.js +2 -0
  188. package/dist/ui/shared/app-shell.js +92 -27
  189. package/dist/ui/shared/app-store.js +26 -1
  190. package/dist/ui/shared/app-stream.js +3 -1
  191. package/dist/ui/shared/app-themes.js +24 -6
  192. package/dist/ui/shared/app.css +365 -17
  193. package/dist/ui/shared/app.js +21 -14
  194. package/dist/ui/shared/card-strings.js +4 -0
  195. package/dist/ui/shared/effect-app-runtime.js +68 -19
  196. package/dist/ui/shared/effect-chat-runtime.js +16 -3
  197. package/dist/ui/shared/effect-frame.js +26 -7
  198. package/dist/ui/shared/effect-geometry.js +24 -9
  199. package/dist/ui/shared/effect-policy.js +21 -7
  200. package/dist/ui/shared/effect-profiler.js +31 -9
  201. package/dist/ui/shared/effect-registry.js +87 -48
  202. package/dist/ui/shared/effect-stock-snapshot.js +9 -0
  203. package/dist/ui/shared/effect-stock-trust.js +62 -10
  204. package/dist/ui/shared/effect-trial-shadow.js +62 -0
  205. package/dist/ui/shared/effect-trial-source.js +1 -1
  206. package/dist/ui/shared/effect-trial-stage.js +54 -31
  207. package/dist/ui/shared/effect-trial.css +40 -0
  208. package/dist/ui/shared/fx-motion.js +67 -0
  209. package/dist/ui/shared/identity-card.js +75 -4
  210. package/dist/ui/shared/identity-color.js +3 -3
  211. package/dist/ui/shared/instance-nav.js +194 -28
  212. package/dist/ui/shared/panel-view.js +24 -4
  213. package/dist/ui/shared/tokens.css +9 -3
  214. package/dist/ui/shared/ui-icons.js +1 -0
  215. package/dist/ui/shared/usage-dialog.js +22 -2
  216. package/dist/ui/shared/view-strings.js +68 -2
  217. package/dist/ui/shared/web-authorize.js +151 -0
  218. package/dist/ui/signin.html +1 -1
  219. package/dist/ui/web-terminal/terminal.css +4 -4
  220. package/dist/usage/providers.d.ts +28 -0
  221. package/dist/usage/providers.js +126 -36
  222. package/dist/usage/providers.js.map +1 -1
  223. package/dist/usage-report.d.ts +158 -0
  224. package/dist/usage-report.js +226 -0
  225. package/dist/usage-report.js.map +1 -0
  226. package/dist/usage-rollup.d.ts +51 -0
  227. package/dist/usage-rollup.js +168 -0
  228. package/dist/usage-rollup.js.map +1 -0
  229. package/dist/view-api.d.ts +39 -0
  230. package/dist/view-api.js +65 -4
  231. package/dist/view-api.js.map +1 -1
  232. package/dist/web-ambient.d.ts +72 -0
  233. package/dist/web-ambient.js +161 -0
  234. package/dist/web-ambient.js.map +1 -0
  235. package/dist/web-api.d.ts +69 -5
  236. package/dist/web-api.js +362 -45
  237. package/dist/web-api.js.map +1 -1
  238. package/dist/web-appearance-policy.d.ts +4 -6
  239. package/dist/web-appearance-policy.js +5 -12
  240. package/dist/web-appearance-policy.js.map +1 -1
  241. package/dist/web-appearance-tools.d.ts +3 -0
  242. package/dist/web-appearance-tools.js +22 -3
  243. package/dist/web-appearance-tools.js.map +1 -1
  244. package/dist/web-appearance-view.d.ts +27 -2
  245. package/dist/web-appearance-view.js +129 -14
  246. package/dist/web-appearance-view.js.map +1 -1
  247. package/dist/web-appearance.d.ts +17 -3
  248. package/dist/web-appearance.js +82 -7
  249. package/dist/web-appearance.js.map +1 -1
  250. package/dist/web-asset-parts.d.ts +5 -10
  251. package/dist/web-asset-parts.js +3 -3
  252. package/dist/web-asset-parts.js.map +1 -1
  253. package/dist/web-auth.js +3 -1
  254. package/dist/web-auth.js.map +1 -1
  255. package/dist/web-card.d.ts +14 -0
  256. package/dist/web-card.js +40 -6
  257. package/dist/web-card.js.map +1 -1
  258. package/dist/web-channel-echo.d.ts +7 -1
  259. package/dist/web-channel-echo.js +6 -2
  260. package/dist/web-channel-echo.js.map +1 -1
  261. package/dist/web-chat-history.d.ts +50 -0
  262. package/dist/web-chat-history.js +87 -5
  263. package/dist/web-chat-history.js.map +1 -1
  264. package/dist/web-commands.d.ts +1 -0
  265. package/dist/web-effect-bootstrap.d.ts +12 -0
  266. package/dist/web-effect-bootstrap.js +49 -0
  267. package/dist/web-effect-bootstrap.js.map +1 -0
  268. package/dist/web-effect-bundle.js +46 -1
  269. package/dist/web-effect-bundle.js.map +1 -1
  270. package/dist/web-effect-playback.d.ts +30 -0
  271. package/dist/web-effect-playback.js +71 -0
  272. package/dist/web-effect-playback.js.map +1 -0
  273. package/dist/web-effect-profiles.d.ts +4 -4
  274. package/dist/web-effect-service.d.ts +11 -4
  275. package/dist/web-effect-service.js +28 -5
  276. package/dist/web-effect-service.js.map +1 -1
  277. package/dist/web-effect-tools.d.ts +1 -0
  278. package/dist/web-effect-tools.js +8 -5
  279. package/dist/web-effect-tools.js.map +1 -1
  280. package/dist/web-effect-trials.d.ts +4 -4
  281. package/dist/web-effect-trials.js +6 -6
  282. package/dist/web-effect-trials.js.map +1 -1
  283. package/dist/web-effect-upload.d.ts +2 -4
  284. package/dist/web-effect-upload.js +3 -8
  285. package/dist/web-effect-upload.js.map +1 -1
  286. package/dist/web-effect-worker.d.ts +1 -1
  287. package/dist/web-effect-worker.js +2 -2
  288. package/dist/web-effect-worker.js.map +1 -1
  289. package/dist/web-effect.d.ts +14 -0
  290. package/dist/web-effect.js +21 -0
  291. package/dist/web-effect.js.map +1 -1
  292. package/dist/web-guest-access.d.ts +125 -0
  293. package/dist/web-guest-access.js +234 -0
  294. package/dist/web-guest-access.js.map +1 -0
  295. package/dist/web-guest-rooms.d.ts +82 -0
  296. package/dist/web-guest-rooms.js +135 -0
  297. package/dist/web-guest-rooms.js.map +1 -0
  298. package/dist/web-identity-color.d.ts +1 -1
  299. package/dist/web-identity-color.js +2 -2
  300. package/dist/web-identity-color.js.map +1 -1
  301. package/dist/web-identity-palette.d.ts +1 -1
  302. package/dist/web-identity-palette.js +2 -2
  303. package/dist/web-identity-palette.js.map +1 -1
  304. package/dist/web-image-meta.d.ts +13 -0
  305. package/dist/web-image-meta.js +62 -0
  306. package/dist/web-image-meta.js.map +1 -1
  307. package/dist/web-login.d.ts +47 -0
  308. package/dist/web-login.js +115 -0
  309. package/dist/web-login.js.map +1 -1
  310. package/dist/web-mod-agent.d.ts +1 -1
  311. package/dist/web-mod-agent.js +12 -8
  312. package/dist/web-mod-agent.js.map +1 -1
  313. package/dist/web-mod-bundle.d.ts +14 -1
  314. package/dist/web-mod-bundle.js +68 -3
  315. package/dist/web-mod-bundle.js.map +1 -1
  316. package/dist/web-mod-export.d.ts +9 -0
  317. package/dist/web-mod-export.js +21 -0
  318. package/dist/web-mod-export.js.map +1 -1
  319. package/dist/web-mod-store.d.ts +7 -1
  320. package/dist/web-mod-store.js +21 -7
  321. package/dist/web-mod-store.js.map +1 -1
  322. package/dist/web-prefs.d.ts +14 -0
  323. package/dist/web-prefs.js +24 -4
  324. package/dist/web-prefs.js.map +1 -1
  325. package/dist/web-preview.d.ts +6 -2
  326. package/dist/web-preview.js +4 -2
  327. package/dist/web-preview.js.map +1 -1
  328. package/dist/web-returning-device.d.ts +41 -0
  329. package/dist/web-returning-device.js +57 -5
  330. package/dist/web-returning-device.js.map +1 -1
  331. package/dist/web-session-authorization.d.ts +132 -0
  332. package/dist/web-session-authorization.js +193 -0
  333. package/dist/web-session-authorization.js.map +1 -0
  334. package/dist/web-session.d.ts +70 -10
  335. package/dist/web-session.js +137 -22
  336. package/dist/web-session.js.map +1 -1
  337. package/dist/web-settings-patch.d.ts +49 -2
  338. package/dist/web-settings-patch.js +145 -1
  339. package/dist/web-settings-patch.js.map +1 -1
  340. package/dist/web-settings-tools.d.ts +89 -0
  341. package/dist/web-settings-tools.js +270 -0
  342. package/dist/web-settings-tools.js.map +1 -0
  343. package/dist/web-shell-routes.d.ts +1 -1
  344. package/dist/web-shell-routes.js +1 -1
  345. package/dist/web-shell-routes.js.map +1 -1
  346. package/dist/web-theme-store.d.ts +39 -0
  347. package/dist/web-theme-store.js +122 -11
  348. package/dist/web-theme-store.js.map +1 -1
  349. package/dist/web-theme.d.ts +2 -0
  350. package/dist/web-theme.js +10 -1
  351. package/dist/web-theme.js.map +1 -1
  352. package/dist/web-upload.d.ts +13 -0
  353. package/dist/web-upload.js +39 -3
  354. package/dist/web-upload.js.map +1 -1
  355. package/dist/workflow-templates/default.md +1 -1
  356. package/package.json +1 -1
  357. package/templates/systemd.service.ejs +8 -0
@@ -40,6 +40,8 @@ register("view", {
40
40
  "usageMinutes": "{n} min",
41
41
  "usageHours": "{n} h",
42
42
  "usageRefresh": "Refresh",
43
+ "usageByAgent": "By agent →",
44
+ "usageByAgentTitle": "Each agent's tokens and API-equivalent (Fleet → Usage)",
43
45
  "usageHigh": "high",
44
46
  "usageNear": "near limit",
45
47
  "usageMoveUp": "Move up",
@@ -51,6 +53,16 @@ register("view", {
51
53
  "tooltipEffort": "Effort: {value}",
52
54
  "tooltipCost": "Cost: {value}",
53
55
  "tooltipUnavailable": "Unavailable",
56
+ "srcConfigured": " (configured)",
57
+ "srcFleetDefault": " (fleet default)",
58
+ "modelDefault": "default",
59
+ "modelDefaultWhy": "default ({reason})",
60
+ "modelCliDefault": "{model} (default)",
61
+ "modelWhyStart": "detected when it starts",
62
+ "modelWhyCli": "this CLI does not report a default",
63
+ "groupOther": "Other",
64
+ "groupTeam": "Team",
65
+ "groupTag": "Tag",
54
66
  "statusRunning": "Running",
55
67
  "statusPaused": "Paused",
56
68
  "statusStopped": "Stopped",
@@ -162,6 +174,9 @@ register("view", {
162
174
  "cardBio": "Bio (up to 280 characters, 3 line breaks)",
163
175
  "cardPortrait": "Portrait",
164
176
  "cardBanner": "Banner",
177
+ "cardBannerFocus": "Banner focus",
178
+ "cardBannerFocusNote": "What stays in view when the banner is cropped: the dashed area shows on every screen and card size; the yellow one is a phone's medium card.",
179
+ "cardFocusFollow": "Follow the agent's",
165
180
  "cardRemove": "Remove yours",
166
181
  "cardKeep": "Keep yours",
167
182
  "clearCard": "Clear card",
@@ -197,7 +212,25 @@ register("view", {
197
212
  "favUp": "Move {name} up",
198
213
  "favDown": "Move {name} down",
199
214
  "favFull": "Favorites are full ({max})",
200
- "favFiltered": "Clear the filter to reorder favorites"
215
+ "favFiltered": "Clear the filter to reorder favorites",
216
+ "favSorted": "Favorites are sorted by last reply — set them back to your order to move them",
217
+ "replyJustNow": "just now",
218
+ "replyMinutes": "{n} min ago",
219
+ "replyHours": "{n} h ago",
220
+ "replyYesterday": "yesterday",
221
+ "replyDays": "{n} d ago",
222
+ "replyTitle": "Last replied: {time}",
223
+ "sortTitle": "Sort the list",
224
+ "sortCustom": "Custom",
225
+ "sortCustomHint": "Your own order (drag rows); replies never move them",
226
+ "sortRecent": "Recent replies",
227
+ "sortRecentHint": "No groups: the last reply first",
228
+ "sortFavs": "Favorites, in Recent replies",
229
+ "sortFavsFixed": "Pinned on top, in your order",
230
+ "sortFavsRecent": "Sorted by last reply too",
231
+ "recentOthers": "Recent replies first",
232
+ "pinnedCurrent": "Current",
233
+ "pinnedLabel": "The instance you have open"
201
234
  },
202
235
  "zh-TW": {
203
236
  "editProfile": "編輯個人資料",
@@ -235,6 +268,8 @@ register("view", {
235
268
  "usageMinutes": "{n} 分鐘",
236
269
  "usageHours": "{n} 小時",
237
270
  "usageRefresh": "重新整理",
271
+ "usageByAgent": "依 agent →",
272
+ "usageByAgentTitle": "每個 agent 的 token 與 API 等值(Fleet → 用量)",
238
273
  "usageHigh": "偏高",
239
274
  "usageNear": "接近上限",
240
275
  "usageMoveUp": "上移",
@@ -246,6 +281,16 @@ register("view", {
246
281
  "tooltipEffort": "推理強度:{value}",
247
282
  "tooltipCost": "費用:{value}",
248
283
  "tooltipUnavailable": "無資料",
284
+ "srcConfigured": "(設定值)",
285
+ "srcFleetDefault": "(fleet 預設)",
286
+ "modelDefault": "預設",
287
+ "modelDefaultWhy": "預設({reason})",
288
+ "modelCliDefault": "{model}(預設)",
289
+ "modelWhyStart": "啟動時偵測",
290
+ "modelWhyCli": "這個 CLI 不提供預設值",
291
+ "groupOther": "其他",
292
+ "groupTeam": "團隊",
293
+ "groupTag": "標籤",
249
294
  "statusRunning": "運行中",
250
295
  "statusPaused": "已暫停",
251
296
  "statusStopped": "已停止",
@@ -357,6 +402,9 @@ register("view", {
357
402
  "cardBio": "簡介(最多 280 個字、3 次換行)",
358
403
  "cardPortrait": "肖像",
359
404
  "cardBanner": "橫幅",
405
+ "cardBannerFocus": "橫幅焦點",
406
+ "cardBannerFocusNote": "橫幅被裁切時保留在畫面中的部分:虛線框在所有螢幕與卡片大小都看得到;黃色框是手機上中卡片的範圍。",
407
+ "cardFocusFollow": "跟隨 agent 的設定",
360
408
  "cardRemove": "移除你的",
361
409
  "cardKeep": "保留你的",
362
410
  "clearCard": "清除卡片",
@@ -392,6 +440,24 @@ register("view", {
392
440
  "favUp": "把 {name} 上移",
393
441
  "favDown": "把 {name} 下移",
394
442
  "favFull": "最愛已滿({max} 個)",
395
- "favFiltered": "清除篩選後才能調整最愛順序"
443
+ "favFiltered": "清除篩選後才能調整最愛順序",
444
+ "favSorted": "最愛目前依最近回覆排序;改回「照你的順序」才能移動",
445
+ "replyJustNow": "剛剛",
446
+ "replyMinutes": "{n} 分鐘前",
447
+ "replyHours": "{n} 小時前",
448
+ "replyYesterday": "昨天",
449
+ "replyDays": "{n} 天前",
450
+ "replyTitle": "最後回覆:{time}",
451
+ "sortTitle": "排序方式",
452
+ "sortCustom": "自訂",
453
+ "sortCustomHint": "你拖曳排好的順序,有回覆也不會跳動",
454
+ "sortRecent": "最近回覆",
455
+ "sortRecentHint": "不分組,最後回覆的在上面",
456
+ "sortFavs": "最愛(最近回覆模式時)",
457
+ "sortFavsFixed": "固定在最上面,照你的順序",
458
+ "sortFavsRecent": "也依最近回覆排序",
459
+ "recentOthers": "最近回覆在上",
460
+ "pinnedCurrent": "目前",
461
+ "pinnedLabel": "你目前開著的 instance"
396
462
  }
397
463
  });
@@ -0,0 +1,151 @@
1
+ // #1939 S2b (design §0.1): "Authorize this browser". The app shows this browser's state — verified (until when), a
2
+ // Confirm card waiting in the account's Discord or Telegram DM (with its countdown), or why it can't be authorized here —
3
+ // and asks for the card. The card itself is the gate: nothing here can authorize anything.
4
+ import { html, useEffect, useRef, useState } from "./app-html.js";
5
+ import { register, t } from "./app-i18n.js";
6
+ import { guestPage } from "./app-store.js";
7
+
8
+ register("webauth", {
9
+ en: {
10
+ verified: "Verified · speaks as you until {0}",
11
+ notVerified: "Not verified",
12
+ authorize: "Authorize this browser",
13
+ waiting: "Waiting for your Confirm in Discord or Telegram ({0})",
14
+ sent: "Sent — confirm it in your DM within 5 minutes.",
15
+ publicLink: "Not on the temporary public link: open the dashboard directly to authorize.",
16
+ reason_disabled: "Web authority is turned off.",
17
+ reason_loopback_only: "Only this computer can authorize, and this browser is not on it.",
18
+ reason_no_account: "This browser was not signed in as an account (agend web): sign in with /web to authorize.",
19
+ reason_not_admin: "Your account is not a fleet admin here.",
20
+ reason_not_admin_session: "Guests can't authorize.",
21
+ reason_token_rotated: "The dashboard's token changed: sign in again.",
22
+ reason_public_link: "Not on the temporary public link.",
23
+ reason_not_session: "Sign in to authorize.",
24
+ reason_other: "This browser can't be authorized here.",
25
+ refusal_pending: "A request is already waiting in your DM.",
26
+ refusal_rate_limited: "Three requests this hour — try again in {0} min.",
27
+ refusal_dm_unavailable: "Couldn't send you a DM. Open your DMs with the bot, then try again.",
28
+ refusal_other: "That didn't work: {0}",
29
+ settings: "Settings",
30
+ },
31
+ "zh-TW": {
32
+ verified: "已驗證 · 以你的身分發言到 {0}",
33
+ notVerified: "未驗證",
34
+ authorize: "授權這個瀏覽器",
35
+ waiting: "等你在 Discord 或 Telegram 按下確認({0})",
36
+ sent: "已送出——請在 5 分鐘內到私訊確認。",
37
+ publicLink: "臨時公開連結上無法授權:請直接開啟儀表板再授權。",
38
+ reason_disabled: "網頁授權已關閉。",
39
+ reason_loopback_only: "只有這台電腦可以授權,而這個瀏覽器不在這台電腦上。",
40
+ reason_no_account: "這個瀏覽器不是用帳號登入的(agend web):請用 /web 登入後再授權。",
41
+ reason_not_admin: "你的帳號在這裡不是 fleet 管理者。",
42
+ reason_not_admin_session: "訪客無法授權。",
43
+ reason_token_rotated: "儀表板的 token 已變更:請重新登入。",
44
+ reason_public_link: "臨時公開連結上無法授權。",
45
+ reason_not_session: "登入後才能授權。",
46
+ reason_other: "這個瀏覽器在這裡無法授權。",
47
+ refusal_pending: "已經有一個請求在你的私訊中等待。",
48
+ refusal_rate_limited: "這一小時已請求三次——請 {0} 分鐘後再試。",
49
+ refusal_dm_unavailable: "無法私訊給你。請開放與 bot 的私訊後再試一次。",
50
+ refusal_other: "沒有成功:{0}",
51
+ settings: "設定",
52
+ },
53
+ });
54
+ const tn = (k, ...v) => t(`webauth.${k}`, ...v);
55
+ const REASONS = ["disabled", "loopback_only", "no_account", "not_admin", "not_admin_session", "token_rotated", "public_link", "not_session"];
56
+ // Until when: the time, with the day when it isn't today (an authorization lasts up to 48 h).
57
+ const clock = (ms) => {
58
+ const d = new Date(ms), today = new Date().toDateString() === d.toDateString();
59
+ return today ? d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
60
+ : d.toLocaleString([], { weekday: "short", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
61
+ };
62
+ const mmss = (ms) => { const s = Math.max(0, Math.ceil(ms / 1000)); return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`; };
63
+ const publicLink = () => globalThis.document?.body?.dataset?.publicLink === "1";
64
+
65
+ // One read per page load, shared by every line that shows it (the composer's, the session menu's): coming back to a chat
66
+ // reads nothing (#1374). It is read again only after an ask, and while a card is pending.
67
+ // An answer of any kind counts as this page's read (one that isn't a state hides the line); only a network failure lets a
68
+ // later mount try again. Signed out (401: the session ended, e.g. Not me), the line goes.
69
+ let shared = null, answered = false, inflight = null, signedOut = false;
70
+ async function readStatus() {
71
+ inflight ??= (async () => {
72
+ try {
73
+ const r = await fetch("/ui/authorization", { credentials: "same-origin" });
74
+ answered = true;
75
+ if (r.status === 401) { signedOut = true; shared = null; }
76
+ const body = r.ok ? await r.json().catch(() => null) : null;
77
+ if (body && typeof body.state === "string") shared = body;
78
+ } catch { /* the line stays as it was; a later mount may try again */ }
79
+ finally { inflight = null; }
80
+ return shared;
81
+ })();
82
+ return inflight;
83
+ }
84
+
85
+ /** This browser's authorization, kept fresh while a card is pending (every 3 s, until it is decided or expires). */
86
+ export function useWebAuthorization() {
87
+ const [st, setSt] = useState(shared), [notice, setNotice] = useState(null);
88
+ const [deadline, setDeadline] = useState(0), [now, setNow] = useState(() => Date.now());
89
+ const alive = useRef(true);
90
+ const load = async () => {
91
+ if (publicLink() || guestPage()) return;
92
+ const body = await readStatus();
93
+ if (!alive.current) return;
94
+ if (signedOut) { setSt(null); setNotice(null); return; }
95
+ if (!body) return;
96
+ setSt(body);
97
+ if (body.state !== "pending") setNotice((n) => (n === tn("sent") ? null : n)); // "Sent —" was about the card
98
+ if (body.state === "pending" && typeof body.expiresInMs === "number") setDeadline(Date.now() + body.expiresInMs);
99
+ };
100
+ useEffect(() => { alive.current = true; if (!answered) load(); return () => { alive.current = false; }; }, []);
101
+ useEffect(() => {
102
+ if (!st || st.state !== "pending") return undefined;
103
+ const tick = setInterval(() => setNow(Date.now()), 1000), poll = setInterval(load, 3000);
104
+ return () => { clearInterval(tick); clearInterval(poll); };
105
+ }, [st && st.state]);
106
+ // "Verified · until X" is read again at X (a page left open must not keep saying verified after it ends).
107
+ useEffect(() => {
108
+ if (!st || st.state !== "verified" || typeof st.until !== "number") return undefined;
109
+ const at = setTimeout(() => { resetWebAuthorization(); load(); }, Math.max(0, Math.min(st.until - Date.now() + 1000, 2 ** 31 - 1)));
110
+ return () => clearTimeout(at);
111
+ }, [st && st.state, st && st.until]);
112
+ const ask = async () => {
113
+ setNotice(null);
114
+ let r = null, body = null;
115
+ try { r = await fetch("/ui/authorization", { method: "POST", credentials: "same-origin", headers: { "content-type": "application/json" }, body: "{}" }); body = await r.json(); } catch { body = null; }
116
+ if (r && r.ok && body && body.ok) { setNotice(tn("sent")); setSt(shared = { state: "pending", expiresInMs: body.expiresInMs }); setDeadline(Date.now() + body.expiresInMs); return; }
117
+ const refusal = body && body.refusal;
118
+ if (refusal === "rate_limited") setNotice(tn("refusal_rate_limited", Math.max(1, Math.ceil((body.retryAfterMs || 0) / 60_000))));
119
+ else if (refusal === "pending" || refusal === "dm_unavailable") setNotice(tn(`refusal_${refusal}`));
120
+ else if (REASONS.includes(refusal)) setNotice(tn(`reason_${refusal}`));
121
+ else setNotice(tn("refusal_other", String(refusal || (r && r.status) || "?")));
122
+ load();
123
+ };
124
+ return { st, notice, ask, left: deadline - now };
125
+ }
126
+
127
+ /**
128
+ * Forget the page's one read, so the next line that mounts reads again: Settings → Web calls it when it shows (a revoke
129
+ * or a changed switch there changes what the line should say). Tests use it too: each case is its own page load.
130
+ */
131
+ export function resetWebAuthorization() { shared = null; answered = false; inflight = null; signedOut = false; }
132
+
133
+ /** The line under the composer: verified, waiting, or "Not verified" with the button (or why not, with the fix). */
134
+ export function AuthorizeChip() {
135
+ const a = useWebAuthorization();
136
+ if (guestPage()) return null;
137
+ if (publicLink()) return html`<p class="webauth-chip note" data-state="public">${tn("publicLink")}</p>`;
138
+ if (!a.st) return null;
139
+ return html`<div class="webauth-chip" data-state=${a.st.state}>${body(a)}${a.notice ? html` <span class="note" role="status">${a.notice}</span>` : null}</div>`;
140
+ }
141
+
142
+ function body(a) {
143
+ const st = a.st;
144
+ if (st.state === "verified") return html`<span class="webauth-ok">✓ ${tn("verified", clock(st.until))}</span>`;
145
+ if (st.state === "pending") return html`<span class="note">${tn("waiting", mmss(a.left))}</span>`;
146
+ if (st.reason === "not_authorized" || st.reason === "authorization_expired")
147
+ return html`<span class="note">${tn("notVerified")} ·</span> <button type="button" class="btn btn-sm" onClick=${a.ask}>${tn("authorize")}</button>`;
148
+ const why = REASONS.includes(st.reason) ? tn(`reason_${st.reason}`) : tn("reason_other");
149
+ // The fix, where there is one the person can take here: the switch lives in Settings → Web.
150
+ return html`<span class="note">${tn("notVerified")}: ${why}</span>${st.reason === "disabled" || st.reason === "loopback_only" ? html` <a href="/settings/web">${tn("settings")}</a>` : null}`;
151
+ }
@@ -55,7 +55,7 @@
55
55
  <div id="msg" role="alert" aria-live="polite"></div>
56
56
  <div class="hint" data-i18n="hint">No code? Send /web in General, then press a sign-in button to get one.</div>
57
57
  <details class="help"><summary data-i18n="nobot">No chat bot?</summary><p data-i18n="nobot_body">Ask the operator to run <code>agend web --code</code> on the AgEnD computer.</p></details>
58
- <details class="help"><summary data-i18n="safe">Page looks wrong after signing in?</summary><p data-i18n="safe_body">Your custom CSS may be hiding something. Add ?safe=1 to the end of the address to load the page without it, then fix it or turn it off in Settings → Appearance.</p></details>
58
+ <details class="help"><summary data-i18n="safe">Page looks wrong after signing in?</summary><p data-i18n="safe_body">Your custom CSS may be hiding something. Add ?safe=1 to the end of the address to load the page without it, then fix it or turn it off in Settings → Web appearance.</p></details>
59
59
  </form>
60
60
  <noscript>This page needs JavaScript to sign you in.</noscript>
61
61
  </main>
@@ -1,4 +1,4 @@
1
- html, body { margin: 0; height: 100%; background: #111; color: #ddd; font: 14px system-ui, sans-serif; }
1
+ html, body { margin: 0; height: 100%; background: #111; color: #ddd; font: 14px system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", emoji; }
2
2
  body { display: flex; flex-direction: column; }
3
3
  #bar { display: flex; align-items: center; gap: 12px; padding: 6px 10px; background: #1c1c1c; border-bottom: 1px solid #333; }
4
4
  #bar #title { font-weight: 600; }
@@ -6,18 +6,18 @@ body { display: flex; flex-direction: column; }
6
6
  #bar button { background: #402; color: #fbb; border: 1px solid #733; border-radius: 4px; padding: 3px 10px; cursor: pointer; }
7
7
  #gate { max-width: 560px; margin: 40px auto; padding: 0 16px; line-height: 1.5; }
8
8
  #gate form { display: flex; gap: 8px; margin-top: 12px; }
9
- #gate input { flex: 1; font: 16px ui-monospace, monospace; letter-spacing: 1px; padding: 8px; background: #000; color: #fff; border: 1px solid #555; border-radius: 4px; }
9
+ #gate input { flex: 1; font: 16px ui-monospace, monospace, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", emoji; letter-spacing: 1px; padding: 8px; background: #000; color: #fff; border: 1px solid #555; border-radius: 4px; }
10
10
  #gate button { padding: 8px 16px; background: #246; color: #fff; border: 0; border-radius: 4px; cursor: pointer; }
11
11
  #gate-msg { color: #f88; min-height: 1.5em; }
12
12
  #term { flex: 1; min-height: 0; padding: 4px; overflow: hidden; } /* never drawn over the rows below it (#1242) */
13
13
  #code-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px 10px; background: #1c1c1c; border-top: 1px solid #333; }
14
14
  #code-row[hidden] { display: none; }
15
- #code-row input { flex: 1; min-width: 12em; font: 16px ui-monospace, monospace; padding: 8px; background: #000; color: #fff; border: 1px solid #555; border-radius: 4px; }
15
+ #code-row input { flex: 1; min-width: 12em; font: 16px ui-monospace, monospace, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", emoji; padding: 8px; background: #000; color: #fff; border: 1px solid #555; border-radius: 4px; }
16
16
  #code-row button { padding: 8px 16px; background: #246; color: #fff; border: 0; border-radius: 4px; cursor: pointer; }
17
17
  #code-msg { color: #9ad; min-height: 1.2em; }
18
18
  #code-hint { flex-basis: 100%; margin: 0; color: #aaa; font-size: 13px; }
19
19
  #keys { display: flex; flex-wrap: wrap; gap: 6px; padding: 6px; background: #1c1c1c; border-top: 1px solid #333; }
20
- #keys button { background: #222; color: #ddd; border: 1px solid #444; border-radius: 4px; padding: 6px 10px; font: 14px ui-monospace, monospace; cursor: pointer; }
20
+ #keys button { background: #222; color: #ddd; border: 1px solid #444; border-radius: 4px; padding: 6px 10px; font: 14px ui-monospace, monospace, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", emoji; cursor: pointer; }
21
21
  #done { padding: 10px; background: #1c1c1c; border-top: 1px solid #333; }
22
22
  #done.ok { color: #8d8; } #done.bad { color: #f88; }
23
23
  @media (min-width: 900px) { #keys { display: none; } }
@@ -34,6 +34,8 @@ export interface UsageMetric {
34
34
  export interface ProviderUsage {
35
35
  id: string;
36
36
  name: string;
37
+ /** #2014: the credential profile this row reads (its `name` ends in ` (<profile>)`); the page shows it on its own line. */
38
+ profile?: string;
37
39
  status: "ok" | "error" | "no-credentials";
38
40
  /** A non-metered subscription whose entitlement is unlimited (not 0/100%). */
39
41
  unlimited?: boolean;
@@ -155,6 +157,8 @@ interface AgyBucket {
155
157
  remainingFraction?: number;
156
158
  resetTime?: string;
157
159
  group?: string;
160
+ /** Not on the summary's wire: the model's `modelProvider` (MODEL_PROVIDER_GOOGLE …), copied on by agyModelBuckets. */
161
+ provider?: string;
158
162
  }
159
163
  interface AgyQuotaSummary {
160
164
  groups?: {
@@ -166,6 +170,28 @@ interface AgyQuotaSummary {
166
170
  /** The summary's buckets, each tagged with its group's name. The top-level list (deprecated in the proto) only counts
167
171
  * when no group carries any bucket. */
168
172
  export declare function agyQuotaBuckets(body: AgyQuotaSummary): AgyBucket[];
173
+ /** `fetchAvailableModels`: each model's own quota (agy's proto: ModelDetails.quotaInfo). */
174
+ interface AgyModels {
175
+ models?: Record<string, {
176
+ displayName?: string;
177
+ modelProvider?: string;
178
+ quotaInfo?: {
179
+ remainingFraction?: number;
180
+ resetTime?: string;
181
+ };
182
+ }>;
183
+ }
184
+ /**
185
+ * Each model's quota as a per-model bucket. Only models with a reset time count: agy's internal models (tab completion,
186
+ * `chat_…`) report a bare 1 with none, which is no limit anyone runs into.
187
+ */
188
+ export declare function agyModelBuckets(body: AgyModels): AgyBucket[];
189
+ /**
190
+ * The buckets to show: the summary's, then — for each pool the summary does not name — the models' own quotas
191
+ * (#2174). agy 1.4 (2026-10-19, this machine's live account) answered the summary with only the "Claude and GPT
192
+ * models" group while every Gemini model still had a quota of its own, so the Gemini pool vanished from the row.
193
+ */
194
+ export declare function agyUsageBuckets(summary: AgyQuotaSummary, models: AgyModels | null): AgyBucket[];
169
195
  /**
170
196
  * Pool the summary's buckets into one metric per pool and limit.
171
197
  *
@@ -197,6 +223,7 @@ interface AgyStoredToken {
197
223
  * uses) is read alongside `expiry` (RFC3339) for the same reason.
198
224
  */
199
225
  export declare function readAgyToken(file: unknown): AgyStoredToken;
226
+ export declare function fetchAntigravityUsage(): Promise<Omit<ProviderUsage, "id" | "name">>;
200
227
  /**
201
228
  * When the soonest of the still-available reset tickets expires (#1244): min(expires_at) over the tickets whose status
202
229
  * is "available", skipping ones with no expiry or one already past. null when there is none — or when the list cannot
@@ -221,6 +248,7 @@ export declare function fetchKiroUsage(storeHome?: string): Promise<Omit<Provide
221
248
  type UsageProvider = {
222
249
  id: string;
223
250
  name: string;
251
+ profile?: string;
224
252
  fetch: (storeHome?: string, scope?: ClaudeUsageScope) => Promise<Omit<ProviderUsage, "id" | "name">>;
225
253
  };
226
254
  type UsageFleetConfig = Parameters<typeof listConfiguredProfiles>[0];
@@ -555,13 +555,39 @@ export function agyQuotaBuckets(body) {
555
555
  const grouped = (body.groups ?? []).flatMap(g => (g.buckets ?? []).map(b => ({ ...b, group: g.displayName?.trim() || undefined })));
556
556
  return grouped.length ? grouped : (body.buckets ?? []);
557
557
  }
558
+ /**
559
+ * Each model's quota as a per-model bucket. Only models with a reset time count: agy's internal models (tab completion,
560
+ * `chat_…`) report a bare 1 with none, which is no limit anyone runs into.
561
+ */
562
+ export function agyModelBuckets(body) {
563
+ return Object.entries(body.models ?? {}).flatMap(([id, m]) => m?.quotaInfo?.resetTime
564
+ ? [{ bucketId: id, remainingFraction: m.quotaInfo.remainingFraction, resetTime: m.quotaInfo.resetTime, provider: m.modelProvider }]
565
+ : []);
566
+ }
567
+ /**
568
+ * The buckets to show: the summary's, then — for each pool the summary does not name — the models' own quotas
569
+ * (#2174). agy 1.4 (2026-10-19, this machine's live account) answered the summary with only the "Claude and GPT
570
+ * models" group while every Gemini model still had a quota of its own, so the Gemini pool vanished from the row.
571
+ */
572
+ export function agyUsageBuckets(summary, models) {
573
+ const fromSummary = agyQuotaBuckets(summary);
574
+ const named = new Set(fromSummary.map(b => { const id = agyBucketId(b); return id ? agyPool(b, id) : null; }));
575
+ return [...fromSummary, ...(models ? agyModelBuckets(models).filter(b => !named.has(agyPool(b, b.bucketId))) : [])];
576
+ }
577
+ function agyBucketId(b) {
578
+ return b.bucketId?.trim() || (b.group ? b.displayName?.trim() : undefined);
579
+ }
558
580
  const AGY_POOLED = {
559
581
  "gemini-5h": { pool: "Gemini", window: "session" },
560
582
  "gemini-weekly": { pool: "Gemini", window: "weekly" },
561
583
  "3p-5h": { pool: "Claude & others", window: "session" },
562
584
  "3p-weekly": { pool: "Claude & others", window: "weekly" },
563
585
  };
564
- /** The pool a bucket belongs to: its group when the summary names one (#1898), else a guess from the bucket id. */
586
+ /**
587
+ * The pool a bucket belongs to: its group when the summary names one (#1898), else its model's provider, else its id.
588
+ * One nobody recognises is a pool of its own under its own name — never folded into another, so a vendor rename shows
589
+ * up as a new row instead of hiding one (#2174).
590
+ */
565
591
  function agyPool(b, id) {
566
592
  const pooled = AGY_POOLED[id];
567
593
  if (pooled)
@@ -574,7 +600,20 @@ function agyPool(b, id) {
574
600
  return "Claude & others";
575
601
  return group;
576
602
  }
577
- return id.startsWith("gemini") ? "Gemini" : "Claude & others";
603
+ const provider = b.provider?.replace(/^MODEL_PROVIDER_/, "");
604
+ if (provider) {
605
+ if (provider === "GOOGLE")
606
+ return "Gemini";
607
+ if (provider === "ANTHROPIC" || provider === "OPENAI")
608
+ return "Claude & others";
609
+ if (provider !== "UNSPECIFIED")
610
+ return provider.replace(/_/g, " ");
611
+ }
612
+ if (/^gemini/i.test(id))
613
+ return "Gemini";
614
+ if (/^(claude|gpt|3p)\b/i.test(id))
615
+ return "Claude & others";
616
+ return id;
578
617
  }
579
618
  /** The limit a bucket counts against ("Five Hour Limit", window "5h" …), or null for a per-model bucket. */
580
619
  function agyWindow(b, id) {
@@ -626,7 +665,7 @@ function agyLabel(pool, window) {
626
665
  export function agyPoolMetrics(buckets) {
627
666
  const entries = new Map();
628
667
  for (const b of buckets) {
629
- const id = b.bucketId?.trim() || (b.group ? b.displayName?.trim() : undefined);
668
+ const id = agyBucketId(b);
630
669
  if (!id || b.disabled === true || typeof b.remainingFraction !== "number" || !Number.isFinite(b.remainingFraction))
631
670
  continue;
632
671
  const pool = agyPool(b, id);
@@ -737,7 +776,48 @@ async function agyAccessToken() {
737
776
  return { error: "Could not reach oauth2.googleapis.com.", errorI18n: i18n("usage.error.unreachable", "oauth2.googleapis.com") };
738
777
  }
739
778
  }
740
- async function fetchAntigravityUsage() {
779
+ /**
780
+ * The models' own quotas (`fetchAvailableModels`, the list agy's model picker reads), from the same base as the
781
+ * summary. Only what the summary leaves out is taken from it, so a failure here costs nothing: null. Optional, so it
782
+ * never costs the row the summary already in hand (#2176 review, as the Codex ticket list, #1246): once the summary
783
+ * has answered it gets only what is left before the provider deadline, less a margin, and is then cancelled.
784
+ */
785
+ const AGY_MODELS_DEADLINE_MARGIN_MS = 1_000;
786
+ function agyModels(base, token) {
787
+ const abort = new AbortController();
788
+ const result = (async () => {
789
+ const res = await fetch(`${base}/v1internal:fetchAvailableModels`, {
790
+ method: "POST",
791
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", "User-Agent": "antigravity" },
792
+ body: "{}",
793
+ signal: AbortSignal.any([abort.signal, AbortSignal.timeout(LONGEST_SINGLE_FETCH_MS)]),
794
+ });
795
+ if (!res.ok)
796
+ return null;
797
+ const body = await res.json();
798
+ return body && typeof body === "object" ? body : null;
799
+ })().catch(() => null);
800
+ return {
801
+ cancel: () => abort.abort(),
802
+ async within(budgetMs) {
803
+ // No time left: an answer already in hand still counts (it settles first), nothing is waited for.
804
+ if (!(budgetMs > 0))
805
+ return Promise.race([result, Promise.resolve(null)]);
806
+ let timer;
807
+ // The timer settles the race, so a fetch that ignores its signal cannot hold the row; the caller then cancels it.
808
+ const timedOut = new Promise(resolve => { timer = setTimeout(() => resolve(null), budgetMs); });
809
+ try {
810
+ return await Promise.race([result, timedOut]);
811
+ }
812
+ finally {
813
+ clearTimeout(timer);
814
+ }
815
+ },
816
+ };
817
+ }
818
+ export async function fetchAntigravityUsage() {
819
+ // Monotonic: the deadline the models' budget is measured against is a timer, which a wall-clock change does not move.
820
+ const startedAt = performance.now();
741
821
  // A CLI-observed rate limit outranks anything the quota API says. The bucket
742
822
  // summary cannot see the account-level individual cap: verified live, it
743
823
  // reported every bucket 0% used in the same minute the CLI was refusing work
@@ -763,38 +843,47 @@ async function fetchAntigravityUsage() {
763
843
  if ("error" in auth)
764
844
  return { status: "error", error: auth.error, errorI18n: auth.errorI18n, metrics: [] };
765
845
  for (const base of AGY_CLOUDCODE_URLS) {
766
- let res;
846
+ // Alongside the summary, not after it: one more round trip in the chain would push a slow-but-healthy pair past
847
+ // the provider deadline. Cancelled when the summary fails (it never rejects).
848
+ const models = agyModels(base, auth.token);
767
849
  try {
768
- res = await fetch(`${base}/v1internal:retrieveUserQuotaSummary`, {
769
- method: "POST",
770
- headers: {
771
- Authorization: `Bearer ${auth.token}`,
772
- "Content-Type": "application/json",
773
- "User-Agent": "antigravity",
774
- },
775
- body: "{}",
776
- signal: AbortSignal.timeout(LONGEST_SINGLE_FETCH_MS),
777
- });
778
- }
779
- catch {
780
- continue;
781
- }
782
- if (res.status === 401 || res.status === 403) {
783
- return { status: "error", error: "Token rejected. Run `agy` once to refresh the login.", errorI18n: i18n("usage.error.token_rejected", "agy"), metrics: [] };
784
- }
785
- if (!res.ok)
786
- continue; // try the other base
787
- let body;
788
- try {
789
- body = await res.json();
850
+ let res;
851
+ try {
852
+ res = await fetch(`${base}/v1internal:retrieveUserQuotaSummary`, {
853
+ method: "POST",
854
+ headers: {
855
+ Authorization: `Bearer ${auth.token}`,
856
+ "Content-Type": "application/json",
857
+ "User-Agent": "antigravity",
858
+ },
859
+ body: "{}",
860
+ signal: AbortSignal.timeout(LONGEST_SINGLE_FETCH_MS),
861
+ });
862
+ }
863
+ catch {
864
+ continue;
865
+ }
866
+ if (res.status === 401 || res.status === 403) {
867
+ return { status: "error", error: "Token rejected. Run `agy` once to refresh the login.", errorI18n: i18n("usage.error.token_rejected", "agy"), metrics: [] };
868
+ }
869
+ if (!res.ok)
870
+ continue; // try the other base
871
+ let body;
872
+ try {
873
+ body = await res.json();
874
+ }
875
+ catch {
876
+ continue;
877
+ }
878
+ const budgetMs = providerDeadlineMs - (performance.now() - startedAt) - AGY_MODELS_DEADLINE_MARGIN_MS;
879
+ const metrics = agyPoolMetrics(agyUsageBuckets(body ?? {}, await models.within(budgetMs)));
880
+ return metrics.length
881
+ ? { status: "ok", plan: null, metrics }
882
+ : { status: "ok", plan: null, hint: "No quota information reported for this account.", hintI18n: i18n("usage.hint.no_quota"), metrics: [] };
790
883
  }
791
- catch {
792
- continue;
884
+ finally {
885
+ models.cancel(); // a base given up on, or a read already settled: nothing left to wait for
793
886
  }
794
- const metrics = agyPoolMetrics(agyQuotaBuckets(body ?? {}));
795
- return metrics.length
796
- ? { status: "ok", plan: null, metrics }
797
- : { status: "ok", plan: null, hint: "No quota information reported for this account.", hintI18n: i18n("usage.hint.no_quota"), metrics: [] };
798
887
  }
799
888
  return { status: "error", error: "Could not reach the Cloud Code quota endpoints.", errorI18n: i18n("usage.error.unreachable", "Cloud Code quota endpoints"), metrics: [] };
800
889
  }
@@ -1458,6 +1547,7 @@ export function providersForConfig(config, base = DEFAULT_PROVIDERS) {
1458
1547
  rows.push(...profiles.map(profile => ({
1459
1548
  id: `${provider.id}:${profile}`,
1460
1549
  name: `${provider.name} (${profile})`,
1550
+ profile,
1461
1551
  fetch: () => provider.fetch(credentialProfileStoreHome(getAgendHome(), backendName, profile), { profile }),
1462
1552
  })));
1463
1553
  return rows;
@@ -1644,16 +1734,16 @@ export async function fetchAllUsage(config) {
1644
1734
  // Fail soft: this row says it could not be read, the rest still answer,
1645
1735
  // and the snapshot returns in bounded time.
1646
1736
  return {
1647
- id: p.id, name: p.name, status: "error",
1737
+ id: p.id, name: p.name, ...(p.profile ? { profile: p.profile } : {}), status: "error",
1648
1738
  error: `Could not reach ${p.name}.`,
1649
1739
  errorI18n: i18n("usage.error.unreachable", p.name),
1650
1740
  metrics: [],
1651
1741
  };
1652
1742
  }
1653
- return { id: p.id, name: p.name, ...outcome };
1743
+ return { id: p.id, name: p.name, ...(p.profile ? { profile: p.profile } : {}), ...outcome };
1654
1744
  }
1655
1745
  catch (err) {
1656
- return { id: p.id, name: p.name, status: "error", error: String(err?.message ?? err), metrics: [] };
1746
+ return { id: p.id, name: p.name, ...(p.profile ? { profile: p.profile } : {}), status: "error", error: String(err?.message ?? err), metrics: [] };
1657
1747
  }
1658
1748
  }));
1659
1749
  return {