@maudecode/talaria-web 1.14.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 (348) hide show
  1. package/_release.json +4 -4
  2. package/dist/api/automation-router.d.ts +123 -7
  3. package/dist/api/automation-router.js +5 -0
  4. package/dist/api/automation-router.js.map +1 -1
  5. package/dist/api/chat-router.d.ts +728 -55
  6. package/dist/api/chat-router.js +122 -126
  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 +6 -3
  13. package/dist/api/router.js +10 -8
  14. package/dist/api/router.js.map +1 -1
  15. package/dist/api/sessions-router.d.ts +2583 -297
  16. package/dist/api/sessions-router.js +92 -47
  17. package/dist/api/sessions-router.js.map +1 -1
  18. package/dist/api/settings-router.d.ts +188 -17
  19. package/dist/api/settings-router.js +75 -26
  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 +3620 -343
  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/config/agent-config.js +20 -15
  39. package/dist/config/agent-config.js.map +1 -1
  40. package/dist/http/addresses.d.ts +6 -0
  41. package/dist/http/addresses.js +26 -0
  42. package/dist/http/addresses.js.map +1 -1
  43. package/dist/http/context.d.ts +7 -5
  44. package/dist/http/context.js.map +1 -1
  45. package/dist/links.d.ts +26 -0
  46. package/dist/links.js +60 -0
  47. package/dist/links.js.map +1 -0
  48. package/dist/onboarding.d.ts +1 -1
  49. package/dist/onboarding.js +28 -8
  50. package/dist/onboarding.js.map +1 -1
  51. package/dist/profiles/profiles.js +1 -1
  52. package/dist/profiles/profiles.js.map +1 -1
  53. package/dist/providers/catalog.d.ts +85 -1
  54. package/dist/providers/catalog.js +280 -43
  55. package/dist/providers/catalog.js.map +1 -1
  56. package/dist/providers/tables.d.ts +3 -1
  57. package/dist/providers/tables.js +9 -4
  58. package/dist/providers/tables.js.map +1 -1
  59. package/dist/redact.js +493 -13
  60. package/dist/redact.js.map +1 -1
  61. package/dist/runtime.js +104 -42
  62. package/dist/runtime.js.map +1 -1
  63. package/dist/sessions/anchor.d.ts +22 -3
  64. package/dist/sessions/anchor.js +87 -42
  65. package/dist/sessions/anchor.js.map +1 -1
  66. package/dist/sessions/background-tasks.d.ts +140 -0
  67. package/dist/sessions/background-tasks.js +352 -0
  68. package/dist/sessions/background-tasks.js.map +1 -0
  69. package/dist/sessions/background-updates.d.ts +56 -0
  70. package/dist/sessions/background-updates.js +152 -0
  71. package/dist/sessions/background-updates.js.map +1 -0
  72. package/dist/sessions/cli-sessions.d.ts +6 -0
  73. package/dist/sessions/cli-sessions.js +20 -7
  74. package/dist/sessions/cli-sessions.js.map +1 -1
  75. package/dist/sessions/completions.d.ts +17 -1
  76. package/dist/sessions/completions.js +84 -13
  77. package/dist/sessions/completions.js.map +1 -1
  78. package/dist/sessions/compress.d.ts +33 -0
  79. package/dist/sessions/compress.js +66 -0
  80. package/dist/sessions/compress.js.map +1 -0
  81. package/dist/sessions/list.d.ts +22 -4
  82. package/dist/sessions/list.js +113 -33
  83. package/dist/sessions/list.js.map +1 -1
  84. package/dist/sessions/merge.d.ts +81 -7
  85. package/dist/sessions/merge.js +391 -26
  86. package/dist/sessions/merge.js.map +1 -1
  87. package/dist/sessions/pending.d.ts +5 -0
  88. package/dist/sessions/pending.js +30 -10
  89. package/dist/sessions/pending.js.map +1 -1
  90. package/dist/sessions/relay.d.ts +7 -1
  91. package/dist/sessions/relay.js +17 -4
  92. package/dist/sessions/relay.js.map +1 -1
  93. package/dist/sessions/service.d.ts +118 -6
  94. package/dist/sessions/service.js +564 -65
  95. package/dist/sessions/service.js.map +1 -1
  96. package/dist/sessions/session.d.ts +22 -1
  97. package/dist/sessions/session.js +29 -2
  98. package/dist/sessions/session.js.map +1 -1
  99. package/dist/sessions/shares.js +11 -2
  100. package/dist/sessions/shares.js.map +1 -1
  101. package/dist/sessions/source-kind.d.ts +22 -0
  102. package/dist/sessions/source-kind.js +54 -0
  103. package/dist/sessions/source-kind.js.map +1 -0
  104. package/dist/sessions/state-db.d.ts +10 -1
  105. package/dist/sessions/state-db.js +48 -17
  106. package/dist/sessions/state-db.js.map +1 -1
  107. package/dist/sessions/store.d.ts +2 -0
  108. package/dist/sessions/store.js +24 -5
  109. package/dist/sessions/store.js.map +1 -1
  110. package/dist/sessions/streams.d.ts +3 -0
  111. package/dist/sessions/streams.js +0 -5
  112. package/dist/sessions/streams.js.map +1 -1
  113. package/dist/sessions/titles.d.ts +4 -0
  114. package/dist/sessions/titles.js +16 -0
  115. package/dist/sessions/titles.js.map +1 -1
  116. package/dist/sessions/turn-context.d.ts +1 -0
  117. package/dist/sessions/turn-context.js +3 -0
  118. package/dist/sessions/turn-context.js.map +1 -1
  119. package/dist/sessions/turn.d.ts +85 -7
  120. package/dist/sessions/turn.js +518 -170
  121. package/dist/sessions/turn.js.map +1 -1
  122. package/dist/settings.d.ts +2 -0
  123. package/dist/settings.js +13 -0
  124. package/dist/settings.js.map +1 -1
  125. package/dist/sidecar/fake.js +2 -0
  126. package/dist/sidecar/fake.js.map +1 -1
  127. package/dist/tools/commands.d.ts +15 -0
  128. package/dist/tools/commands.js +52 -0
  129. package/dist/tools/commands.js.map +1 -0
  130. package/dist/tools/crons.d.ts +38 -4
  131. package/dist/tools/crons.js +323 -36
  132. package/dist/tools/crons.js.map +1 -1
  133. package/dist/tools/mcp.js +5 -1
  134. package/dist/tools/mcp.js.map +1 -1
  135. package/dist/tools/update-notifications.d.ts +1 -1
  136. package/dist/tools/update-notifications.js +1 -1
  137. package/dist/tools/update-notifications.js.map +1 -1
  138. package/dist/tools/updates.d.ts +8 -3
  139. package/dist/tools/updates.js +133 -34
  140. package/dist/tools/updates.js.map +1 -1
  141. package/dist/workspace/git.d.ts +57 -10
  142. package/dist/workspace/git.js +302 -195
  143. package/dist/workspace/git.js.map +1 -1
  144. package/dist/workspace/media-refs.d.ts +13 -0
  145. package/dist/workspace/media-refs.js +165 -0
  146. package/dist/workspace/media-refs.js.map +1 -0
  147. package/dist/workspace/media.d.ts +19 -0
  148. package/dist/workspace/media.js +61 -2
  149. package/dist/workspace/media.js.map +1 -1
  150. package/dist/workspace/workspaces.d.ts +7 -1
  151. package/dist/workspace/workspaces.js +30 -21
  152. package/dist/workspace/workspaces.js.map +1 -1
  153. package/dist/workspace/worktrees.d.ts +2 -4
  154. package/dist/workspace/worktrees.js +28 -33
  155. package/dist/workspace/worktrees.js.map +1 -1
  156. package/package.json +4 -2
  157. package/sidecar/scripts/replay_sidecar.py +3 -1
  158. package/sidecar/talaria_sidecar/__init__.py +1 -1
  159. package/sidecar/talaria_sidecar/home.py +59 -2
  160. package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
  161. package/sidecar/talaria_sidecar/methods/aux.py +19 -3
  162. package/sidecar/talaria_sidecar/methods/chat.py +360 -27
  163. package/sidecar/talaria_sidecar/methods/commands.py +46 -31
  164. package/sidecar/talaria_sidecar/methods/config.py +14 -10
  165. package/sidecar/talaria_sidecar/methods/cron.py +22 -14
  166. package/sidecar/talaria_sidecar/methods/oauth.py +439 -0
  167. package/sidecar/talaria_sidecar/methods/process.py +318 -0
  168. package/sidecar/talaria_sidecar/methods/profiles.py +5 -4
  169. package/sidecar/talaria_sidecar/methods/providers.py +161 -1
  170. package/sidecar/talaria_sidecar/methods/runtime.py +2 -0
  171. package/sidecar/talaria_sidecar/methods/text.py +1 -20
  172. package/sidecar/talaria_sidecar/methods/usage.py +7 -1
  173. package/static/dist/FILES.txt +124 -119
  174. package/static/dist/assets/AppShell-ef3bhu32.js +1 -0
  175. package/static/dist/assets/Button-nx37f4u1.js +1 -0
  176. package/static/dist/assets/ChatPage-cdcpykcg.js +25 -0
  177. package/static/dist/assets/Dialog-mec42hvb.js +1 -0
  178. package/static/dist/assets/HubRoute-ecb65zpr.js +2 -0
  179. package/static/dist/assets/{Markdown-emii55o0.js → Markdown-jqrx5es0.js} +125 -125
  180. package/static/dist/assets/RightPanel-d1lgvk4s.js +1 -0
  181. package/static/dist/assets/Select-h4ivl9qf.js +1 -0
  182. package/static/dist/assets/SessionListPanel-ewib046b.js +1 -0
  183. package/static/dist/assets/SettingsLayout-lx654cup.js +1 -0
  184. package/static/dist/assets/States-ehsb6lzo.js +1 -0
  185. package/static/dist/assets/Toaster-mch1pa3t.js +1 -0
  186. package/static/dist/assets/_app-m8d961ef.js +1 -0
  187. package/static/dist/assets/_app.ext._extensionId-mp33hbzi.js +1 -0
  188. package/static/dist/assets/_app.index-dk6zp7e0.js +1 -0
  189. package/static/dist/assets/_app.insights-jt2fk4i1.js +1 -0
  190. package/static/dist/assets/_app.kanban-if4pubeu.js +1 -0
  191. package/static/dist/assets/_app.logs-mfaw9m6g.js +1 -0
  192. package/static/dist/assets/_app.memory-h3eaarb6.js +1 -0
  193. package/static/dist/assets/_app.profiles-ienktovs.js +1 -0
  194. package/static/dist/assets/{_app.session._sessionId-m6nx9g8t.js → _app.session._sessionId-hh27wwu2.js} +1 -1
  195. package/static/dist/assets/_app.settings-dpoe0bzf.js +1 -0
  196. package/static/dist/assets/_app.settings._section-l468ynag.js +3 -0
  197. package/static/dist/assets/_app.skills-kcbncc8i.js +1 -0
  198. package/static/dist/assets/_app.tasks-occdrqhw.js +2 -0
  199. package/static/dist/assets/_app.todos-nnke2rcc.js +1 -0
  200. package/static/dist/assets/_app.workspaces-cdlatvwj.js +1 -0
  201. package/static/dist/assets/{abnfDiagram-VCTEODGH-kejgxpil.js → abnfDiagram-VCTEODGH-oity9y4o.js} +1 -1
  202. package/static/dist/assets/architecture-7GRP2DOG-hj11333z.js +1 -0
  203. package/static/dist/assets/architectureDiagram-5GKGNRK7-ceaepnyr.js +36 -0
  204. package/static/dist/assets/back-eu64w89w.js +1 -0
  205. package/static/dist/assets/{blockDiagram-I7D4REHJ-l1466a7j.js → blockDiagram-I7D4REHJ-n79st4pj.js} +1 -1
  206. package/static/dist/assets/{c4Diagram-7LVT6UL2-gg3aq3sr.js → c4Diagram-7LVT6UL2-mi73akzk.js} +1 -1
  207. package/static/dist/assets/cancel-mzvkdjg8.js +1 -0
  208. package/static/dist/assets/channel-mbvmf2vv.js +1 -0
  209. package/static/dist/assets/{chunk-4HAMMTFA-oalgz5l8.js → chunk-4HAMMTFA-laat0g1q.js} +1 -1
  210. package/static/dist/assets/{chunk-75Z2AOVW-babmzhdg.js → chunk-75Z2AOVW-0psy1tr2.js} +1 -1
  211. package/static/dist/assets/{chunk-DU6HZSFF-lagu350c.js → chunk-DU6HZSFF-lw8uox8x.js} +3 -3
  212. package/static/dist/assets/{chunk-F27PBJKO-gjh2tqif.js → chunk-F27PBJKO-erassib3.js} +1 -1
  213. package/static/dist/assets/{chunk-GMAD6QVW-ipdgja5c.js → chunk-GMAD6QVW-fz7rx619.js} +1 -1
  214. package/static/dist/assets/{chunk-GVQU2GXP-j8wkn1at.js → chunk-GVQU2GXP-gdb0epb2.js} +1 -1
  215. package/static/dist/assets/{chunk-IMKFNOWR-k00laqyk.js → chunk-IMKFNOWR-ea8t26qw.js} +1 -1
  216. package/static/dist/assets/{chunk-L3NEJ4N5-fju6xqj4.js → chunk-L3NEJ4N5-mb1gu9kz.js} +1 -1
  217. package/static/dist/assets/{chunk-OSK3NFVY-bi6widqt.js → chunk-OSK3NFVY-eovmccvy.js} +1 -1
  218. package/static/dist/assets/{chunk-P2QGCYS3-czr4tebv.js → chunk-P2QGCYS3-bo55zjo5.js} +1 -1
  219. package/static/dist/assets/{chunk-POPQ4Y6H-ktdhn67w.js → chunk-POPQ4Y6H-ja7ohe4h.js} +1 -1
  220. package/static/dist/assets/{chunk-PWAF6VOD-ii4dsu0d.js → chunk-PWAF6VOD-e30g9nus.js} +1 -1
  221. package/static/dist/assets/{chunk-SHT3W25Y-fpwk3xxp.js → chunk-SHT3W25Y-gbb798f7.js} +3 -3
  222. package/static/dist/assets/{chunk-SVP7TREG-k0jrvhei.js → chunk-SVP7TREG-h2iw8urm.js} +1 -1
  223. package/static/dist/assets/{chunk-TICWLB2K-nkqv9ot0.js → chunk-TICWLB2K-fd2cottz.js} +1 -1
  224. package/static/dist/assets/classDiagram-ZZMXUADV-o8r05k0n.js +1 -0
  225. package/static/dist/assets/classDiagram-v2-VYDZK3BY-o8r05k0n.js +1 -0
  226. package/static/dist/assets/{copied-gpchexqu.js → copied-h65fc3cx.js} +1 -1
  227. package/static/dist/assets/copy-e6977uwq.js +1 -0
  228. package/static/dist/assets/{cynefin-OW5HDTMX-lqxt8rs3.js → cynefin-OW5HDTMX-lbltyl5j.js} +1 -1
  229. package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-j9eqovf5.js → cynefinDiagram-5FMLGOSQ-lxhsqq4a.js} +1 -1
  230. package/static/dist/assets/{dagre-GXQ25YYZ-m9agobjm.js → dagre-GXQ25YYZ-gn4nnaln.js} +4 -4
  231. package/static/dist/assets/{diagram-S7CK7UJ4-itodc50c.js → diagram-S7CK7UJ4-pa3ij6q7.js} +3 -3
  232. package/static/dist/assets/{diagram-UQ7AKVKN-n1h2t4ah.js → diagram-UQ7AKVKN-zbipyz57.js} +1 -1
  233. package/static/dist/assets/{diagram-VSXAHHWV-lvtvm9bj.js → diagram-VSXAHHWV-d1kbpzgf.js} +1 -1
  234. package/static/dist/assets/{diagram-VX7I27RA-cgas58sy.js → diagram-VX7I27RA-i3ty14xl.js} +1 -1
  235. package/static/dist/assets/{diagram-Z3DM3KII-d0aeps1q.js → diagram-Z3DM3KII-m8xefqa7.js} +1 -1
  236. package/static/dist/assets/{ebnfDiagram-PWID7BFC-md345fyq.js → ebnfDiagram-PWID7BFC-nz7wj05l.js} +1 -1
  237. package/static/dist/assets/endpoints-k7yf25m0.js +64 -0
  238. package/static/dist/assets/{erDiagram-RLTQ6QDP-i4rua63b.js → erDiagram-RLTQ6QDP-fa41ib83.js} +1 -1
  239. package/static/dist/assets/eventmodeling-NTZA5JFV-kpbq09bx.js +1 -0
  240. package/static/dist/assets/flowDiagram-HODETNUW-lcz0kx14.js +1 -0
  241. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-geqx7zgl.js → ganttDiagram-EL5Y4UJY-n7zq01qu.js} +1 -1
  242. package/static/dist/assets/{gitGraph-4MIJSDKK-leoq0z8t.js → gitGraph-4MIJSDKK-c89emb07.js} +1 -1
  243. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-llrlxfha.js → gitGraphDiagram-WWUBYQGX-cz7cgr6w.js} +1 -1
  244. package/static/dist/assets/{highlighted-body-KPVGNVTW-kq2cnw6q.js → highlighted-body-KPVGNVTW-d9bapmni.js} +1 -1
  245. package/static/dist/assets/{index-f9bzglkn.js → index-h00usru2.js} +3 -3
  246. package/static/dist/assets/index-n73ijkv2.css +2 -0
  247. package/static/dist/assets/{info-A6RAGUB7-lkly4xj6.js → info-A6RAGUB7-ndddhhhh.js} +1 -1
  248. package/static/dist/assets/infoDiagram-27XIBGKW-ccxigcpf.js +2 -0
  249. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-hq6jf374.js → ishikawaDiagram-5VMMS53U-le0z9vtc.js} +1 -1
  250. package/static/dist/assets/{journeyDiagram-3NMN7TZE-is03ntb0.js → journeyDiagram-3NMN7TZE-ivsmb26o.js} +1 -1
  251. package/static/dist/assets/{kanban-definition-UXKFOSKX-jmerizqb.js → kanban-definition-UXKFOSKX-h6n2awt0.js} +1 -1
  252. package/static/dist/assets/lazyRouteComponent-m1rtyk4t.js +1 -0
  253. package/static/dist/assets/{line-laaa8h99.js → line-dmzlfe0u.js} +1 -1
  254. package/static/dist/assets/link-budeg5t9.js +1 -0
  255. package/static/dist/assets/login-kjhasvp8.js +1 -0
  256. package/static/dist/assets/mermaid-HWGCJPDP-jb5d211j.js +1 -0
  257. package/static/dist/assets/{mermaid-parser.core-ix4junux.js → mermaid-parser.core-gwyd5bnq.js} +3 -3
  258. package/static/dist/assets/{mindmap-definition-YA3MSWOX-o0e0adl1.js → mindmap-definition-YA3MSWOX-l68gg5h7.js} +1 -1
  259. package/static/dist/assets/onboarding-cdfrk0pj.js +1 -0
  260. package/static/dist/assets/{packet-AYTQ26CC-kl8pwsyg.js → packet-AYTQ26CC-gpv06065.js} +1 -1
  261. package/static/dist/assets/{pegDiagram-XKGWAZYB-bbqn9jo4.js → pegDiagram-XKGWAZYB-e52vs23u.js} +1 -1
  262. package/static/dist/assets/{pie-WAS4IAKB-cpyo8bkr.js → pie-WAS4IAKB-l6m7nz5s.js} +1 -1
  263. package/static/dist/assets/{pieDiagram-E7YTZNPT-pelug34f.js → pieDiagram-E7YTZNPT-b4is36bq.js} +3 -3
  264. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-jds4jmr5.js → quadrantDiagram-AXDQQJYC-m8vt2788.js} +1 -1
  265. package/static/dist/assets/{radar-RG4KPBEZ-e02oxnvd.js → radar-RG4KPBEZ-nxbrhiov.js} +1 -1
  266. package/static/dist/assets/{railroad-74A4TZTK-kz3delwo.js → railroad-74A4TZTK-222mu9hi.js} +1 -1
  267. package/static/dist/assets/railroad-abnf-HS5TGJTU-kdvgl6x8.js +1 -0
  268. package/static/dist/assets/railroad-ebnf-LZEXJU2U-itiqslby.js +1 -0
  269. package/static/dist/assets/railroad-peg-WCYAUIDC-go7kov78.js +1 -0
  270. package/static/dist/assets/{railroadDiagram-O6MQD6OU-jgroj2ew.js → railroadDiagram-O6MQD6OU-o8lldfzv.js} +1 -1
  271. package/static/dist/assets/{refresh-cw-k7yxjdn3.js → refresh-cw-kbcje1uy.js} +1 -1
  272. package/static/dist/assets/{requirementDiagram-BXWQKSXE-m4az5e0j.js → requirementDiagram-BXWQKSXE-ovyq38kv.js} +1 -1
  273. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-vlryqiv3.js → sankeyDiagram-P5KCCOFB-ou7lznor.js} +1 -1
  274. package/static/dist/assets/{saved-brdfjjnw.js → saved-bazmcup9.js} +1 -1
  275. package/static/dist/assets/search-jen74z4u.js +1 -0
  276. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-fr8hrpql.js → sequenceDiagram-WJ2MYXX4-ovckdzf2.js} +1 -1
  277. package/static/dist/assets/{settings_section_extensions_title-l0zodj0b.js → settings_section_extensions_title-ertwwhlx.js} +1 -1
  278. package/static/dist/assets/share._token-dan9hgvj.js +2 -0
  279. package/static/dist/assets/{sliders-horizontal-gcgscv8r.js → sliders-horizontal-f9cb3lh3.js} +1 -1
  280. package/static/dist/assets/{stateDiagram-D77RDMKH-bg548iyr.js → stateDiagram-D77RDMKH-fcrexkkv.js} +1 -1
  281. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-lcfraua2.js +1 -0
  282. package/static/dist/assets/{swimlanes-42K2YHIH-hk5z3efh.js → swimlanes-42K2YHIH-f7usu8fw.js} +1 -1
  283. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-ogmanni0.js +8 -0
  284. package/static/dist/assets/{timeline-definition-24CTP7MA-olvknr62.js → timeline-definition-24CTP7MA-gp1hvz9f.js} +1 -1
  285. package/static/dist/assets/todoStore-eqtid4r4.js +1 -0
  286. package/static/dist/assets/{treeView-Q6P3EWNA-ovvjsxtf.js → treeView-Q6P3EWNA-gb9gf11u.js} +1 -1
  287. package/static/dist/assets/{treemap-WGGIJYW6-ggkipj59.js → treemap-WGGIJYW6-nuvfnvvi.js} +1 -1
  288. package/static/dist/assets/{untitled-c8ydw0bu.js → untitled-htelhc68.js} +1 -1
  289. package/static/dist/assets/useForm-h2t0u14e.js +1 -0
  290. package/static/dist/assets/useLocale-hcsb84pj.js +1 -0
  291. package/static/dist/assets/useNavigate-nwy70ztj.js +1 -0
  292. package/static/dist/assets/{useParams-katwvzil.js → useParams-dqd64jv7.js} +1 -1
  293. package/static/dist/assets/{useSearch-c70sgau1.js → useSearch-j30pvsy7.js} +1 -1
  294. package/static/dist/assets/{vennDiagram-4TSXK5OY-hzld7zt7.js → vennDiagram-4TSXK5OY-fzchbzaj.js} +1 -1
  295. package/static/dist/assets/{wardley-WFR3VGLG-hxkeoms8.js → wardley-WFR3VGLG-m47mhfbh.js} +1 -1
  296. package/static/dist/assets/{wardleyDiagram-VM6X3IG4-ljh90mb1.js → wardleyDiagram-VM6X3IG4-odg6ct1z.js} +2 -2
  297. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-mnuaes03.js → xychartDiagram-S5SC5T6Z-mrhms5um.js} +1 -1
  298. package/static/dist/index.html +1 -1
  299. package/static/dist/sw.js +1 -1
  300. package/static/dist/assets/AppShell-haqi9qzu.js +0 -1
  301. package/static/dist/assets/ChatPage-hjyu2g64.js +0 -22
  302. package/static/dist/assets/HubRoute-gkd8o1vg.js +0 -2
  303. package/static/dist/assets/RightPanel-o73y98dn.js +0 -1
  304. package/static/dist/assets/Select-jld3yeni.js +0 -1
  305. package/static/dist/assets/SessionListPanel-d3qx4ric.js +0 -1
  306. package/static/dist/assets/SettingsLayout-lnn2wo2s.js +0 -1
  307. package/static/dist/assets/States-e5hv6gdh.js +0 -1
  308. package/static/dist/assets/Toaster-c4jq1i26.js +0 -1
  309. package/static/dist/assets/_app-bnip2pjg.js +0 -1
  310. package/static/dist/assets/_app.ext._extensionId-egptzrfy.js +0 -1
  311. package/static/dist/assets/_app.index-vdz13ul5.js +0 -1
  312. package/static/dist/assets/_app.insights-lgjshsba.js +0 -1
  313. package/static/dist/assets/_app.kanban-c18cjrpz.js +0 -1
  314. package/static/dist/assets/_app.logs-ezhojgeg.js +0 -1
  315. package/static/dist/assets/_app.memory-i00paisv.js +0 -1
  316. package/static/dist/assets/_app.profiles-kd02bwjx.js +0 -1
  317. package/static/dist/assets/_app.settings-giaudkuk.js +0 -1
  318. package/static/dist/assets/_app.settings._section-k4qtlexk.js +0 -1
  319. package/static/dist/assets/_app.skills-bby2uvl8.js +0 -1
  320. package/static/dist/assets/_app.tasks-1kz9bosm.js +0 -2
  321. package/static/dist/assets/_app.todos-izjdbkm2.js +0 -1
  322. package/static/dist/assets/_app.workspaces-lfh2kw7s.js +0 -1
  323. package/static/dist/assets/architecture-7GRP2DOG-b4u9z1w3.js +0 -1
  324. package/static/dist/assets/architectureDiagram-5GKGNRK7-hqnf84fc.js +0 -36
  325. package/static/dist/assets/channel-o77ioktg.js +0 -1
  326. package/static/dist/assets/classDiagram-ZZMXUADV-f5rvod26.js +0 -1
  327. package/static/dist/assets/classDiagram-v2-VYDZK3BY-f5rvod26.js +0 -1
  328. package/static/dist/assets/endpoints-e58saih3.js +0 -9
  329. package/static/dist/assets/eventmodeling-NTZA5JFV-bn3fjsaq.js +0 -1
  330. package/static/dist/assets/flowDiagram-HODETNUW-li70d120.js +0 -1
  331. package/static/dist/assets/index-gkh7kum4.css +0 -2
  332. package/static/dist/assets/infoDiagram-27XIBGKW-oo7kdyte.js +0 -2
  333. package/static/dist/assets/lazyRouteComponent-b1h2ykmb.js +0 -1
  334. package/static/dist/assets/link-d8f0788e.js +0 -1
  335. package/static/dist/assets/login-llkohrcg.js +0 -1
  336. package/static/dist/assets/mermaid-HWGCJPDP-id0ffk2r.js +0 -1
  337. package/static/dist/assets/onboarding-lx0d6b38.js +0 -1
  338. package/static/dist/assets/railroad-abnf-HS5TGJTU-l13ped2z.js +0 -1
  339. package/static/dist/assets/railroad-ebnf-LZEXJU2U-c7rg3g4g.js +0 -1
  340. package/static/dist/assets/railroad-peg-WCYAUIDC-od0b8niq.js +0 -1
  341. package/static/dist/assets/search-gt2ueyvg.js +0 -1
  342. package/static/dist/assets/share._token-oyjv046r.js +0 -2
  343. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-j5v40qih.js +0 -1
  344. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-grzefo9c.js +0 -8
  345. package/static/dist/assets/tabBuild-idliyw9f.js +0 -56
  346. package/static/dist/assets/todoStore-j4c2xq0b.js +0 -1
  347. package/static/dist/assets/useForm-kd759sjk.js +0 -1
  348. package/static/dist/assets/useLocale-hds4xmja.js +0 -1
