@maudecode/talaria-web 1.15.0 → 1.16.1

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 (312) hide show
  1. package/_release.json +4 -4
  2. package/dist/api/automation-router.d.ts +21 -7
  3. package/dist/api/automation-router.js +3 -0
  4. package/dist/api/automation-router.js.map +1 -1
  5. package/dist/api/chat-router.d.ts +142 -6
  6. package/dist/api/chat-router.js +11 -5
  7. package/dist/api/chat-router.js.map +1 -1
  8. package/dist/api/git-router.js +44 -32
  9. package/dist/api/git-router.js.map +1 -1
  10. package/dist/api/raw-routes.js +5 -58
  11. package/dist/api/raw-routes.js.map +1 -1
  12. package/dist/api/router.d.ts +4 -2
  13. package/dist/api/router.js +8 -8
  14. package/dist/api/router.js.map +1 -1
  15. package/dist/api/sessions-router.d.ts +714 -42
  16. package/dist/api/sessions-router.js +57 -23
  17. package/dist/api/sessions-router.js.map +1 -1
  18. package/dist/api/settings-router.d.ts +153 -9
  19. package/dist/api/settings-router.js +61 -16
  20. package/dist/api/settings-router.js.map +1 -1
  21. package/dist/api/sse-routes.js +1 -1
  22. package/dist/api/sse-routes.js.map +1 -1
  23. package/dist/api/tools-router.d.ts +13 -1
  24. package/dist/api/tools-router.js +10 -8
  25. package/dist/api/tools-router.js.map +1 -1
  26. package/dist/app.d.ts +1047 -67
  27. package/dist/auth/gate.js +8 -4
  28. package/dist/auth/gate.js.map +1 -1
  29. package/dist/auth/oidc.d.ts +6 -2
  30. package/dist/auth/oidc.js +16 -11
  31. package/dist/auth/oidc.js.map +1 -1
  32. package/dist/auth/store.d.ts +14 -2
  33. package/dist/auth/store.js +26 -1
  34. package/dist/auth/store.js.map +1 -1
  35. package/dist/cli/dotenv.d.ts +1 -1
  36. package/dist/cli/dotenv.js +36 -3
  37. package/dist/cli/dotenv.js.map +1 -1
  38. package/dist/http/addresses.d.ts +6 -0
  39. package/dist/http/addresses.js +26 -0
  40. package/dist/http/addresses.js.map +1 -1
  41. package/dist/http/context.d.ts +3 -3
  42. package/dist/links.d.ts +26 -0
  43. package/dist/links.js +60 -0
  44. package/dist/links.js.map +1 -0
  45. package/dist/onboarding.js +26 -7
  46. package/dist/onboarding.js.map +1 -1
  47. package/dist/profiles/profiles.js +1 -1
  48. package/dist/profiles/profiles.js.map +1 -1
  49. package/dist/providers/catalog.d.ts +74 -1
  50. package/dist/providers/catalog.js +248 -42
  51. package/dist/providers/catalog.js.map +1 -1
  52. package/dist/providers/tables.d.ts +3 -1
  53. package/dist/providers/tables.js +9 -4
  54. package/dist/providers/tables.js.map +1 -1
  55. package/dist/redact.js +493 -13
  56. package/dist/redact.js.map +1 -1
  57. package/dist/runtime.js +52 -28
  58. package/dist/runtime.js.map +1 -1
  59. package/dist/sessions/anchor.d.ts +22 -3
  60. package/dist/sessions/anchor.js +87 -42
  61. package/dist/sessions/anchor.js.map +1 -1
  62. package/dist/sessions/background-tasks.d.ts +4 -1
  63. package/dist/sessions/background-tasks.js +7 -4
  64. package/dist/sessions/background-tasks.js.map +1 -1
  65. package/dist/sessions/completions.js +1 -1
  66. package/dist/sessions/completions.js.map +1 -1
  67. package/dist/sessions/list.d.ts +8 -4
  68. package/dist/sessions/list.js +24 -17
  69. package/dist/sessions/list.js.map +1 -1
  70. package/dist/sessions/merge.d.ts +70 -7
  71. package/dist/sessions/merge.js +331 -23
  72. package/dist/sessions/merge.js.map +1 -1
  73. package/dist/sessions/pending.d.ts +5 -0
  74. package/dist/sessions/pending.js +30 -10
  75. package/dist/sessions/pending.js.map +1 -1
  76. package/dist/sessions/service.d.ts +57 -3
  77. package/dist/sessions/service.js +327 -56
  78. package/dist/sessions/service.js.map +1 -1
  79. package/dist/sessions/session.d.ts +6 -1
  80. package/dist/sessions/session.js +6 -0
  81. package/dist/sessions/session.js.map +1 -1
  82. package/dist/sessions/shares.js +11 -2
  83. package/dist/sessions/shares.js.map +1 -1
  84. package/dist/sessions/store.js +1 -1
  85. package/dist/sessions/store.js.map +1 -1
  86. package/dist/sessions/turn.d.ts +19 -1
  87. package/dist/sessions/turn.js +218 -113
  88. package/dist/sessions/turn.js.map +1 -1
  89. package/dist/settings.js +9 -0
  90. package/dist/settings.js.map +1 -1
  91. package/dist/sidecar/fake.js +2 -0
  92. package/dist/sidecar/fake.js.map +1 -1
  93. package/dist/tools/commands.d.ts +15 -0
  94. package/dist/tools/commands.js +52 -0
  95. package/dist/tools/commands.js.map +1 -0
  96. package/dist/tools/crons.d.ts +4 -1
  97. package/dist/tools/crons.js +58 -11
  98. package/dist/tools/crons.js.map +1 -1
  99. package/dist/tools/mcp.js +5 -1
  100. package/dist/tools/mcp.js.map +1 -1
  101. package/dist/tools/update-notifications.d.ts +1 -1
  102. package/dist/tools/update-notifications.js +1 -1
  103. package/dist/tools/update-notifications.js.map +1 -1
  104. package/dist/tools/updates.d.ts +8 -3
  105. package/dist/tools/updates.js +133 -34
  106. package/dist/tools/updates.js.map +1 -1
  107. package/dist/workspace/git.d.ts +57 -10
  108. package/dist/workspace/git.js +302 -195
  109. package/dist/workspace/git.js.map +1 -1
  110. package/dist/workspace/media-refs.d.ts +13 -0
  111. package/dist/workspace/media-refs.js +165 -0
  112. package/dist/workspace/media-refs.js.map +1 -0
  113. package/dist/workspace/media.d.ts +19 -0
  114. package/dist/workspace/media.js +61 -2
  115. package/dist/workspace/media.js.map +1 -1
  116. package/dist/workspace/workspaces.d.ts +7 -1
  117. package/dist/workspace/workspaces.js +30 -21
  118. package/dist/workspace/workspaces.js.map +1 -1
  119. package/dist/workspace/worktrees.d.ts +2 -4
  120. package/dist/workspace/worktrees.js +28 -33
  121. package/dist/workspace/worktrees.js.map +1 -1
  122. package/package.json +4 -2
  123. package/sidecar/scripts/replay_sidecar.py +3 -1
  124. package/sidecar/talaria_sidecar/__init__.py +1 -1
  125. package/sidecar/talaria_sidecar/home.py +59 -2
  126. package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
  127. package/sidecar/talaria_sidecar/methods/aux.py +19 -3
  128. package/sidecar/talaria_sidecar/methods/chat.py +129 -23
  129. package/sidecar/talaria_sidecar/methods/commands.py +46 -31
  130. package/sidecar/talaria_sidecar/methods/config.py +14 -10
  131. package/sidecar/talaria_sidecar/methods/oauth.py +439 -0
  132. package/sidecar/talaria_sidecar/methods/profiles.py +5 -4
  133. package/sidecar/talaria_sidecar/methods/providers.py +161 -1
  134. package/sidecar/talaria_sidecar/methods/runtime.py +2 -0
  135. package/sidecar/talaria_sidecar/methods/text.py +1 -20
  136. package/sidecar/talaria_sidecar/methods/usage.py +7 -1
  137. package/static/dist/FILES.txt +124 -120
  138. package/static/dist/assets/AppShell-ef3bhu32.js +1 -0
  139. package/static/dist/assets/Button-nx37f4u1.js +1 -0
  140. package/static/dist/assets/ChatPage-cdcpykcg.js +25 -0
  141. package/static/dist/assets/Dialog-mec42hvb.js +1 -0
  142. package/static/dist/assets/HubRoute-ecb65zpr.js +2 -0
  143. package/static/dist/assets/{Markdown-dy8t4dy7.js → Markdown-jqrx5es0.js} +125 -125
  144. package/static/dist/assets/RightPanel-d1lgvk4s.js +1 -0
  145. package/static/dist/assets/Select-h4ivl9qf.js +1 -0
  146. package/static/dist/assets/SessionListPanel-ewib046b.js +1 -0
  147. package/static/dist/assets/SettingsLayout-lx654cup.js +1 -0
  148. package/static/dist/assets/States-ehsb6lzo.js +1 -0
  149. package/static/dist/assets/Toaster-mch1pa3t.js +1 -0
  150. package/static/dist/assets/_app-m8d961ef.js +1 -0
  151. package/static/dist/assets/_app.ext._extensionId-mp33hbzi.js +1 -0
  152. package/static/dist/assets/_app.index-dk6zp7e0.js +1 -0
  153. package/static/dist/assets/_app.insights-jt2fk4i1.js +1 -0
  154. package/static/dist/assets/_app.kanban-if4pubeu.js +1 -0
  155. package/static/dist/assets/_app.logs-mfaw9m6g.js +1 -0
  156. package/static/dist/assets/_app.memory-h3eaarb6.js +1 -0
  157. package/static/dist/assets/_app.profiles-ienktovs.js +1 -0
  158. package/static/dist/assets/{_app.session._sessionId-gyoh5ylx.js → _app.session._sessionId-hh27wwu2.js} +1 -1
  159. package/static/dist/assets/_app.settings-dpoe0bzf.js +1 -0
  160. package/static/dist/assets/_app.settings._section-l468ynag.js +3 -0
  161. package/static/dist/assets/_app.skills-kcbncc8i.js +1 -0
  162. package/static/dist/assets/_app.tasks-occdrqhw.js +2 -0
  163. package/static/dist/assets/_app.todos-nnke2rcc.js +1 -0
  164. package/static/dist/assets/_app.workspaces-cdlatvwj.js +1 -0
  165. package/static/dist/assets/{abnfDiagram-VCTEODGH-cjqletra.js → abnfDiagram-VCTEODGH-oity9y4o.js} +1 -1
  166. package/static/dist/assets/architecture-7GRP2DOG-hj11333z.js +1 -0
  167. package/static/dist/assets/architectureDiagram-5GKGNRK7-ceaepnyr.js +36 -0
  168. package/static/dist/assets/{back-e4m6nsnm.js → back-eu64w89w.js} +1 -1
  169. package/static/dist/assets/{blockDiagram-I7D4REHJ-b195z021.js → blockDiagram-I7D4REHJ-n79st4pj.js} +1 -1
  170. package/static/dist/assets/{c4Diagram-7LVT6UL2-olu212k1.js → c4Diagram-7LVT6UL2-mi73akzk.js} +1 -1
  171. package/static/dist/assets/cancel-mzvkdjg8.js +1 -0
  172. package/static/dist/assets/channel-mbvmf2vv.js +1 -0
  173. package/static/dist/assets/{chunk-4HAMMTFA-b8o1nufs.js → chunk-4HAMMTFA-laat0g1q.js} +1 -1
  174. package/static/dist/assets/{chunk-75Z2AOVW-l1seddto.js → chunk-75Z2AOVW-0psy1tr2.js} +1 -1
  175. package/static/dist/assets/{chunk-DU6HZSFF-lo5zg15v.js → chunk-DU6HZSFF-lw8uox8x.js} +3 -3
  176. package/static/dist/assets/{chunk-F27PBJKO-dsdzrp16.js → chunk-F27PBJKO-erassib3.js} +1 -1
  177. package/static/dist/assets/{chunk-GMAD6QVW-f3fxqq8b.js → chunk-GMAD6QVW-fz7rx619.js} +1 -1
  178. package/static/dist/assets/{chunk-GVQU2GXP-h8egxtf6.js → chunk-GVQU2GXP-gdb0epb2.js} +1 -1
  179. package/static/dist/assets/{chunk-IMKFNOWR-iolwd8s0.js → chunk-IMKFNOWR-ea8t26qw.js} +1 -1
  180. package/static/dist/assets/{chunk-L3NEJ4N5-b88bbtwi.js → chunk-L3NEJ4N5-mb1gu9kz.js} +1 -1
  181. package/static/dist/assets/{chunk-OSK3NFVY-e3e7nr6s.js → chunk-OSK3NFVY-eovmccvy.js} +1 -1
  182. package/static/dist/assets/{chunk-P2QGCYS3-mdc46rt1.js → chunk-P2QGCYS3-bo55zjo5.js} +1 -1
  183. package/static/dist/assets/{chunk-POPQ4Y6H-kqlllrrm.js → chunk-POPQ4Y6H-ja7ohe4h.js} +1 -1
  184. package/static/dist/assets/{chunk-PWAF6VOD-he585k2x.js → chunk-PWAF6VOD-e30g9nus.js} +1 -1
  185. package/static/dist/assets/{chunk-SHT3W25Y-gh94y9a4.js → chunk-SHT3W25Y-gbb798f7.js} +3 -3
  186. package/static/dist/assets/{chunk-SVP7TREG-euxp9y7e.js → chunk-SVP7TREG-h2iw8urm.js} +1 -1
  187. package/static/dist/assets/{chunk-TICWLB2K-c8cxes1q.js → chunk-TICWLB2K-fd2cottz.js} +1 -1
  188. package/static/dist/assets/classDiagram-ZZMXUADV-o8r05k0n.js +1 -0
  189. package/static/dist/assets/classDiagram-v2-VYDZK3BY-o8r05k0n.js +1 -0
  190. package/static/dist/assets/{copied-kq0u3ht7.js → copied-h65fc3cx.js} +1 -1
  191. package/static/dist/assets/copy-e6977uwq.js +1 -0
  192. package/static/dist/assets/{cynefin-OW5HDTMX-lxd44ux4.js → cynefin-OW5HDTMX-lbltyl5j.js} +1 -1
  193. package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-njpiwat8.js → cynefinDiagram-5FMLGOSQ-lxhsqq4a.js} +1 -1
  194. package/static/dist/assets/{dagre-GXQ25YYZ-nvgy6yju.js → dagre-GXQ25YYZ-gn4nnaln.js} +4 -4
  195. package/static/dist/assets/{diagram-S7CK7UJ4-g98nyv72.js → diagram-S7CK7UJ4-pa3ij6q7.js} +3 -3
  196. package/static/dist/assets/{diagram-UQ7AKVKN-ovgm6lvv.js → diagram-UQ7AKVKN-zbipyz57.js} +1 -1
  197. package/static/dist/assets/{diagram-VSXAHHWV-pb2bn2e4.js → diagram-VSXAHHWV-d1kbpzgf.js} +1 -1
  198. package/static/dist/assets/{diagram-VX7I27RA-6j4mvfqo.js → diagram-VX7I27RA-i3ty14xl.js} +1 -1
  199. package/static/dist/assets/{diagram-Z3DM3KII-dqmb982r.js → diagram-Z3DM3KII-m8xefqa7.js} +1 -1
  200. package/static/dist/assets/{ebnfDiagram-PWID7BFC-ozbpzb6d.js → ebnfDiagram-PWID7BFC-nz7wj05l.js} +1 -1
  201. package/static/dist/assets/endpoints-k7yf25m0.js +64 -0
  202. package/static/dist/assets/{erDiagram-RLTQ6QDP-hl7m8vua.js → erDiagram-RLTQ6QDP-fa41ib83.js} +1 -1
  203. package/static/dist/assets/eventmodeling-NTZA5JFV-kpbq09bx.js +1 -0
  204. package/static/dist/assets/flowDiagram-HODETNUW-lcz0kx14.js +1 -0
  205. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ompixqmh.js → ganttDiagram-EL5Y4UJY-n7zq01qu.js} +1 -1
  206. package/static/dist/assets/{gitGraph-4MIJSDKK-b6vxwp9q.js → gitGraph-4MIJSDKK-c89emb07.js} +1 -1
  207. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-k98f57on.js → gitGraphDiagram-WWUBYQGX-cz7cgr6w.js} +1 -1
  208. package/static/dist/assets/{highlighted-body-KPVGNVTW-cd3ff6ge.js → highlighted-body-KPVGNVTW-d9bapmni.js} +1 -1
  209. package/static/dist/assets/{index-nqed3oum.js → index-h00usru2.js} +3 -3
  210. package/static/dist/assets/index-n73ijkv2.css +2 -0
  211. package/static/dist/assets/{info-A6RAGUB7-hjcwjami.js → info-A6RAGUB7-ndddhhhh.js} +1 -1
  212. package/static/dist/assets/infoDiagram-27XIBGKW-ccxigcpf.js +2 -0
  213. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-bxak4mwu.js → ishikawaDiagram-5VMMS53U-le0z9vtc.js} +1 -1
  214. package/static/dist/assets/{journeyDiagram-3NMN7TZE-khmihclr.js → journeyDiagram-3NMN7TZE-ivsmb26o.js} +1 -1
  215. package/static/dist/assets/{kanban-definition-UXKFOSKX-h1r32wdx.js → kanban-definition-UXKFOSKX-h6n2awt0.js} +1 -1
  216. package/static/dist/assets/lazyRouteComponent-m1rtyk4t.js +1 -0
  217. package/static/dist/assets/{line-friyjof3.js → line-dmzlfe0u.js} +1 -1
  218. package/static/dist/assets/link-budeg5t9.js +1 -0
  219. package/static/dist/assets/login-kjhasvp8.js +1 -0
  220. package/static/dist/assets/mermaid-HWGCJPDP-jb5d211j.js +1 -0
  221. package/static/dist/assets/{mermaid-parser.core-f1gvpwrt.js → mermaid-parser.core-gwyd5bnq.js} +3 -3
  222. package/static/dist/assets/{mindmap-definition-YA3MSWOX-mt2oxwue.js → mindmap-definition-YA3MSWOX-l68gg5h7.js} +1 -1
  223. package/static/dist/assets/onboarding-cdfrk0pj.js +1 -0
  224. package/static/dist/assets/{packet-AYTQ26CC-jik4ixlf.js → packet-AYTQ26CC-gpv06065.js} +1 -1
  225. package/static/dist/assets/{pegDiagram-XKGWAZYB-erxlv6cc.js → pegDiagram-XKGWAZYB-e52vs23u.js} +1 -1
  226. package/static/dist/assets/{pie-WAS4IAKB-mnkoldfl.js → pie-WAS4IAKB-l6m7nz5s.js} +1 -1
  227. package/static/dist/assets/{pieDiagram-E7YTZNPT-i7lwrada.js → pieDiagram-E7YTZNPT-b4is36bq.js} +3 -3
  228. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-fr8ejdoy.js → quadrantDiagram-AXDQQJYC-m8vt2788.js} +1 -1
  229. package/static/dist/assets/{radar-RG4KPBEZ-otarjte2.js → radar-RG4KPBEZ-nxbrhiov.js} +1 -1
  230. package/static/dist/assets/{railroad-74A4TZTK-lw362oto.js → railroad-74A4TZTK-222mu9hi.js} +1 -1
  231. package/static/dist/assets/railroad-abnf-HS5TGJTU-kdvgl6x8.js +1 -0
  232. package/static/dist/assets/railroad-ebnf-LZEXJU2U-itiqslby.js +1 -0
  233. package/static/dist/assets/railroad-peg-WCYAUIDC-go7kov78.js +1 -0
  234. package/static/dist/assets/{railroadDiagram-O6MQD6OU-esyn75pw.js → railroadDiagram-O6MQD6OU-o8lldfzv.js} +1 -1
  235. package/static/dist/assets/{refresh-cw-k66keho6.js → refresh-cw-kbcje1uy.js} +1 -1
  236. package/static/dist/assets/{requirementDiagram-BXWQKSXE-cyhi5u29.js → requirementDiagram-BXWQKSXE-ovyq38kv.js} +1 -1
  237. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-jljpbo0h.js → sankeyDiagram-P5KCCOFB-ou7lznor.js} +1 -1
  238. package/static/dist/assets/{saved-do49si3v.js → saved-bazmcup9.js} +1 -1
  239. package/static/dist/assets/search-jen74z4u.js +1 -0
  240. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-fz0dhlu7.js → sequenceDiagram-WJ2MYXX4-ovckdzf2.js} +1 -1
  241. package/static/dist/assets/{settings_section_extensions_title-b7lojn0o.js → settings_section_extensions_title-ertwwhlx.js} +1 -1
  242. package/static/dist/assets/share._token-dan9hgvj.js +2 -0
  243. package/static/dist/assets/{sliders-horizontal-c4wya4dy.js → sliders-horizontal-f9cb3lh3.js} +1 -1
  244. package/static/dist/assets/{stateDiagram-D77RDMKH-kdnithdx.js → stateDiagram-D77RDMKH-fcrexkkv.js} +1 -1
  245. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-lcfraua2.js +1 -0
  246. package/static/dist/assets/{swimlanes-42K2YHIH-esd6gmz3.js → swimlanes-42K2YHIH-f7usu8fw.js} +1 -1
  247. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-ogmanni0.js +8 -0
  248. package/static/dist/assets/{timeline-definition-24CTP7MA-g0qygccv.js → timeline-definition-24CTP7MA-gp1hvz9f.js} +1 -1
  249. package/static/dist/assets/todoStore-eqtid4r4.js +1 -0
  250. package/static/dist/assets/{treeView-Q6P3EWNA-ndchzkuu.js → treeView-Q6P3EWNA-gb9gf11u.js} +1 -1
  251. package/static/dist/assets/{treemap-WGGIJYW6-hwjxj0zz.js → treemap-WGGIJYW6-nuvfnvvi.js} +1 -1
  252. package/static/dist/assets/{untitled-mbr6tuhu.js → untitled-htelhc68.js} +1 -1
  253. package/static/dist/assets/useForm-h2t0u14e.js +1 -0
  254. package/static/dist/assets/useLocale-hcsb84pj.js +1 -0
  255. package/static/dist/assets/useNavigate-nwy70ztj.js +1 -0
  256. package/static/dist/assets/{useParams-lo9u33d1.js → useParams-dqd64jv7.js} +1 -1
  257. package/static/dist/assets/{useSearch-lq1mr090.js → useSearch-j30pvsy7.js} +1 -1
  258. package/static/dist/assets/{vennDiagram-4TSXK5OY-gubovd1r.js → vennDiagram-4TSXK5OY-fzchbzaj.js} +1 -1
  259. package/static/dist/assets/{wardley-WFR3VGLG-ir3om65j.js → wardley-WFR3VGLG-m47mhfbh.js} +1 -1
  260. package/static/dist/assets/{wardleyDiagram-VM6X3IG4-ce0or1ie.js → wardleyDiagram-VM6X3IG4-odg6ct1z.js} +2 -2
  261. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-dexylcjg.js → xychartDiagram-S5SC5T6Z-mrhms5um.js} +1 -1
  262. package/static/dist/index.html +1 -1
  263. package/static/dist/sw.js +1 -1
  264. package/static/dist/assets/AppShell-orat1s43.js +0 -1
  265. package/static/dist/assets/ChatPage-ojhka3hy.js +0 -22
  266. package/static/dist/assets/HubRoute-gxgw1t33.js +0 -2
  267. package/static/dist/assets/RightPanel-c66ani1s.js +0 -1
  268. package/static/dist/assets/Select-fswf66up.js +0 -1
  269. package/static/dist/assets/SessionListPanel-goh5mtt8.js +0 -1
  270. package/static/dist/assets/SettingsLayout-n5d4wq4m.js +0 -1
  271. package/static/dist/assets/States-kxyu50at.js +0 -1
  272. package/static/dist/assets/Toaster-6zmbcqgg.js +0 -1
  273. package/static/dist/assets/_app-fowyruiu.js +0 -1
  274. package/static/dist/assets/_app.ext._extensionId-nvjyrwhy.js +0 -1
  275. package/static/dist/assets/_app.index-c9zv4y7w.js +0 -1
  276. package/static/dist/assets/_app.insights-jf12nofo.js +0 -1
  277. package/static/dist/assets/_app.kanban-lri5a4yu.js +0 -1
  278. package/static/dist/assets/_app.logs-md1p0zqo.js +0 -1
  279. package/static/dist/assets/_app.memory-bzvf2e28.js +0 -1
  280. package/static/dist/assets/_app.profiles-jpg7zmbn.js +0 -1
  281. package/static/dist/assets/_app.settings-mpuwlx4j.js +0 -1
  282. package/static/dist/assets/_app.settings._section-knz8rwoj.js +0 -1
  283. package/static/dist/assets/_app.skills-fwu1dkir.js +0 -1
  284. package/static/dist/assets/_app.tasks-h9gphtl3.js +0 -2
  285. package/static/dist/assets/_app.todos-hd1dnvrh.js +0 -1
  286. package/static/dist/assets/_app.workspaces-l8toyn5f.js +0 -1
  287. package/static/dist/assets/architecture-7GRP2DOG-cavnusaw.js +0 -1
  288. package/static/dist/assets/architectureDiagram-5GKGNRK7-k5a7v36n.js +0 -36
  289. package/static/dist/assets/channel-gozy3dp3.js +0 -1
  290. package/static/dist/assets/classDiagram-ZZMXUADV-n0vs2f2q.js +0 -1
  291. package/static/dist/assets/classDiagram-v2-VYDZK3BY-n0vs2f2q.js +0 -1
  292. package/static/dist/assets/endpoints-k8agnro8.js +0 -9
  293. package/static/dist/assets/eventmodeling-NTZA5JFV-j1y2ekec.js +0 -1
  294. package/static/dist/assets/flowDiagram-HODETNUW-g2cvcbm5.js +0 -1
  295. package/static/dist/assets/index-nfy0ezs6.css +0 -2
  296. package/static/dist/assets/infoDiagram-27XIBGKW-fm06rj36.js +0 -2
  297. package/static/dist/assets/lazyRouteComponent-b2eg0fws.js +0 -1
  298. package/static/dist/assets/link-m90ahx3n.js +0 -1
  299. package/static/dist/assets/login-bw7m2fzo.js +0 -1
  300. package/static/dist/assets/mermaid-HWGCJPDP-njwcl62n.js +0 -1
  301. package/static/dist/assets/onboarding-ret37j1e.js +0 -1
  302. package/static/dist/assets/railroad-abnf-HS5TGJTU-m741owar.js +0 -1
  303. package/static/dist/assets/railroad-ebnf-LZEXJU2U-noa4fbia.js +0 -1
  304. package/static/dist/assets/railroad-peg-WCYAUIDC-o4bpw4nb.js +0 -1
  305. package/static/dist/assets/search-j5ki6a9o.js +0 -1
  306. package/static/dist/assets/share._token-kg8w647b.js +0 -2
  307. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-k2wg2645.js +0 -1
  308. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-n5kvjtgr.js +0 -8
  309. package/static/dist/assets/tabBuild-bjmyvmn5.js +0 -56
  310. package/static/dist/assets/todoStore-kjk5l0sy.js +0 -1
  311. package/static/dist/assets/useForm-ocxu460f.js +0 -1
  312. package/static/dist/assets/useLocale-fzy5jhg6.js +0 -1