@@ -11,6 +11,7 @@ through ``approval.respond`` / ``clarify.respond``; ``rpc.cancel`` or
11
11
  from __future__ import annotations
12
12
 
13
13
  import contextlib
14
+ import contextvars
14
15
  import hashlib
15
16
  import inspect
16
17
  import json
@@ -257,13 +258,86 @@ def _clarify_timeout(params: dict) -> int:
257
258
  return 3600
258
259
 
259
260
 
261
+ def _steer_slot_lock(agent):
262
+ """The Agent's own pending-steer lock (a plain ``Lock``), so a slot read or rewrite is atomic against its drains."""
263
+ lock = agent.__dict__.get("_pending_steer_lock") if hasattr(agent, "__dict__") else None
264
+ return contextlib.nullcontext() if lock is None else lock
265
+
266
+
267
+ def _steer_slot(agent) -> str:
268
+ """The Agent's not-yet-applied steer text; call under ``_steer_slot_lock``."""
269
+ return str(getattr(agent, "_pending_steer", "") or "")
270
+
271
+
260
272
  def _agent_pending_steer_text(agent) -> str:
261
273
  """Predecessor ``_agent_pending_steer_text``: the Agent's not-yet-applied steer text."""
262
- lock = agent.__dict__.get("_pending_steer_lock") if hasattr(agent, "__dict__") else None
263
- if lock is None:
264
- return str(getattr(agent, "_pending_steer", "") or "")
265
- with lock:
266
- return str(agent.__dict__.get("_pending_steer") or "")
274
+ with _steer_slot_lock(agent):
275
+ return _steer_slot(agent)
276
+
277
+
278
+ def _slot_without(slot: str, pending: list[str], index: int) -> str | None:
279
+ """``slot`` without the steer ``pending[index]`` while the Agent still holds it, else None (it already took it).
280
+
281
+ The Agent keeps every not-yet-applied steer in one newline-joined slot, so the slot is the tail of ``pending``
282
+ (the server's steers, oldest first), possibly behind text another surface queued: the server's
283
+ ``pendingSteerSuffixStart`` rule. The longest tail that still holds the steer wins."""
284
+ for start in range(index + 1):
285
+ tail = "\n".join(pending[start:])
286
+ if slot == tail or slot.endswith("\n" + tail):
287
+ head = slot[: len(slot) - len(tail)].rstrip("\n")
288
+ return "\n".join(part for part in [head, *pending[start:index], *pending[index + 1:]] if part)
289
+ return None
290
+
291
+
292
+ def withdraw_steer(agent, pending: list[str], index: int) -> bool:
293
+ """TAL-424: take a not-yet-applied steer back out of the Agent's slot, keeping the others in order. False when the
294
+ Agent already took it; the slot is then left as it was, so no text is ever lost."""
295
+ with _steer_slot_lock(agent):
296
+ rest = _slot_without(_steer_slot(agent), pending, index)
297
+ if rest is None:
298
+ return False
299
+ agent._pending_steer = rest or None
300
+ return True
301
+
302
+
303
+ def steer_now(agent, pending: list[str], index: int) -> dict:
304
+ """TAL-424: deliver a pending steer now with the Agent's ``redirect``. During a model request that request restarts
305
+ with it (``delivery: redirect``); during tools it goes back on the slot and the tools yield (``delivery: steer``).
306
+ With no live request the steer stays pending: back in its place, or last when the slot changed meanwhile."""
307
+ text = pending[index]
308
+ with _steer_slot_lock(agent):
309
+ before = _steer_slot(agent)
310
+ rest = _slot_without(before, pending, index)
311
+ if rest is None:
312
+ return {"redirected": False, "withdrawn": False}
313
+ agent._pending_steer = rest or None
314
+ redirect = getattr(agent, "redirect", None)
315
+ try:
316
+ redirected = bool(redirect(text)) if callable(redirect) else False
317
+ except Exception as exc: # noqa: BLE001
318
+ log.debug("redirect failed: %s", exc)
319
+ redirected = False
320
+ if not redirected:
321
+ with _steer_slot_lock(agent):
322
+ if _steer_slot(agent) == rest:
323
+ agent._pending_steer = before or None
324
+ return {"redirected": False, "withdrawn": True, "requeued": "kept"}
325
+ agent.steer(text)
326
+ return {"redirected": False, "withdrawn": True, "requeued": "last"}
327
+ with _steer_slot_lock(agent):
328
+ slot = _steer_slot(agent)
329
+ delivery = "steer" if slot == text or slot.endswith("\n" + text) else "redirect"
330
+ return {"redirected": True, "withdrawn": True, "delivery": delivery}
331
+
332
+
333
+ def _steer_queue_params(params: dict) -> tuple[list[str], int]:
334
+ pending = params.get("pending")
335
+ index = params.get("index")
336
+ if not isinstance(pending, list) or not all(isinstance(t, str) and t.strip() for t in pending):
337
+ raise InvalidParams("pending must be a list of steer texts")
338
+ if not isinstance(index, int) or isinstance(index, bool) or not 0 <= index < len(pending):
339
+ raise InvalidParams("index must point into pending")
340
+ return [t.strip() for t in pending], index
267
341
 
268
342
 
269
343
  def _cancel_checkpoint(run: _Run) -> list | None:
@@ -299,7 +373,7 @@ def _resolve_runtime(provider: str | None, model: str) -> dict:
299
373
  def _turn_identity(session_id: str, workspace: str):
300
374
  """Bind this turn's identity to the calling context the way the predecessor's ``_set_turn_session_identity`` did:
301
375
  the approval session key (so ``register_gateway_notify`` cards reach this turn and dangerous commands are gated
302
- instead of auto-approved), the gateway session vars (platform ``webui``, chat/ui session ids), and the session cwd
376
+ instead of auto-approved), the gateway session vars (platform ``webui``, chat/ui session ids, profile), and the session cwd
303
377
  (so terminals and AGENTS.md discovery run in the selected workspace, not the sidecar's launch directory). Every
304
378
  binding is a context variable, so concurrent turns cannot overwrite each other. Missing Agent surfaces are logged,
305
379
  not fatal."""
@@ -315,10 +389,14 @@ def _turn_identity(session_id: str, workspace: str):
315
389
  log.debug("per-turn approval session-key bind failed", exc_info=True)
316
390
  try:
317
391
  from gateway import session_context as sc
392
+ from hermes_cli.profiles import get_active_profile_name
393
+ from hermes_constants import get_hermes_home, profile_name_for_home
318
394
 