@@ -4,6 +4,8 @@ export interface ModelEntry {
4
4
  id: string;
5
5
  label: string;
6
6
  supports_fast_tier?: boolean;
7
+ provider_id?: string;
8
+ bare_id?: string;
7
9
  }
8
10
  export interface ModelGroup {
9
11
  provider: string;
@@ -11,11 +13,16 @@ export interface ModelGroup {
11
13
  models: ModelEntry[];
12
14
  extra_models?: ModelEntry[];
13
15
  }
16
+ /** TAL-301: the catalog entry a stored `(model, provider)` pair selects, so clients render the pairing instead of computing it. */
17
+ export declare function catalogOptionId(catalog: ModelsCatalog, model: string | null, provider: string | null): string | null;
14
18
  /** Python `_split_picker_overflow_models`: past 25 rows the picker shows 15, keeping the selected model visible. */
15
19
  export declare function splitPickerOverflow(models: ModelEntry[], selected: string, providerId: string): [ModelEntry[], ModelEntry[]];
16
20
  export interface ModelsCatalog {
17
21
  active_provider: string | null;
18
22
  default_model: string;
23
+ default_provider_id?: string | null;
24
+ default_bare_id?: string;
25
+ default_option_id?: string | null;
19
26
  groups: ModelGroup[];
20
27
  aliases: Record<string, string>;
21
28
  configured_model_badges: Record<string, {
@@ -49,6 +56,49 @@ export interface CatalogDeps {
49
56
  /** WebUI state directory; holds `.quota_scope_id`, the stable public identity namespace for quota sources. */
50
57
  stateDir?: string;
51
58
  }
59
+ /** ISO-8601 UTC for epoch seconds, without a `.000` fraction. */
60
+ export declare const isoAt: (seconds: number) => string;
61
+ /** ISO-8601 UTC (`…Z`) or null. Accepts the pre-TAL-409 sidecar's `str(datetime)` space form; a zone-less stamp is UTC. */
62
+ export declare function isoUtc(value: unknown): string | null;
63
+ export interface QuotaPace {
64
+ expected_remaining_percent: number;
65
+ pace_delta_percent: number;
66
+ burn_rate: number;
67
+ minutes_to_reset: number;
68
+ projected_minutes_to_empty: number | null;
69
+ elapsed_minutes: number;
70
+ valid_until: string;
71
+ }
72
+ export interface QuotaForecast {
73
+ outcome: 'safe' | 'warning';
74
+ budget_unit: 'hour' | 'day';
75
+ budget_percent: number | null;
76
+ depletion_margin_minutes: number | null;
77
+ }
78
+ export interface QuotaWindow {
79
+ label: string;
80
+ used_percent: number | null;
81
+ remaining_percent: number | null;
82
+ reset_at: string | null;
83
+ detail: string | null;
84
+ window_seconds: number | null;
85
+ pace: QuotaPace | null;
86
+ forecast: QuotaForecast | null;
87
+ }
88
+ export interface QuotaWindows {
89
+ windows: QuotaWindow[];
90
+ pace_window_index: number | null;
91
+ session_window_index: number | null;
92
+ weekly_window_index: number | null;
93
+ }
94
+ /** The forecast a pace implies: per-hour or per-day budget until reset, and whether the projection empties the window first. */
95
+ export declare function quotaForecast(pace: QuotaPace, remaining: number | null): QuotaForecast;
96
+ /**
97
+ * TAL-409: the one normaliser for account-usage windows (Python `_snapshot_windows_payload` plus the pace math the
98
+ * App used to run). Blank labels drop; `reset_at` becomes ISO `Z`; each window gets its duration, pace and forecast
99
+ * as of `nowSeconds`; the indexes name the windows the App shows for pace, session and weekly selections.
100
+ */
101
+ export declare function normalizeQuotaWindows(raw: unknown, nowSeconds: number): QuotaWindows;
52
102
  export declare function displayName(pid: string): string;
53
103
  /** Python `_format_ollama_label`. */
54
104
  export declare function formatOllamaLabel(mid: string): string;
@@ -59,12 +109,20 @@ export declare function providerEnvVar(pid: string): string | null;
59
109
  export declare function applyProviderPrefix(models: ModelEntry[], pid: string, active: string | null): ModelEntry[];
60
110
  /** Python `_deduplicate_model_ids`: later duplicate ids (by provider-id order) gain an `@provider:` prefix. */
61
111
  export declare function deduplicateModelIds(groups: ModelGroup[]): void;
112
+ /** Quota sources keep the first row per source id, ordered by provider id, account label, then source id (TAL-272). */
113
+ export declare function uniqueQuotaSources<T extends {
114
+ source_id: string;
115
+ provider_id: string;
116
+ account_label: string;
117
+ }>(sources: T[]): T[];
62
118
  export declare class ProviderCatalog {
63
119
  private readonly deps;
64
120
  private readonly liveFailed;
65
121
  private readonly liveIds;
66
122
  private readonly liveInflight;
67
123
  private readonly providersCache;
124
+ /** TAL-301: the last catalog served per profile home, for the sync session payloads' `model_option_id`. */
125
+ private readonly lastModels;
68
126
  constructor(deps: CatalogDeps);
69
127
  /** Python `models_cache` diagnostics: live-id groups held, ids across them, and the oldest snapshot's age. */
70
128
  diagnosticSnapshot(): {
@@ -73,6 +131,10 @@ export declare class ProviderCatalog {
73
131
  age_seconds: number | null;
74
132
  };
75
133
  invalidate(profileHome?: string, provider?: string): void;
134
+ /** TAL-301: the entry id a stored `(model, provider)` pair selects in the last catalog built for this home; a cold home starts building one. */
135
+ modelOptionFor(profileHome: string, model: string | null, provider: string | null): string | null;
136
+ /** Builds the catalog `modelOptionFor` reads once per home; an unavailable catalog leaves every option id null. */
137
+ warmModelOptions(profileHome: string): Promise<void>;
76
138
  /**
77
139
  * The process-environment value of `name` as a named profile may see it: values `loadStartupEnv` copied from the
78
140
  * default profile's `.env` belong to that profile only, so they never count for another profile.
@@ -86,6 +148,11 @@ export declare class ProviderCatalog {
86
148
  liveModelIds(profileHome: string, pid: string, opts?: {
87
149
  force?: boolean;
88
150
  }): Promise<string[]>;
151
+ /**
152
+ * TAL-288: the model-provider plugins installed and enabled in this profile, as the sidecar reads them from the Agent.
153
+ * A failed lookup lists none, so every other provider stays usable; an id that cannot be a provider routing hint is dropped.
154
+ */
155
+ private pluginProviders;
89
156
  /** Python `get_providers`. */
90
157
  providers(profileHome: string): Promise<{
91
158
  providers: Dict[];
@@ -93,6 +160,8 @@ export declare class ProviderCatalog {
93
160
  }>;
94
161
  /** Python `get_available_models` (static catalog + live ids for keyed providers). */
95
162
  models(profileHome: string): Promise<ModelsCatalog>;
163
+ /** Stamps the default's entry id and keeps the catalog for `modelOptionFor`. */
164
+ private remember;
96
165
  /** Python `_configured_model_badges_from_static_catalog`: which picker rows are the main/fallback selections. */
97
166
  private badges;
98
167
  /** Python `_handle_live_models`. */
@@ -111,6 +180,7 @@ export declare class ProviderCatalog {
111
180
  private readonly accountUsageCache;
112
181
  quota(profileHome: string, providerRaw: string | null, opts?: {
113
182
  refresh?: boolean;
183
+ at?: number;
114
184
  }): Promise<Dict>;
115
185
  private fetchOpenRouterKey;
116
186
  private quotaScopeCache;
@@ -118,7 +188,10 @@ export declare class ProviderCatalog {
118
188
  private quotaServerScopeId;
119
189
  /** Python `_quota_profile_scope_id`. */
120
190
  quotaProfileScopeId(profile: string): string;
121
- /** Python `get_provider_quotas`: one source per keyed provider, in the stable-identity envelope the iOS widget persists. */
191
+ /**
192
+ * Python `get_provider_quotas`: one source per keyed provider, in the stable-identity envelope the iOS widget persists.
193
+ * Each source id appears once (two custom providers can share a slug), ordered by provider, account label, then source id.
194
+ */
122
195
  quotas(profileHome: string, profile: string, opts?: {
123
196
  sourceId?: string | null;
124
197
  refresh?: boolean;
@@ -18,7 +18,7 @@ import { str } from '../util.js';
18
18
  import { loadEnvFile } from './env-file.js';
19
19
  import { ACCOUNT_USAGE_PROVIDERS, FALLBACK_MODELS, OAUTH_PROVIDERS, PORTAL_PROVIDERS, PROVIDER_DISPLAY, PROVIDER_ENV_VAR, PROVIDER_ENV_VAR_ALIASES, PROVIDER_MODELS, SELF_HOSTED_PROVIDER_IDS, } from './tables.js';
20
20
  import { activeProviderFromConfig, canonicaliseProviderId, configuredModelIds, configuredModelOptions, customProviderEntries, customProviderSlug, dict, effectiveDefaultModel, isDict, isOpenAiFamilyProvider, mainModelSupportsServiceTier, modelSection, parseProviderQualifiedModel, providerIdentity, resolveProviderAlias, } from '../config/agent-config.js';
21
- /** Python `_model_matches_picker_selection`: same bare id, and the routing hints agree when both name one. */
21
+ /** Python `_model_matches_picker_selection`: same bare id, and the routing hints agree when the selection names one. */
22
22
  function modelMatchesPickerSelection(modelId, selected, providerId) {
23
23
  const candidate = modelId.trim();
24
24
  const sel = selected.trim();
@@ -26,12 +26,28 @@ function modelMatchesPickerSelection(modelId, selected, providerId) {
26
26
  return false;
27
27
  if (candidate === sel)
28
28
  return true;
29
- const bare = (v) => (v.startsWith('@') && v.includes(':') ? v.slice(v.indexOf(':') + 1) : v);
30
- if (bare(sel) !== bare(candidate))
31
- return false;
32
- const selProvider = sel.startsWith('@') && sel.includes(':') ? sel.slice(1, sel.indexOf(':')).toLowerCase() : '';
33
- const candProvider = candidate.startsWith('@') && candidate.includes(':') ? candidate.slice(1, candidate.indexOf(':')).toLowerCase() : providerId.trim().toLowerCase();
34
- return !selProvider || !candProvider || selProvider === candProvider;
29
+ const [candBare, candProvider] = parseProviderQualifiedModel(candidate) ?? [candidate, providerId.trim()];
30
+ const [selBare, selProvider] = parseProviderQualifiedModel(sel) ?? [sel, ''];
31
+ return selBare === candBare && (!selProvider || selProvider.toLowerCase() === candProvider.toLowerCase());
32
+ }
33
+ /** TAL-301: every entry names its routing provider and bare id, split once by `parseProviderQualifiedModel`. */
34
+ function stampModelEntries(models, providerId) {
35
+ return models.map((m) => {
36
+ const [bare, provider] = parseProviderQualifiedModel(m.id) ?? [m.id, providerId];
37
+ return { ...m, bare_id: bare, provider_id: provider };
38
+ });
39
+ }
40
+ /** TAL-301: the catalog entry a stored `(model, provider)` pair selects, so clients render the pairing instead of computing it. */
41
+ export function catalogOptionId(catalog, model, provider) {
42
+ const bare = str(model).trim();
43
+ const pid = str(provider).trim().toLowerCase();
44
+ if (!bare || !pid)
45
+ return null;
46
+ for (const g of catalog.groups)
47
+ for (const e of [...g.models, ...(g.extra_models ?? [])])
48
+ if (e.bare_id === bare && str(e.provider_id).toLowerCase() === pid)
49
+ return e.id;
50
+ return null;
35
51
  }
36
52
  /** Python `_split_picker_overflow_models`: past 25 rows the picker shows 15, keeping the selected model visible. */
37
53
  export function splitPickerOverflow(models, selected, providerId) {
@@ -83,10 +99,7 @@ export function stampAuxiliarySelections(aux, catalog) {
83
99
  /** Python `_BESPOKE_CATALOG_PROVIDERS`: cards whose catalog is resolved by their own rule, never the generic live probe. */
84
100
  const BESPOKE_CATALOG_PROVIDERS = new Set(['openai-codex', 'nous', 'xai-oauth', 'lmstudio', 'opencode-go']);
85
101
  /** Python `_unqualified_model_id`: strip a picker routing hint (`@provider:`). */
86
- function unqualifiedModelId(id) {
87
- const raw = id.trim();
88
- return raw.startsWith('@') && raw.includes(':') ? raw.slice(raw.indexOf(':') + 1) : raw;
89
- }
102
+ const unqualifiedModelId = (id) => parseProviderQualifiedModel(id)?.[0] ?? id.trim();
90
103
  /** Python `_MODEL_PICKER_OVERFLOW_THRESHOLD` / `_MODEL_PICKER_VISIBLE_TARGET`. */
91
104
  const MODEL_PICKER_OVERFLOW_THRESHOLD = 25;
92
105
  const MODEL_PICKER_VISIBLE_TARGET = 15;
@@ -95,7 +108,110 @@ const LIVE_TTL_S = 86_400;
95
108
  const PROVIDERS_TTL_S = 30;
96
109
  const QUOTA_TIMEOUT_MS = 15_000;
97
110
  const OPENROUTER_KEY_URL = 'https://openrouter.ai/api/v1/key';
111
+ const PLUGIN_LIST_TIMEOUT_MS = 15_000;
112
+ /** TAL-288: the card text for a plugin provider the Agent does not report ready. */
113
+ const PLUGIN_SETUP_ERRORS = {
114
+ missing_cli: "This provider's CLI was not found on the server. Install it and sign in, then refresh.",
115
+ needs_setup: "This provider is not set up yet. Finish its setup in Hermes, then refresh.",
116
+ not_loaded: 'This provider is installed but could not be loaded. Check it with hermes plugins doctor, then restart Talaria Web.',
117
+ unavailable: "This provider's setup status is unavailable.",
118
+ };
119
+ const PLUGIN_NO_MODELS = 'This provider listed no models.';
98
120
  const COST_SNAPSHOT_MAX_DAYS = 365;
121
+ const FIVE_HOUR_WINDOW_S = 18_000;
122
+ const WEEK_WINDOW_S = 604_800;
123
+ /** Swift `rounded()`: half away from zero. */
124
+ function roundTo(value, digits) {
125
+ const scale = 10 ** digits;
126
+ return (Math.sign(value) * Math.round(Math.abs(value) * scale)) / scale;
127
+ }
128
+ const finite = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
129
+ /** ISO-8601 UTC for epoch seconds, without a `.000` fraction. */
130
+ export const isoAt = (seconds) => new Date(seconds * 1000).toISOString().replace('.000Z', 'Z');
131
+ /** ISO-8601 UTC (`…Z`) or null. Accepts the pre-TAL-409 sidecar's `str(datetime)` space form; a zone-less stamp is UTC. */
132
+ export function isoUtc(value) {
133
+ if (typeof value !== 'string' || !value.trim())
134
+ return null;
135
+ let text = value.trim().replace(' ', 'T');
136
+ if (text.includes('T') && !/(Z|[+-]\d{2}:?\d{2})$/i.test(text))
137
+ text += 'Z';
138
+ const ms = Date.parse(text);
139
+ return Number.isFinite(ms) ? isoAt(ms / 1000) : null;
140
+ }
141
+ /** The forecast a pace implies: per-hour or per-day budget until reset, and whether the projection empties the window first. */
142
+ export function quotaForecast(pace, remaining) {
143
+ const daily = pace.minutes_to_reset >= 24 * 60;
144
+ const margin = pace.projected_minutes_to_empty === null ? null : pace.projected_minutes_to_empty - pace.minutes_to_reset;
145
+ return {
146
+ outcome: margin === null || margin >= 0 ? 'safe' : 'warning',
147
+ budget_unit: daily ? 'day' : 'hour',
148
+ budget_percent: remaining !== null && pace.minutes_to_reset > 0 ? remaining / (pace.minutes_to_reset / (daily ? 24 * 60 : 60)) : null,
149
+ depletion_margin_minutes: margin,
150
+ };
151
+ }
152
+ /**
153
+ * TAL-409: the one normaliser for account-usage windows (Python `_snapshot_windows_payload` plus the pace math the
154
+ * App used to run). Blank labels drop; `reset_at` becomes ISO `Z`; each window gets its duration, pace and forecast
155
+ * as of `nowSeconds`; the indexes name the windows the App shows for pace, session and weekly selections.
156
+ */
157
+ export function normalizeQuotaWindows(raw, nowSeconds) {
158
+ const nowMs = nowSeconds * 1000;
159
+ const windows = [];
160
+ for (const entry of Array.isArray(raw) ? raw : []) {
161
+ if (!isDict(entry))
162
+ continue;
163
+ const label = str(entry.label).trim();
164
+ if (!label)
165
+ continue;
166
+ const usedRaw = finite(entry.used_percent);
167
+ const remainingRaw = finite(entry.remaining_percent);
168
+ const clamp = (v) => Math.min(Math.max(v, 0), 100);
169
+ const used = usedRaw !== null ? clamp(usedRaw) : remainingRaw !== null ? clamp(100 - remainingRaw) : null;
170
+ const remaining = used === null ? null : 100 - used;
171
+ const resetAt = isoUtc(entry.reset_at);
172
+ const resetMs = resetAt === null ? null : Date.parse(resetAt);
173
+ const minutesToReset = resetMs === null ? null : Math.max(0, roundTo((resetMs - nowMs) / 60_000, 0));
174
+ const lower = label.toLowerCase();
175
+ const providedSeconds = finite(entry.window_seconds);
176
+ const windowSeconds = providedSeconds
177
+ ?? (lower.includes('week') ? WEEK_WINDOW_S
178
+ : lower.includes('5h') ? FIVE_HOUR_WINDOW_S
179
+ // A "Session" window is 5h, or the weekly window a provider collapsed into one session bucket.
180
+ : lower.includes('session') && minutesToReset !== null ? (minutesToReset > 5 * 60 ? WEEK_WINDOW_S : FIVE_HOUR_WINDOW_S)
181
+ : null);
182
+ let pace = null;
183
+ if (resetAt !== null && resetMs !== null && minutesToReset !== null && resetMs > nowMs && used !== null && remaining !== null
184
+ && (windowSeconds === FIVE_HOUR_WINDOW_S || windowSeconds === WEEK_WINDOW_S)) {
185
+ const windowMinutes = windowSeconds / 60;
186
+ const elapsed = Math.max(0, windowMinutes - minutesToReset);
187
+ const expectedRemaining = roundTo(Math.min(Math.max((minutesToReset / windowMinutes) * 100, 0), 100), 1);
188
+ const expectedUsed = roundTo(100 - expectedRemaining, 1);
189
+ const usagePerMinute = elapsed > 0 ? used / elapsed : 0;
190
+ pace = {
191
+ expected_remaining_percent: expectedRemaining,
192
+ pace_delta_percent: roundTo(remaining - expectedRemaining, 1),
193
+ burn_rate: expectedUsed > 0 ? roundTo(used / expectedUsed, 2) : 0,
194
+ minutes_to_reset: minutesToReset,
195
+ projected_minutes_to_empty: usagePerMinute > 0 ? roundTo(remaining / usagePerMinute, 0) : null,
196
+ elapsed_minutes: elapsed,
197
+ valid_until: resetAt,
198
+ };
199
+ }
200
+ windows.push({
201
+ label, used_percent: usedRaw, remaining_percent: remaining, reset_at: resetAt, detail: typeof entry.detail === 'string' ? entry.detail : null,
202
+ window_seconds: windowSeconds, pace, forecast: pace ? quotaForecast(pace, remaining) : null,
203
+ });
204
+ }
205
+ const index = (match) => { const i = windows.findIndex(match); return i < 0 ? null : i; };
206
+ const labelHas = (needle) => (w) => w.label.toLowerCase().includes(needle);
207
+ const knownPace = (w) => w.window_seconds === FIVE_HOUR_WINDOW_S || w.window_seconds === WEEK_WINDOW_S || ['week', 'session', '5h'].some((n) => labelHas(n)(w));
208
+ return {
209
+ windows,
210
+ pace_window_index: index(labelHas('week')) ?? index(knownPace),
211
+ session_window_index: index(labelHas('session')) ?? index(labelHas('5h')),
212
+ weekly_window_index: index(labelHas('week')),
213
+ };
214
+ }
99
215
  export function displayName(pid) {
100
216
  return PROVIDER_DISPLAY[pid] ?? pid.split('-').map((w) => (w ? w[0].toUpperCase() + w.slice(1) : w)).join(' ');
101
217
  }
@@ -115,9 +231,7 @@ export function formatOllamaLabel(mid) {
115
231
  }
116
232
  /** Python `_get_label_for_model`: reuse a known label, else prettify the bare id. */
117
233
  export function labelForModel(modelId, groups) {
118
- let lookup = modelId;
119
- if (lookup.startsWith('@') && lookup.includes(':'))
120
- lookup = lookup.slice(lookup.indexOf(':') + 1);
234
+ const lookup = unqualifiedModelId(modelId);
121
235
  const hasScheme = (s) => s.includes('://');
122
236
  const norm = (s) => (s.includes('/') && !hasScheme(s) ? s.slice(s.indexOf('/') + 1) : s).replaceAll('-', '.').toLowerCase();
123
237
  const target = norm(lookup);
@@ -186,12 +300,20 @@ export function deduplicateModelIds(groups) {
186
300
  m.id = `@${groups[gi].provider_id}:${m.id}`;
187
301
  }
188
302
  }
303
+ /** Quota sources keep the first row per source id, ordered by provider id, account label, then source id (TAL-272). */
304
+ export function uniqueQuotaSources(sources) {
305
+ const cmp = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
306
+ return sources.filter((q, i) => sources.findIndex((o) => o.source_id === q.source_id) === i)
307
+ .sort((a, b) => cmp(a.provider_id, b.provider_id) || cmp(a.account_label, b.account_label) || cmp(a.source_id, b.source_id));
308
+ }
189
309
  export class ProviderCatalog {
190
310
  deps;
191
311
  liveFailed = new Set();
192
312
  liveIds = new Map();
193
313
  liveInflight = new Map();
194
314
  providersCache = new Map();
315
+ /** TAL-301: the last catalog served per profile home, for the sync session payloads' `model_option_id`. */
316
+ lastModels = new Map();
195
317
  constructor(deps) {
196
318
  this.deps = deps;
197
319
  }
@@ -214,6 +336,29 @@ export class ProviderCatalog {
214
336
  this.liveIds.delete(key);
215
337
  }
216
338
  this.providersCache.clear();
339
+ // The picker catalog changed: option ids rebuild on the next read rather than pairing against the old one.
340
+ if (profileHome)
341
+ this.lastModels.delete(profileHome);
342
+ else
343
+ this.lastModels.clear();
344
+ }
345
+ /** TAL-301: the entry id a stored `(model, provider)` pair selects in the last catalog built for this home; a cold home starts building one. */
346
+ modelOptionFor(profileHome, model, provider) {
347
+ const catalog = this.lastModels.get(profileHome);
348
+ if (!catalog) {
349
+ void this.warmModelOptions(profileHome);
350
+ return null;
351
+ }
352
+ return catalogOptionId(catalog, model, provider);
353
+ }
354
+ /** Builds the catalog `modelOptionFor` reads once per home; an unavailable catalog leaves every option id null. */
355
+ async warmModelOptions(profileHome) {
356
+ if (this.lastModels.has(profileHome))
357
+ return;
358
+ try {
359
+ await this.models(profileHome);
360
+ }
361
+ catch { /* fail closed: no option ids */ }
217
362
  }
218
363
  /**
219
364
  * The process-environment value of `name` as a named profile may see it: values `loadStartupEnv` copied from the
@@ -337,6 +482,23 @@ export class ProviderCatalog {
337
482
  this.liveInflight.set(key, run);
338
483
  return run;
339
484
  }
485
+ /**
486
+ * TAL-288: the model-provider plugins installed and enabled in this profile, as the sidecar reads them from the Agent.
487
+ * A failed lookup lists none, so every other provider stays usable; an id that cannot be a provider routing hint is dropped.
488
+ */
489
+ async pluginProviders(profileHome) {
490
+ const sidecar = this.deps.sidecar();
491
+ if (!sidecar)
492
+ return [];
493
+ try {
494
+ const { providers } = await sidecar.call('plugins.providers', { profile_home: profileHome }, { timeoutMs: PLUGIN_LIST_TIMEOUT_MS });
495
+ return providers.filter((p, i, all) => /^[a-z0-9][a-z0-9._-]{0,63}$/.test(p.name) && all.findIndex((q) => q.name === p.name) === i);
496
+ }
497
+ catch (error) {
498
+ this.deps.log(`[catalog] plugin providers failed: ${str(error.message)}`);
499
+ return [];
500
+ }
501
+ }
340
502
  /** Python `get_providers`. */
341
503
  async providers(profileHome) {
342
504
  const config = await this.deps.config.read(profileHome);
@@ -347,10 +509,16 @@ export class ProviderCatalog {
347
509
  return structuredClone(hit.payload);
348
510
  const active = activeProviderFromConfig(config);
349
511
  const known = new Set([...Object.keys(PROVIDER_DISPLAY), ...Object.keys(PROVIDER_MODELS), ...OAUTH_PROVIDERS]);
512
+ // A plugin never stands in for a built-in provider, by id or alias (that would route to the built-in's billing); it
513
+ // does own a `providers.<id>` config entry.
514
+ const plugins = (await this.pluginProviders(profileHome)).filter((p) => !known.has(p.name) && !known.has(providerIdentity(p.name)));
515
+ const pluginIds = new Set(plugins.map((p) => p.name));
350
516
  const providersCfg = dict(config.providers);
351
517
  for (const key of Object.keys(providersCfg)) {
352
518
  const identity = canonicaliseProviderId(key);
353
- known.add(known.has(identity) ? identity : key);
519
+ const id = known.has(identity) ? identity : key;
520
+ if (!pluginIds.has(id))
521
+ known.add(id);
354
522
  }
355
523
  const rows = [];
356
524
  for (const pid of [...known].sort()) {
@@ -395,6 +563,17 @@ export class ProviderCatalog {
395
563
  models_total: modelsTotal,
396
564
  });
397
565
  }
566
+ // TAL-288: a plugin is selectable only once the Agent reports its setup ready; it never borrows a built-in's key.
567
+ for (const plugin of plugins) {
568
+ const ready = plugin.setup === 'ready';
569
+ const live = ready ? await this.liveModelIds(profileHome, plugin.name) : [];
570
+ rows.push({
571
+ id: plugin.name, display_name: plugin.display_name || plugin.name, has_key: ready,
572
+ configurable: false, is_oauth: false, is_plugin_provider: true, is_self_hosted: false, is_custom: false, key_source: ready ? 'plugin' : 'none',
573
+ base_url: null, auth_error: plugin.setup === 'ready' ? (live.length ? null : PLUGIN_NO_MODELS) : PLUGIN_SETUP_ERRORS[plugin.setup], env_var: null,
574
+ models: live.map((id) => ({ id, label: labelForModel(id, []) })), models_total: live.length,
575
+ });
576
+ }
398
577
  for (const cp of customProviderEntries(config)) {
399
578
  const name = str(cp.name).trim();
400
579
  if (!name)
@@ -414,6 +593,8 @@ export class ProviderCatalog {
414
593
  // Python `_provider_sort_key`: active first, then `custom:*`, then keyed providers, then the rest (alphabetical within).
415
594
  const rank = (p) => (str(p.id) === active ? 0 : str(p.id).startsWith('custom:') ? 1 : p.has_key ? 2 : 3);
416
595
  rows.sort((a, b) => rank(a) - rank(b) || (str(a.id) < str(b.id) ? -1 : str(a.id) > str(b.id) ? 1 : 0));
596
+ for (const row of rows)
597
+ row.models = stampModelEntries(row.models, str(row.id));
417
598
  const payload = { providers: rows, active_provider: active };
418
599
  this.providersCache.set(profileHome, { at: this.deps.now(), key: cacheKey, payload });
419
600
  return structuredClone(payload);
@@ -462,12 +643,18 @@ export class ProviderCatalog {
462
643
  if (canonical && this.providerHasKey(canonical, config, envValues, profileHome))
463
644
  detected.add(canonical);
464
645
  }
465
- // Python: OAuth providers the Agent reports as logged in join the picker with their live catalog (#1567, #2545).
466
- const oauthLoggedIn = new Set();
467
- for (const row of (await this.providers(profileHome)).providers)
468
- if (row.is_oauth === true && row.has_key === true)
469
- oauthLoggedIn.add(str(row.id));
470
- for (const pid of oauthLoggedIn)
646
+ // Python: OAuth providers the Agent reports as logged in join the picker with their live catalog (#1567, #2545); so do
647
+ // ready plugin providers (TAL-288), under their own name.
648
+ const signedIn = new Set();
649
+ const pluginNames = new Map();
650
+ for (const row of (await this.providers(profileHome)).providers) {
651
+ if (row.has_key !== true || (row.is_oauth !== true && row.is_plugin_provider !== true))
652
+ continue;
653
+ signedIn.add(str(row.id));
654
+ if (row.is_plugin_provider === true)
655
+ pluginNames.set(str(row.id), str(row.display_name) || str(row.id));
656
+ }
657
+ for (const pid of signedIn)
471
658
  detected.add(pid);
472
659
  const fallbackCfg = Array.isArray(config.fallback_providers) ? config.fallback_providers.filter(isDict) : [];
473
660
  for (const entry of fallbackCfg) {
@@ -500,7 +687,7 @@ export class ProviderCatalog {
500
687
  detected.add(named?.[0] ?? active ?? 'custom');
501
688
  }
502
689
  const groups = [];
503
- for (const pid of [...detected].map((p) => (p.startsWith('custom') ? p : canonicaliseProviderId(p) || p)).filter((v, i, a) => v && a.indexOf(v) === i).sort()) {
690
+ for (const pid of [...detected].map((p) => (p.startsWith('custom') || pluginNames.has(p) ? p : canonicaliseProviderId(p) || p)).filter((v, i, a) => v && a.indexOf(v) === i).sort()) {
504
691
  if (pid.startsWith('custom:')) {
505
692
  const g = namedCustom.get(pid);
506
693
  const models = [...(g?.models ?? [])];
@@ -521,7 +708,7 @@ export class ProviderCatalog {
521
708
  let raw = [];
522
709
  if ('models' in providerCfg && providerCfg.models_discovered !== true)
523
710
  raw = configuredModelOptions(providerCfg.models);
524
- if (!raw.length && (this.providerHasKey(pid, config, envValues, profileHome) || oauthLoggedIn.has(pid))) {
711
+ if (!raw.length && (this.providerHasKey(pid, config, envValues, profileHome) || signedIn.has(pid))) {
525
712
  const live = await this.liveModelIds(profileHome, pid);
526
713
  if (live.length)
527
714
  raw = live.map((id) => ({ id, label: pid === 'nous' ? `${formatOllamaLabel(id.includes('/') ? id.slice(id.indexOf('/') + 1) : id)} (via Nous)` : labelForModel(id, []) }));
@@ -535,7 +722,7 @@ export class ProviderCatalog {
535
722
  if (!raw.some((m) => m.id === id))
536
723
  raw.push({ id, label: labelForModel(id, groups) });
537
724
  if (raw.length)
538
- groups.push({ provider: displayName(pid), provider_id: pid, models: applyProviderPrefix(raw, pid, active) });
725
+ groups.push({ provider: pluginNames.get(pid) ?? displayName(pid), provider_id: pid, models: applyProviderPrefix(raw, pid, active) });
539
726
  }
540
727
  if (defaultModel) {
541
728
  const all = new Set(groups.flatMap((g) => g.models.map((m) => m.id)));
@@ -581,10 +768,20 @@ export class ProviderCatalog {
581
768
  for (const [k, v] of Object.entries(rawAliases))
582
769
  if (k && v)
583
770
  aliases[k.trim()] = str(v).trim();
771
+ const [defaultBare, defaultProvider] = parseProviderQualifiedModel(defaultModel) ?? [defaultModel, active];
772
+ const defaults = { active_provider: active, default_model: defaultModel, default_provider_id: defaultProvider, default_bare_id: defaultBare };
584
773
  if (!kept.length && defaultModel) {
585
- return { active_provider: active, default_model: defaultModel, groups: [{ provider: 'Default', provider_id: active ?? 'default', models: [{ id: defaultModel, label: labelForModel(defaultModel, []) }] }], aliases: {}, configured_model_badges: {} };
774
+ const providerId = active ?? 'default';
775
+ return this.remember(profileHome, { ...defaults, groups: [{ provider: 'Default', provider_id: providerId, models: stampModelEntries([{ id: defaultModel, label: labelForModel(defaultModel, []) }], providerId) }], aliases: {}, configured_model_badges: {} });
586
776
  }
587
- return { active_provider: active, default_model: defaultModel, groups: kept, aliases, configured_model_badges: this.badges(kept, active, defaultModel, fallbackCfg) };
777
+ const stamped = kept.map((g) => ({ ...g, models: stampModelEntries(g.models, g.provider_id), ...(g.extra_models ? { extra_models: stampModelEntries(g.extra_models, g.provider_id) } : {}) }));
778
+ return this.remember(profileHome, { ...defaults, groups: stamped, aliases, configured_model_badges: this.badges(kept, active, defaultModel, fallbackCfg) });
779
+ }
780
+ /** Stamps the default's entry id and keeps the catalog for `modelOptionFor`. */
781
+ remember(profileHome, catalog) {
782
+ catalog.default_option_id = catalogOptionId(catalog, catalog.default_bare_id ?? null, catalog.default_provider_id ?? null);
783
+ this.lastModels.set(profileHome, catalog);
784
+ return structuredClone(catalog);
588
785
  }
589
786
  /** Python `_configured_model_badges_from_static_catalog`: which picker rows are the main/fallback selections. */
590
787
  badges(groups, active, defaultModel, fallback) {
@@ -640,7 +837,7 @@ export class ProviderCatalog {
640
837
  if (provider !== 'nous' && ids.length > MODEL_PICKER_OVERFLOW_THRESHOLD)
641
838
  ids = ids.slice(0, MODEL_PICKER_VISIBLE_TARGET);
642
839
  const annotateFastTier = isOpenAiFamilyProvider(provider);
643
- const models = ids.filter(Boolean).map((id) => ({ id, label: labelForModel(id, []), ...(annotateFastTier ? { supports_fast_tier: mainModelSupportsServiceTier(id, provider) } : {}) }));
840
+ const models = stampModelEntries(ids.filter(Boolean).map((id) => ({ id, label: labelForModel(id, []), ...(annotateFastTier ? { supports_fast_tier: mainModelSupportsServiceTier(id, provider) } : {}) })), provider);
644
841
  return { provider, source, models, count: models.length };
645
842
  }
646
843
  /**
@@ -652,7 +849,7 @@ export class ProviderCatalog {
652
849
  const config = await this.deps.config.read(profileHome);
653
850
  const section = modelSection(config);
654
851
  const pid = canonicaliseProviderId(provider ?? section.provider) || null;
655
- const bare = model.replace(/^@[^:]+:/, '');
852
+ const bare = unqualifiedModelId(model);
656
853
  const positive = (v) => { const n = Math.trunc(Number(v)); return Number.isFinite(n) && n > 0 ? n : null; };
657
854
  const fromModels = (models) => {
658
855
  if (isDict(models)) {
@@ -675,7 +872,7 @@ export class ProviderCatalog {
675
872
  let baseUrl = '';
676
873
  let configContextLength = null;
677
874
  const sectionModel = str(section.default || (typeof config.model === 'string' ? config.model : '')).trim();
678
- if (sectionModel && [model, bare].includes(sectionModel.replace(/^@[^:]+:/, '')))
875
+ if (sectionModel && [model, bare].includes(unqualifiedModelId(sectionModel)))
679
876
  configContextLength = positive(section.context_length);
680
877
  if (!pid || canonicaliseProviderId(section.provider) === pid)
681
878
  baseUrl = str(section.base_url).trim();
@@ -714,10 +911,12 @@ export class ProviderCatalog {
714
911
  /** Python `get_provider_quota`. */
715
912
  accountUsageCache = new Map();
716
913
  async quota(profileHome, providerRaw, opts = {}) {
914
+ const at = opts.at ?? this.deps.now();
915
+ const computed_at = isoAt(at);
717
916
  const config = await this.deps.config.read(profileHome);
718
917
  const provider = (providerRaw ?? activeProviderFromConfig(config) ?? '').trim().toLowerCase();
719
918
  if (!provider)
720
- return { ok: false, provider: null, display_name: null, supported: false, status: 'unavailable', quota: null, message: 'No active provider is configured.' };
919
+ return { computed_at, ok: false, provider: null, display_name: null, supported: false, status: 'unavailable', quota: null, message: 'No active provider is configured.' };
721
920
  const name = displayName(provider);
722
921
  if (ACCOUNT_USAGE_PROVIDERS.has(provider)) {
723
922
  const sidecar = this.deps.sidecar();
@@ -738,22 +937,25 @@ export class ProviderCatalog {
738
937
  }
739
938
  this.accountUsageCache.set(cacheKey, { at: this.deps.now(), limits });
740
939
  }
940
+ // The cache keeps the Agent's raw windows; pace is recomputed as of every response.
941
+ if (limits)
942
+ limits = { ...limits, ...normalizeQuotaWindows(limits.windows, at), fetched_at: isoUtc(limits.fetched_at) };
741
943
  if (limits?.available)
742
- return { ok: true, provider, display_name: name, supported: true, status: limits.stale ? 'stale' : 'available', label: limits.title, quota: null, account_limits: limits, message: limits.stale ? `${name} refresh failed; showing last-known account limits.` : `${name} account limits loaded.` };
944
+ return { computed_at, ok: true, provider, display_name: name, supported: true, status: limits.stale ? 'stale' : 'available', label: limits.title, quota: null, account_limits: limits, message: limits.stale ? `${name} refresh failed; showing last-known account limits.` : `${name} account limits loaded.` };
743
945
  const reason = str(limits?.unavailable_reason).trim();
744
- return { ok: false, provider, display_name: name, supported: true, status: 'unavailable', quota: null, account_limits: limits, message: reason ? `${name} account limits are unavailable. ${reason}` : `${name} account limits are unavailable. Confirm provider authentication and try again.` };
946
+ return { computed_at, ok: false, provider, display_name: name, supported: true, status: 'unavailable', quota: null, account_limits: limits, message: reason ? `${name} account limits are unavailable. ${reason}` : `${name} account limits are unavailable. Confirm provider authentication and try again.` };
745
947
  }
746
948
  if (provider === 'openrouter') {
747
949
  const apiKey = this.apiKeyFor('openrouter', profileHome, config);
748
950
  if (!apiKey)
749
- return { ok: false, provider, display_name: name, supported: true, status: 'no_key', quota: null, message: 'OpenRouter quota status needs an OPENROUTER_API_KEY configured on the server.' };
951
+ return { computed_at, ok: false, provider, display_name: name, supported: true, status: 'no_key', quota: null, message: 'OpenRouter quota status needs an OPENROUTER_API_KEY configured on the server.' };
750
952
  const info = await this.fetchOpenRouterKey(apiKey);
751
953
  if (info.kind === 'ok')
752
- return { ok: true, provider, display_name: name, supported: true, status: 'available', label: 'OpenRouter credits', quota: info.quota, message: 'OpenRouter quota status loaded.' };
954
+ return { computed_at, ok: true, provider, display_name: name, supported: true, status: 'available', label: 'OpenRouter credits', quota: info.quota, message: 'OpenRouter quota status loaded.' };
753
955
  const status = info.kind === 'invalid_key' ? 'invalid_key' : 'unavailable';
754
- return { ok: false, provider, display_name: name, supported: true, status, quota: null, message: status === 'invalid_key' ? 'OpenRouter rejected the configured API key.' : 'OpenRouter quota status is temporarily unavailable.' };
956
+ return { computed_at, ok: false, provider, display_name: name, supported: true, status, quota: null, message: status === 'invalid_key' ? 'OpenRouter rejected the configured API key.' : 'OpenRouter quota status is temporarily unavailable.' };
755
957
  }
756
- return { ok: false, provider, display_name: name, supported: false, status: 'unsupported', quota: null, message: `No verified server-side quota or balance endpoint is available for ${name}.` };
958
+ return { computed_at, ok: false, provider, display_name: name, supported: false, status: 'unsupported', quota: null, message: `No verified server-side quota or balance endpoint is available for ${name}.` };
757
959
  }
758
960
  async fetchOpenRouterKey(apiKey) {
759
961
  const f = this.deps.fetch ?? fetch;
@@ -807,28 +1009,32 @@ export class ProviderCatalog {
807
1009
  quotaProfileScopeId(profile) {
808
1010
  return `qscope_${createHash('sha256').update(`${this.quotaServerScopeId()}\0${profile}`).digest('hex').slice(0, 32)}`;
809
1011
  }
810
- /** Python `get_provider_quotas`: one source per keyed provider, in the stable-identity envelope the iOS widget persists. */
1012
+ /**
1013
+ * Python `get_provider_quotas`: one source per keyed provider, in the stable-identity envelope the iOS widget persists.
1014
+ * Each source id appears once (two custom providers can share a slug), ordered by provider, account label, then source id.
1015
+ */
811
1016
  async quotas(profileHome, profile, opts = {}) {
1017
+ const at = this.deps.now();
812
1018
  const status = await this.providers(profileHome);
813
1019
  const active = status.active_provider;
814
1020
  const scopeId = this.quotaProfileScopeId(profile);
815
1021
  // Python `_quota_source_id(profile, provider, "provider")`: the single-credential descriptor per provider.
816
1022
  const sourceId = (pid) => `qsrc_${createHash('sha256').update(`${scopeId}\0${pid}\0provider`).digest('hex').slice(0, 32)}`;
817
- let descriptors = status.providers.filter((p) => p.has_key || p.is_custom).map((p) => ({ source_id: sourceId(str(p.id)), provider_id: str(p.id), provider_label: str(p.display_name) || str(p.id) }));
1023
+ let descriptors = uniqueQuotaSources(status.providers.filter((p) => p.has_key || p.is_custom).map((p) => ({ source_id: sourceId(str(p.id)), provider_id: str(p.id), provider_label: str(p.display_name) || str(p.id), account_label: str(p.display_name) || str(p.id) })));
818
1024
  const requested = str(opts.sourceId).trim() || null;
819
1025
  if (requested)
820
1026
  descriptors = descriptors.filter((d) => d.source_id === requested);
821
1027
  const sources = await Promise.all(descriptors.map(async (d) => {
822
- const q = await this.quota(profileHome, d.provider_id, { refresh: opts.refresh ?? false });
1028
+ const q = await this.quota(profileHome, d.provider_id, { refresh: opts.refresh ?? false, at });
823
1029
  const limits = dict(q.account_limits);
824
1030
  return {
825
- source_id: d.source_id, provider_id: d.provider_id, provider_label: d.provider_label, account_label: d.provider_label,
1031
+ source_id: d.source_id, provider_id: d.provider_id, provider_label: d.provider_label, account_label: d.account_label,
826
1032
  is_active_provider: d.provider_id === active, supported: q.supported === true, status: str(limits.status) || str(q.status) || 'unavailable',
827
- plan: limits.plan ?? null, windows: limits.windows ?? [], quota: q.quota ?? null, balances: q.balances ?? [], details: limits.details ?? [],
1033
+ plan: limits.plan ?? null, windows: limits.windows ?? [], pace_window_index: limits.pace_window_index ?? null, session_window_index: limits.session_window_index ?? null, weekly_window_index: limits.weekly_window_index ?? null, quota: q.quota ?? null, balances: q.balances ?? [], details: limits.details ?? [],
828
1034
  unavailable_reason: limits.unavailable_reason ?? null, retry_after: limits.retry_after ?? null, fetched_at: limits.fetched_at ?? null, message: q.message ?? null,
829
1035
  };
830
1036
  }));
831
- return { version: 1, scope_id: scopeId, profile_id: profile, active_provider: active, requested_source_id: requested, missing_source: Boolean(requested && !descriptors.length), sources };
1037
+ return { version: 1, computed_at: isoAt(at), scope_id: scopeId, profile_id: profile, active_provider: active, requested_source_id: requested, missing_source: Boolean(requested && !descriptors.length), sources };
832
1038
  }
833
1039
  /** Python `get_provider_cost_history` (OpenRouter only; daily snapshots under `<home>/cost-snapshots`). */
834
1040
  async costHistory(profileHome, providerRaw, days) {