395
+ # The persistent Docker sandbox is keyed by the profile name; unbound, every profile reuses "default".
396
+ profile = profile_name_for_home(get_hermes_home()) or get_active_profile_name()
319
397
  pairs = [
320
398
  (sc._SESSION_KEY, session_id), (sc._SESSION_UI_SESSION_ID, session_id), (sc._SESSION_PLATFORM, "webui"),
321
- (sc._SESSION_CHAT_ID, session_id), (sc._SESSION_ID, session_id),
399
+ (sc._SESSION_CHAT_ID, session_id), (sc._SESSION_ID, session_id), (sc._SESSION_PROFILE, profile),
322
400
  ]
323
401
  for var, value in pairs:
324
402
  tok = var.set(value)
@@ -342,6 +420,63 @@ def _turn_identity(session_id: str, workspace: str):
342
420
  log.debug("per-turn identity reset failed", exc_info=True)
343
421
 
344
422
 
423
+ #: Older configs named the CLI composite ``hermes``; the Agent registers it split in two (predecessor
424
+ #: ``_LEGACY_CLI_TOOLSET_ALIASES``).
425
+ _LEGACY_TOOLSET_ALIASES = {"hermes": ("hermes-cli", "hermes-api-server")}
426
+
427
+
428
+ def _turn_toolsets(toolsets) -> list[str]:
429
+ """The session's override as sent, else the profile's configured toolsets. ``None`` would hand the Agent every
430
+ registered toolset."""
431
+ return toolsets if isinstance(toolsets, list) else _profile_toolsets()
432
+
433
+
434
+ def _profile_toolsets() -> list[str]:
435
+ """The profile's ``platform_toolsets.cli`` through the Agent's own resolver (default-off toolsets stay off), like
436
+ ``hermes chat``. Runs under the call's ``scoped_home``."""
437
+ from hermes_cli.config import load_config
438
+ from hermes_cli.tools_config import _get_platform_tools
439
+
440
+ resolved = sorted(_get_platform_tools(load_config() or {}, "cli"))
441
+ return list(dict.fromkeys(name for raw in resolved for name in _LEGACY_TOOLSET_ALIASES.get(raw, (raw,))))
442
+
443
+
444
+ def _discover_mcp_tools(run: _Run) -> None:
445
+ """Connect the profile's MCP servers before the turn's agent snapshots its tools, like the predecessor's turn start.
446
+ The Agent keys connections by profile scope and skips live ones, so later turns are cheap. Runs under the call's
447
+ ``scoped_home``; a failure leaves the turn without MCP tools rather than failing it. A Stop does not wait for a slow
448
+ server: the turn moves on to its cancelled end while the connect finishes in the background."""
449
+ from .commands import retire_unscoped_launch_mcp_servers
450
+
451
+ done = threading.Event()
452
+
453
+ def discover() -> None:
454
+ try:
455
+ from tools.mcp_tool_discovery import discover_mcp_tools
456
+
457
+ retire_unscoped_launch_mcp_servers()
458
+ discover_mcp_tools()
459
+ except Exception: # noqa: BLE001
460
+ log.warning("MCP discovery failed", exc_info=True)
461
+ finally:
462
+ done.set()
463
+
464
+ # The copied context carries the turn's home override and secret scope into the worker.
465
+ threading.Thread(target=contextvars.copy_context().run, args=(discover,), daemon=True, name=f"chat-mcp-{run.stream_id[:8]}").start()
466
+ while not done.wait(0.25):
467
+ if run.cancel.is_set() or run.ctx.cancelled:
468
+ return
469
+
470
+
471
+ def _profile_fallback_chain() -> list[dict] | None:
472
+ """The profile's fallback routes (``fallback_providers`` first, then legacy ``fallback_model``, deduplicated)
473
+ through the Agent's own resolver, like the CLI and gateway. Runs under the call's ``scoped_home``."""
474
+ from hermes_cli.config import load_config
475
+ from hermes_cli.fallback_config import get_fallback_chain
476
+
477
+ return get_fallback_chain(load_config() or {}) or None
478
+
479
+
345
480
  def _agent_signature(model: str, provider, runtime: dict, toolsets, home: str, kwargs: dict) -> str:
346
481
  """Cache identity of an ``AIAgent``: everything its constructor bound from the resolved runtime, so a rotated key,
347
482
  a different API mode, ACP command, or credential pool never reuses an agent built for the old bundle. The key
@@ -357,6 +492,8 @@ def _agent_signature(model: str, provider, runtime: dict, toolsets, home: str, k
357
492
  "max_iterations": kwargs.get("max_iterations"), "max_tokens": kwargs.get("max_tokens"),
358
493
  # Bound at construction too: a reasoning-effort change from the composer must build a fresh agent.
359
494
  "reasoning_config": kwargs.get("reasoning_config"),
495
+ # Entries may carry their own ``api_key``.
496
+ "fallback_model": hashlib.sha256(json.dumps(kwargs.get("fallback_model"), sort_keys=True, default=str).encode("utf-8")).hexdigest()[:16],
360
497
  }
361
498
  return json.dumps(bundle, sort_keys=True, default=str)
362
499
 
@@ -399,6 +536,34 @@ def _supported(cls, name: str) -> bool:
399
536
  return name in parameters or any(p.kind is inspect.Parameter.VAR_KEYWORD for p in parameters.values())
400
537
 
401
538
 
539
+ def _approval_notifier(session_id: str, emit):
540
+ """The Agent's gateway notify callback for one turn: emits each parked approval, then ``approval_resolved``
541
+ (id plus the Agent's reason) once its wait ends by any path, an ``approvals.timeout`` expiry included (TAL-514)."""
542
+ try:
543
+ from tools.approval import register_gateway_settle
544
+ except Exception: # noqa: BLE001
545
+ register_gateway_settle = None
546
+
547
+ def notify(data):
548
+ payload = dict(data or {})
549
+ payload.setdefault("session_id", session_id)
550
+ payload.setdefault("approval_id", str(payload.get("request_id") or uuid.uuid4().hex))
551
+ emit("approval", payload)
552
+ request_id = str(payload.get("request_id") or "")
553
+ if register_gateway_settle is None or not request_id:
554
+ return
555
+ approval_id = payload["approval_id"]
556
+
557
+ def settle(reason):
558
+ emit("approval_resolved", {"approval_id": approval_id, "session_id": session_id, "reason": str(reason or "")})
559
+
560
+ # False: the wait already ended between the emit and this registration.
561
+ if not register_gateway_settle(session_id, request_id, settle):
562
+ settle("resolved")
563
+
564
+ return notify
565
+
566
+
402
567
  def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn, one function
403
568
  session_id = str(params.get("session_id") or "").strip()
404
569
  stream_id = str(params.get("stream_id") or "").strip()
@@ -414,7 +579,7 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
414
579
  history = []
415
580
  if not isinstance(history, list):
416
581
  raise InvalidParams("conversation_history must be a list")
417
- toolsets = params.get("enabled_toolsets")
582
+ toolsets = _turn_toolsets(params.get("enabled_toolsets"))
418
583
  system_message = params.get("system_message")
419
584
  run = _Run(stream_id, session_id, ctx)
420
585
  with _RUNS_LOCK:
@@ -437,6 +602,7 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
437
602
  raw_emit("steer_pending", {"text": pending})
438
603
  raw_emit(event, data)
439
604
  try:
605
+ _discover_mcp_tools(run)
440
606
  runtime = _resolve_runtime(provider, model)
441
607
  resolved_model = model or str(runtime.get("model") or "")
442
608
  resolved_provider = provider or runtime.get("provider")
@@ -553,15 +719,7 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
553
719
  from tools.approval import register_gateway_notify, unregister_gateway_notify
554
720
  except Exception: # noqa: BLE001
555
721
  register_gateway_notify = unregister_gateway_notify = None
556
- if register_gateway_notify is not None:
557
- def _approval_cb(data):
558
- payload = dict(data or {})
559
- payload.setdefault("session_id", session_id)
560
- payload.setdefault("approval_id", str(payload.get("request_id") or uuid.uuid4().hex))
561
- emit("approval", payload)
562
- approval_cb = _approval_cb
563
- else:
564
- approval_cb = None
722
+ approval_cb = _approval_notifier(session_id, emit) if register_gateway_notify is not None else None
565
723
 
566
724
  kwargs: dict = dict(
567
725
  model=resolved_model,
@@ -570,7 +728,7 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
570
728
  api_key=runtime.get("api_key"),
571
729
  platform="webui",
572
730
  quiet_mode=True,
573
- enabled_toolsets=toolsets if isinstance(toolsets, list) else None,
731
+ enabled_toolsets=toolsets,
574
732
  session_id=session_id,
575
733
  stream_delta_callback=on_token,
576
734
  reasoning_callback=on_reasoning,
@@ -598,6 +756,9 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
598
756
  reasoning_config = params.get("reasoning_config")
599
757
  if isinstance(reasoning_config, dict) and _supported(AIAgent, "reasoning_config"):
600
758
  kwargs["reasoning_config"] = reasoning_config
759
+ fallback_chain = _profile_fallback_chain()
760
+ if fallback_chain and _supported(AIAgent, "fallback_model"):
761
+ kwargs["fallback_model"] = fallback_chain
601
762
  signature = _agent_signature(resolved_model, resolved_provider, runtime, toolsets, str(params.get("profile_home")), kwargs)
602
763
  agent = None
603
764
  if not session_busy:
@@ -699,14 +860,22 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
699
860
  error = str(last_error)
700
861
  if not error and isinstance(result.get("error"), str) and result.get("error"):
701
862
  error = str(result["error"])
702
- status = "cancelled" if cancelled else ("error" if error and not result.get("messages") else "completed")
863
+ # The Agent ends a failed turn with ``failed``/``partial`` (never ``status``), often after text streamed and with
864
+ # the turn's messages; a failed turn is an error whatever it carries.
865
+ failed = bool(result.get("failed"))
866
+ partial = bool(result.get("partial"))
867
+ if not error and failed and result.get("final_response"):
868
+ error = str(result["final_response"])
869
+ status = "cancelled" if cancelled else ("error" if failed or (error and not result.get("messages")) else "completed")
703
870
  pending_steer = str(result.get("pending_steer") or "") if isinstance(result, dict) else ""
704
871
  return {
705
872
  "status": status,
706
873
  "messages": [m for m in (result.get("messages") or []) if isinstance(m, dict)],
707
874
  "final_response": str(result.get("final_response") or ""),
708
875
  "error": error,
709
- "result_status": str(result.get("status") or result.get("state") or ""),
876
+ "failed": failed,
877
+ "partial": partial,
878
+ "compression_exhausted": bool(result.get("compression_exhausted")),
710
879
  "tool_limit_reached": bool(result.get("tool_limit_reached") or result.get("max_iterations_reached")),
711
880
  "usage": _usage(agent),
712
881
  "context": _context_length(agent),
@@ -725,6 +894,131 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
725
894
  _RUNS_BY_SESSION.pop(session_id, None)
726
895
 
727
896
 
897
+ def _checkpoint_required() -> bool:
898
+ """``compression.checkpoint_required`` needs the memory provider loaded to write the pre-compression checkpoint."""
899
+ try:
900
+ from hermes_cli.config import load_config
901
+
902
+ return ((load_config() or {}).get("compression") or {}).get("checkpoint_required") is True
903
+ except Exception: # noqa: BLE001
904
+ return False
905
+
906
+
907
+ #: Compressed results awaiting the server's write: ``commit_token -> (agent, profile home, expiry timer)``.
908
+ _PENDING_COMPRESSIONS: dict[str, tuple[Any, Any, threading.Timer]] = {}
909
+ _PENDING_COMPRESSIONS_LOCK = threading.Lock()
910
+ #: A server that never answers (restart, crash) gets its compression discarded after this long.
911
+ _PENDING_COMPRESSION_TTL = 600.0
912
+
913
+
914
+ def _release_compression(agent, committed: bool) -> None:
915
+ """Emit (committed) or discard the Agent's deferred context-engine notification, then close the throwaway agent."""
916
+ from agent.conversation_compression import finalize_context_engine_compression_notification
917
+
918
+ try:
919
+ finalize_context_engine_compression_notification(agent, committed=committed)
920
+ finally:
921
+ with contextlib.suppress(Exception):
922
+ agent._end_session_on_close = False
923
+ close = getattr(agent, "close", None)
924
+ if callable(close):
925
+ try:
926
+ close()
927
+ except Exception: # noqa: BLE001
928
+ log.debug("compression agent close failed", exc_info=True)
929
+
930
+
931
+ def finalize_compression(token: str, committed: bool) -> bool:
932
+ """``chat.compress_finalize``: the server reports whether it installed the result; unknown tokens are a no-op."""
933
+ with _PENDING_COMPRESSIONS_LOCK:
934
+ entry = _PENDING_COMPRESSIONS.pop(token, None)
935
+ if entry is None:
936
+ return False
937
+ agent, home, expiry = entry
938
+ expiry.cancel() # a no-op when the timer itself is finalizing
939
+ with scoped_home(home):
940
+ _release_compression(agent, committed=committed)
941
+ return True
942
+
943
+
944
+ def compress(ctx: CallContext, params: dict) -> dict:
945
+ """Manual ``/compress`` of ``conversation_history`` through the Agent's shared core (``compress_now``) on a throwaway
946
+ agent, like the gateway's ``_run_manual_compression``. The server owns the transcript: it re-checks the session and
947
+ installs ``messages`` itself, so nothing here touches history or the cached turn agent. The agent has no session
948
+ store, so the Agent neither rotates nor writes state.db; its one deferred effect, the context-engine notification,
949
+ waits for ``chat.compress_finalize`` with the returned ``commit_token`` (two-phase, like the gateway's commit)."""
950
+ home = profile_home_param(params)
951
+ session_id = str(params.get("session_id") or "").strip()
952
+ if not session_id:
953
+ raise InvalidParams("session_id is required")
954
+ history = params.get("conversation_history")
955
+ if not isinstance(history, list):
956
+ raise InvalidParams("conversation_history must be a list")
957
+ model = str(params.get("model") or "").strip()
958
+ provider = str(params.get("model_provider") or "").strip() or None
959
+ focus_topic = str(params.get("focus_topic") or "").strip()[:500] or None
960
+ toolsets = _turn_toolsets(params.get("enabled_toolsets"))
961
+ runtime = _resolve_runtime(provider, model)
962
+ if not runtime.get("api_key"):
963
+ raise RpcError("No provider configured -- cannot compress.", condition="credential_missing")
964
+ from agent.conversation_compression_manual import CompressRequest, compress_now
965
+
966
+ AIAgent = _agent_class()
967
+ kwargs: dict = dict(
968
+ model=model or str(runtime.get("model") or ""),
969
+ provider=provider or runtime.get("provider"),
970
+ base_url=runtime.get("base_url"),
971
+ api_key=runtime.get("api_key"),
972
+ platform="webui",
973
+ quiet_mode=True,
974
+ enabled_toolsets=toolsets,
975
+ session_id=session_id,
976
+ )
977
+ for name, value in (
978
+ ("api_mode", runtime.get("api_mode")),
979
+ ("acp_command", runtime.get("acp_command")),
980
+ ("acp_args", runtime.get("acp_args")),
981
+ ("credential_pool", runtime.get("credential_pool")),
982
+ ("gateway_session_key", session_id),
983
+ ("skip_memory", not _checkpoint_required()),
984
+ ):
985
+ if _supported(AIAgent, name) and value is not None:
986
+ kwargs[name] = value
987
+ agent = AIAgent(**kwargs)
988
+ held = False
989
+ try:
990
+ result = compress_now(agent, history, CompressRequest(focus_topic=focus_topic), task_id=session_id)
991
+ message = None
992
+ if result.status != "compressed":
993
+ from agent.conversation_compression_manual import render_compress_result
994
+
995
+ message = "\n".join(render_compress_result(result)) or None
996
+ payload = {
997
+ "status": result.status,
998
+ "messages": json.loads(json.dumps([m for m in result.after_messages if isinstance(m, dict)], default=str)),
999
+ "before_tokens": int(result.before_tokens or 0),
1000
+ "after_tokens": int(result.after_tokens or 0),
1001
+ "summary": json.loads(json.dumps(result.summary, default=str)) if isinstance(result.summary, dict) else None,
1002
+ "message": message,
1003
+ "agent_session_id": str(getattr(agent, "session_id", None) or session_id),
1004
+ "commit_token": None,
1005
+ }
1006
+ if result.status == "compressed":
1007
+ token = uuid.uuid4().hex
1008
+ # A server that never answers (restart, failed RPC) gets the compression discarded on its own.
1009
+ expiry = threading.Timer(_PENDING_COMPRESSION_TTL, finalize_compression, args=(token, False))
1010
+ expiry.daemon = True
1011
+ with _PENDING_COMPRESSIONS_LOCK:
1012
+ _PENDING_COMPRESSIONS[token] = (agent, home, expiry)
1013
+ expiry.start()
1014
+ held = True
1015
+ payload["commit_token"] = token
1016
+ return payload
1017
+ finally:
1018
+ if not held:
1019
+ _release_compression(agent, committed=False)
1020
+
1021
+
728
1022
  def _run_for(params: dict) -> _Run | None:
729
1023
  stream_id = str(params.get("stream_id") or "").strip()
730
1024
  session_id = str(params.get("session_id") or "").strip()
@@ -748,6 +1042,18 @@ def register(registry) -> None:
748
1042
  with _turn_identity(session_id, workspace):
749
1043
  return start(ctx, params)
750
1044
 
1045
+ @registry.method("chat.compress")
1046
+ def compress_(ctx: CallContext, params: dict) -> dict:
1047
+ with scoped_home(profile_home_param(params)):
1048
+ return compress(ctx, params)
1049
+
1050
+ @registry.method("chat.compress_finalize", requires_agent=False)
1051
+ def compress_finalize_(ctx: CallContext, params: dict) -> dict:
1052
+ token = str(params.get("commit_token") or "").strip()
1053
+ if not token:
1054
+ raise InvalidParams("commit_token is required")
1055
+ return {"finalized": finalize_compression(token, params.get("committed") is True)}
1056
+
751
1057
  @registry.method("chat.interrupt", requires_agent=False)
752
1058
  def interrupt_(ctx: CallContext, params: dict) -> dict:
753
1059
  run = _run_for(params)
@@ -786,18 +1092,43 @@ def register(registry) -> None:
786
1092
  except Exception as exc: # noqa: BLE001
787
1093
  log.debug("steer failed: %s", exc)
788
1094
  return {"accepted": False, "fallback": "steer_error"}
789
- return {"accepted": accepted, "fallback": None if accepted else "not_running"}
1095
+ # `can_redirect`: whether this Agent can deliver a pending steer now (TAL-424 "Send now").
1096
+ return {"accepted": accepted, "fallback": None if accepted else "not_running", "can_redirect": callable(getattr(run.agent, "redirect", None))}
1097
+
1098
+ @registry.method("chat.steer_withdraw", requires_agent=False)
1099
+ def steer_withdraw_(ctx: CallContext, params: dict) -> dict:
1100
+ pending, index = _steer_queue_params(params)
1101
+ run = _run_for(params)
1102
+ if run is None or run.agent is None:
1103
+ return {"withdrawn": False}
1104
+ return {"withdrawn": withdraw_steer(run.agent, pending, index)}
1105
+
1106
+ @registry.method("chat.steer_now", requires_agent=False)
1107
+ def steer_now_(ctx: CallContext, params: dict) -> dict:
1108
+ pending, index = _steer_queue_params(params)
1109
+ run = _run_for(params)
1110
+ if run is None or run.agent is None:
1111
+ return {"redirected": False, "withdrawn": False}
1112
+ return steer_now(run.agent, pending, index)
790
1113
 
791
1114
  @registry.method("chat.evict_agent", requires_agent=False)
792
1115
  def evict_(ctx: CallContext, params: dict) -> dict:
1116
+ """Drop a session's cached agent. Only delete/clear (``clear_session``) end its approval grants, parked
1117
+ approvals and kernels; a model switch, truncate or compress leaves a running turn's agent in place."""
793
1118
  session_id = str(params.get("session_id") or "").strip()
1119
+ end_session = params.get("clear_session") is True
794
1120
  with _AGENT_CACHE_LOCK:
1121
+ with _RUNS_LOCK:
1122
+ run = _RUNS.get(_RUNS_BY_SESSION.get(session_id) or "")
1123
+ if not end_session and run is not None and not run.finished.is_set():
1124
+ return {"evicted": False}
795
1125
  evicted = _AGENT_CACHE.pop(session_id, None) is not None
796
- try:
797
- from tools.approval import clear_session
798
- clear_session(session_id)
799
- except Exception: # noqa: BLE001
800
- pass
1126
+ if end_session:
1127
+ try:
1128
+ from tools.approval import clear_session
1129
+ clear_session(session_id)
1130
+ except Exception: # noqa: BLE001
1131
+ pass
801
1132
  return {"evicted": evicted}
802
1133
 
803
1134
  @registry.method("chat.commit_memory", requires_agent=False)
@@ -874,7 +1205,9 @@ def register(registry) -> None:
874
1205
  except Exception as exc: # noqa: BLE001
875
1206
  raise RpcError(f"approval module unavailable: {exc}", condition="agent_unavailable") from exc
876
1207
  (enable_session_yolo if enabled else disable_session_yolo)(session_id)
877
- released = resolve_gateway_approval(session_id, "session", resolve_all=True) if enabled else 0
1208
+ # Parked approvals are released `once`: a `session` or `always` grant would keep their patterns approved after
1209
+ # YOLO turns off, whatever choice the card that enabled it carried.
1210
+ released = resolve_gateway_approval(session_id, "once", resolve_all=True) if enabled else 0
878
1211
  return {"yolo_enabled": bool(is_session_yolo_enabled(session_id)), "released": int(released or 0)}
879
1212
 
880
1213
  @registry.method("clarify.respond", requires_agent=False)
@@ -7,8 +7,9 @@ import threading
7
7
  from typing import Any
8
8
 
9
9
  from ..errors import InvalidParams, RpcError
10
- from ..home import profile_home_param, scoped_home
10
+ from ..home import _is_named_profile, profile_home_param, scoped_home
11
11
  from ..rpc import CallContext
12
+ from .providers import plugin_providers
12
13
 
13
14
  log = logging.getLogger("talaria_sidecar.commands")
14
15
 
@@ -60,19 +61,54 @@ def list_commands() -> list[dict[str, Any]]:
60
61
  return out
61
62
 
62
63
 
64
+ def retire_unscoped_launch_mcp_servers() -> set[str]:
65
+ """Close the launch profile's connections opened before multiplexing started; returns their server names.
66
+
67
+ Until the first named-profile call, the launch profile's connections and tools are unscoped (process-wide). Once
68
+ multiplexing is active, a scoped shutdown never selects them and scoped discovery opens duplicates beside them, so
69
+ the launch profile's own calls close them first and its scope reconnects them from the current config."""
70
+ from agent.secret_scope import is_multiplex_active
71
+ from hermes_constants import get_hermes_home
72
+ from tools.mcp_tool import _lock, _servers
73
+ from tools.mcp_tool_lifecycle import shutdown_mcp_servers
74
+ from tools.mcp_tool_scope import _key_name, _key_scope
75
+
76
+ if not is_multiplex_active() or _is_named_profile(get_hermes_home()):
77
+ return set()
78
+ with _lock:
79
+ names = {_key_name(key) for key in _servers if _key_scope(key) is None}
80
+ if names:
81
+ shutdown_mcp_servers(scope=None, names=names)
82
+ return names
83
+
84
+
63
85
  def _reload_mcp() -> str:
64
86
  with _RELOAD_MCP_LOCK:
65
87
  try:
66
- from tools.mcp_tool import _lock, _servers, discover_mcp_tools, shutdown_mcp_servers
88
+ from agent.secret_scope import is_multiplex_active
89
+ from tools.mcp_tool import _lock, _mcp_tool_server_names, _server_visible_in_scope, _servers
90
+ from tools.mcp_tool_discovery import discover_mcp_tools
91
+ from tools.mcp_tool_lifecycle import shutdown_mcp_servers
92
+ from tools.mcp_tool_scope import _key_name
93
+ from tools.registry import registry
67
94
  except Exception as exc: # noqa: BLE001
68
95
  raise RpcError("MCP runtime unavailable", condition="mcp_unavailable") from exc
69
- try:
70
- with _lock:
71
- old = set(_servers.keys())
72
- shutdown_mcp_servers()
73
- tools = discover_mcp_tools()
96
+ # Like the gateway's ``_execute_mcp_reload``: under multiplexing only this profile's connections go down and
97
+ # come back. The bare shutdown is the process-wide wildcard, which would drop every other profile's servers.
98
+ scope = registry.current_scope_key() if is_multiplex_active() else None
99
+
100
+ def server_names() -> set[str]:
74
101
  with _lock:
75
- connected = set(_servers.keys())
102
+ return {_key_name(key) for key in _servers if _server_visible_in_scope(key, scope)}
103
+
104
+ try:
105
+ old = server_names() | retire_unscoped_launch_mcp_servers()
106
+ shutdown_mcp_servers(scope=scope)
107
+ tools = discover_mcp_tools() or []
108
+ connected = server_names()
109
+ if scope is not None:
110
+ with _lock:
111
+ tools = [name for name in tools if _mcp_tool_server_names.get(name) in connected]
76
112
  except Exception as exc: # noqa: BLE001
77
113
  log.warning("Failed to reload MCP servers", exc_info=True)
78
114
  raise RpcError("Failed to reload MCP servers", condition="mcp_reload_failed") from exc
@@ -84,7 +120,7 @@ def _reload_mcp() -> str:
84
120
  lines.append(f"Added: {', '.join(sorted(added))}")
85
121
  if removed:
86
122
  lines.append(f"Removed: {', '.join(sorted(removed))}")
87
- lines.append(f"{len(tools or [])} tool(s) available across {len(connected)} server(s)" if connected else "No MCP servers connected")
123
+ lines.append(f"{len(tools)} tool(s) available across {len(connected)} server(s)" if connected else "No MCP servers connected")
88
124
  if not reconnected and not added and not removed:
89
125
  lines.append("Tooling state was already current")
90
126
  return "\n".join(lines)
@@ -230,27 +266,6 @@ def resolve_moa_config(preset: str | None) -> dict:
230
266
  return resolved
231
267
 
232
268
 
233
- def plugin_provider_profiles() -> list[dict]:
234
- """Model-provider plugins registered with the Agent's provider registry."""
235
- try:
236
- from providers import list_providers
237
- except Exception: # noqa: BLE001
238
- return []
239
- out = []
240
- try:
241
- for profile in list_providers():
242
- name = str(getattr(profile, "name", "") or "").strip().lower()
243
- if not name:
244
- continue
245
- env_vars = [str(v) for v in (getattr(profile, "env_vars", ()) or ())]
246
- api_key_env = next((v for v in env_vars if not v.upper().endswith(("_BASE_URL", "_URL", "_FOLDER_ID"))), None)
247
- out.append({"name": name, "display_name": str(getattr(profile, "display_name", "") or name).strip(), "env_vars": env_vars, "api_key_env": api_key_env})
248
- except Exception: # noqa: BLE001
249
- log.debug("Failed to enumerate model-provider plugins", exc_info=True)
250
- return []
251
- return out
252
-
253
-
254
269
  PLUGIN_VISIBILITY_HOOKS = ("pre_tool_call", "post_tool_call", "pre_llm_call", "post_llm_call")
255
270
 
256
271
 
@@ -332,7 +347,7 @@ def register(registry) -> None:
332
347
  @registry.method("plugins.providers")
333
348
  def providers(ctx: CallContext, params: dict) -> dict:
334
349
  with scoped_home(profile_home_param(params)):
335
- return {"providers": plugin_provider_profiles()}
350
+ return {"providers": plugin_providers()}
336
351
 
337
352
  @registry.method("plugins.list")
338
353
  def list_(ctx: CallContext, params: dict) -> dict: