@maudecode/talaria-web 1.19.0 → 1.20.0

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 (591) hide show
  1. package/_release.json +8 -8
  2. package/dist/api/automation-raw.js +61 -6
  3. package/dist/api/automation-raw.js.map +1 -1
  4. package/dist/api/automation-router.d.ts +8 -6
  5. package/dist/api/automation-router.js +7 -1
  6. package/dist/api/automation-router.js.map +1 -1
  7. package/dist/api/chat-router.d.ts +258 -33
  8. package/dist/api/chat-router.js +60 -25
  9. package/dist/api/chat-router.js.map +1 -1
  10. package/dist/api/git-router.d.ts +4 -0
  11. package/dist/api/git-router.js +1 -1
  12. package/dist/api/git-router.js.map +1 -1
  13. package/dist/api/raw-routes.js +13 -1
  14. package/dist/api/raw-routes.js.map +1 -1
  15. package/dist/api/router.d.ts +2 -2
  16. package/dist/api/session-visibility.js +2 -2
  17. package/dist/api/session-visibility.js.map +1 -1
  18. package/dist/api/sessions-router.d.ts +787 -107
  19. package/dist/api/sessions-router.js +16 -4
  20. package/dist/api/sessions-router.js.map +1 -1
  21. package/dist/api/settings-router.d.ts +34 -13
  22. package/dist/api/settings-router.js +54 -17
  23. package/dist/api/settings-router.js.map +1 -1
  24. package/dist/api/sse-routes.js +41 -19
  25. package/dist/api/sse-routes.js.map +1 -1
  26. package/dist/api/tools-router.d.ts +314 -32
  27. package/dist/api/tools-router.js +135 -38
  28. package/dist/api/tools-router.js.map +1 -1
  29. package/dist/app.js +15 -11
  30. package/dist/app.js.map +1 -1
  31. package/dist/auth/crypto.d.ts +3 -0
  32. package/dist/auth/crypto.js +18 -0
  33. package/dist/auth/crypto.js.map +1 -1
  34. package/dist/auth/gate.js +5 -1
  35. package/dist/auth/gate.js.map +1 -1
  36. package/dist/cli/ctl.js +6 -1
  37. package/dist/cli/ctl.js.map +1 -1
  38. package/dist/cli/main.js +23 -18
  39. package/dist/cli/main.js.map +1 -1
  40. package/dist/cli/supervise.d.ts +22 -0
  41. package/dist/cli/supervise.js +80 -2
  42. package/dist/cli/supervise.js.map +1 -1
  43. package/dist/config/agent-config.d.ts +3 -0
  44. package/dist/config/agent-config.js +36 -9
  45. package/dist/config/agent-config.js.map +1 -1
  46. package/dist/fs/atomic.d.ts +3 -0
  47. package/dist/fs/atomic.js +19 -5
  48. package/dist/fs/atomic.js.map +1 -1
  49. package/dist/fs/stat.d.ts +6 -0
  50. package/dist/fs/stat.js +18 -2
  51. package/dist/fs/stat.js.map +1 -1
  52. package/dist/http/context.d.ts +14 -0
  53. package/dist/http/context.js +48 -5
  54. package/dist/http/context.js.map +1 -1
  55. package/dist/profiles/profiles.d.ts +2 -0
  56. package/dist/profiles/profiles.js +1 -0
  57. package/dist/profiles/profiles.js.map +1 -1
  58. package/dist/providers/catalog.d.ts +11 -6
  59. package/dist/providers/catalog.js +76 -24
  60. package/dist/providers/catalog.js.map +1 -1
  61. package/dist/redact.js +38 -14
  62. package/dist/redact.js.map +1 -1
  63. package/dist/release.js +5 -3
  64. package/dist/release.js.map +1 -1
  65. package/dist/runtime.js +85 -21
  66. package/dist/runtime.js.map +1 -1
  67. package/dist/server.js +24 -21
  68. package/dist/server.js.map +1 -1
  69. package/dist/sessions/anchor.d.ts +2 -1
  70. package/dist/sessions/anchor.js +5 -1
  71. package/dist/sessions/anchor.js.map +1 -1
  72. package/dist/sessions/background-tasks.d.ts +23 -3
  73. package/dist/sessions/background-tasks.js +74 -4
  74. package/dist/sessions/background-tasks.js.map +1 -1
  75. package/dist/sessions/cli-sessions.d.ts +9 -2
  76. package/dist/sessions/cli-sessions.js +38 -57
  77. package/dist/sessions/cli-sessions.js.map +1 -1
  78. package/dist/sessions/completions.d.ts +8 -0
  79. package/dist/sessions/completions.js +55 -25
  80. package/dist/sessions/completions.js.map +1 -1
  81. package/dist/sessions/export.d.ts +2 -0
  82. package/dist/sessions/export.js +6 -2
  83. package/dist/sessions/export.js.map +1 -1
  84. package/dist/sessions/gateway-sessions.d.ts +44 -4
  85. package/dist/sessions/gateway-sessions.js +626 -90
  86. package/dist/sessions/gateway-sessions.js.map +1 -1
  87. package/dist/sessions/gateway-watcher.d.ts +10 -11
  88. package/dist/sessions/gateway-watcher.js +25 -21
  89. package/dist/sessions/gateway-watcher.js.map +1 -1
  90. package/dist/sessions/list.d.ts +9 -2
  91. package/dist/sessions/list.js +38 -26
  92. package/dist/sessions/list.js.map +1 -1
  93. package/dist/sessions/merge.d.ts +11 -5
  94. package/dist/sessions/merge.js +59 -17
  95. package/dist/sessions/merge.js.map +1 -1
  96. package/dist/sessions/prefix-index.d.ts +7 -4
  97. package/dist/sessions/prefix-index.js +21 -10
  98. package/dist/sessions/prefix-index.js.map +1 -1
  99. package/dist/sessions/recovery.d.ts +1 -1
  100. package/dist/sessions/recovery.js +4 -3
  101. package/dist/sessions/recovery.js.map +1 -1
  102. package/dist/sessions/service.d.ts +107 -37
  103. package/dist/sessions/service.js +490 -156
  104. package/dist/sessions/service.js.map +1 -1
  105. package/dist/sessions/session.d.ts +2 -0
  106. package/dist/sessions/session.js +4 -0
  107. package/dist/sessions/session.js.map +1 -1
  108. package/dist/sessions/state-db-worker.js +22 -5
  109. package/dist/sessions/state-db-worker.js.map +1 -1
  110. package/dist/sessions/state-db.d.ts +106 -15
  111. package/dist/sessions/state-db.js +651 -236
  112. package/dist/sessions/state-db.js.map +1 -1
  113. package/dist/sessions/state-saved.d.ts +1 -1
  114. package/dist/sessions/state-saved.js +35 -11
  115. package/dist/sessions/state-saved.js.map +1 -1
  116. package/dist/sessions/storage/backend.d.ts +10 -1
  117. package/dist/sessions/storage/backend.js.map +1 -1
  118. package/dist/sessions/storage/conformance.js +1 -1
  119. package/dist/sessions/storage/conformance.js.map +1 -1
  120. package/dist/sessions/storage/json-documents.d.ts +2 -0
  121. package/dist/sessions/storage/json-documents.js +70 -26
  122. package/dist/sessions/storage/json-documents.js.map +1 -1
  123. package/dist/sessions/storage/json-files.d.ts +13 -2
  124. package/dist/sessions/storage/json-files.js +126 -6
  125. package/dist/sessions/storage/json-files.js.map +1 -1
  126. package/dist/sessions/storage/json-journal.d.ts +6 -7
  127. package/dist/sessions/storage/json-journal.js +143 -73
  128. package/dist/sessions/storage/json-journal.js.map +1 -1
  129. package/dist/sessions/storage/json.d.ts +15 -1
  130. package/dist/sessions/storage/json.js +125 -33
  131. package/dist/sessions/storage/json.js.map +1 -1
  132. package/dist/sessions/store.d.ts +5 -1
  133. package/dist/sessions/store.js +67 -27
  134. package/dist/sessions/store.js.map +1 -1
  135. package/dist/sessions/streams.d.ts +8 -7
  136. package/dist/sessions/streams.js +49 -14
  137. package/dist/sessions/streams.js.map +1 -1
  138. package/dist/sessions/tool-display.d.ts +3 -1
  139. package/dist/sessions/tool-display.js +6 -0
  140. package/dist/sessions/tool-display.js.map +1 -1
  141. package/dist/sessions/turn-context.js +6 -2
  142. package/dist/sessions/turn-context.js.map +1 -1
  143. package/dist/sessions/turn.d.ts +23 -5
  144. package/dist/sessions/turn.js +168 -78
  145. package/dist/sessions/turn.js.map +1 -1
  146. package/dist/sidecar/client.d.ts +24 -1
  147. package/dist/sidecar/client.js +118 -38
  148. package/dist/sidecar/client.js.map +1 -1
  149. package/dist/sidecar/fake.d.ts +6 -2
  150. package/dist/sidecar/fake.js +15 -2
  151. package/dist/sidecar/fake.js.map +1 -1
  152. package/dist/spa.d.ts +9 -4
  153. package/dist/spa.js +36 -12
  154. package/dist/spa.js.map +1 -1
  155. package/dist/startup-timing.d.ts +66 -0
  156. package/dist/startup-timing.js +129 -0
  157. package/dist/startup-timing.js.map +1 -0
  158. package/dist/test/fs-handles.d.ts +11 -0
  159. package/dist/test/fs-handles.js +31 -0
  160. package/dist/test/fs-handles.js.map +1 -0
  161. package/dist/tools/connectors.d.ts +1 -1
  162. package/dist/tools/connectors.js +2 -2
  163. package/dist/tools/connectors.js.map +1 -1
  164. package/dist/tools/crons.d.ts +8 -4
  165. package/dist/tools/crons.js +25 -18
  166. package/dist/tools/crons.js.map +1 -1
  167. package/dist/tools/dashboard-plugins.d.ts +29 -0
  168. package/dist/tools/dashboard-plugins.js +132 -0
  169. package/dist/tools/dashboard-plugins.js.map +1 -1
  170. package/dist/tools/gateway-restart.d.ts +25 -5
  171. package/dist/tools/gateway-restart.js +103 -23
  172. package/dist/tools/gateway-restart.js.map +1 -1
  173. package/dist/tools/health.d.ts +13 -1
  174. package/dist/tools/health.js +80 -22
  175. package/dist/tools/health.js.map +1 -1
  176. package/dist/tools/insights.d.ts +1 -1
  177. package/dist/tools/insights.js +2 -2
  178. package/dist/tools/insights.js.map +1 -1
  179. package/dist/tools/mcp-health.js +74 -24
  180. package/dist/tools/mcp-health.js.map +1 -1
  181. package/dist/tools/mcp.js +19 -1
  182. package/dist/tools/mcp.js.map +1 -1
  183. package/dist/tools/plugin-oauth.d.ts +14 -0
  184. package/dist/tools/plugin-oauth.js +53 -0
  185. package/dist/tools/plugin-oauth.js.map +1 -0
  186. package/dist/tools/prune-advice.d.ts +50 -0
  187. package/dist/tools/prune-advice.js +114 -0
  188. package/dist/tools/prune-advice.js.map +1 -0
  189. package/dist/tools/skills.d.ts +5 -2
  190. package/dist/tools/skills.js +9 -6
  191. package/dist/tools/skills.js.map +1 -1
  192. package/dist/tools/update-notifications.d.ts +73 -7
  193. package/dist/tools/update-notifications.js +277 -43
  194. package/dist/tools/update-notifications.js.map +1 -1
  195. package/dist/tools/updates.d.ts +8 -1
  196. package/dist/tools/updates.js +51 -17
  197. package/dist/tools/updates.js.map +1 -1
  198. package/dist/workspace/media.js +69 -20
  199. package/dist/workspace/media.js.map +1 -1
  200. package/dist/workspace/preview.js +4 -14
  201. package/dist/workspace/preview.js.map +1 -1
  202. package/dist/workspace/rollback.d.ts +6 -2
  203. package/dist/workspace/rollback.js +17 -7
  204. package/dist/workspace/rollback.js.map +1 -1
  205. package/package.json +3 -3
  206. package/sidecar/agent_dependency.json +4 -4
  207. package/sidecar/agent_support.json +17 -14
  208. package/sidecar/scripts/suite_runner.py +183 -27
  209. package/sidecar/scripts/test.sh +5 -7
  210. package/sidecar/talaria_sidecar/__init__.py +1 -1
  211. package/sidecar/talaria_sidecar/__main__.py +10 -3
  212. package/sidecar/talaria_sidecar/agent_mutation.py +159 -23
  213. package/sidecar/talaria_sidecar/agent_services.py +74 -11
  214. package/sidecar/talaria_sidecar/context_detail.py +59 -0
  215. package/sidecar/talaria_sidecar/home.py +97 -20
  216. package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
  217. package/sidecar/talaria_sidecar/methods/aux.py +80 -18
  218. package/sidecar/talaria_sidecar/methods/chat.py +571 -106
  219. package/sidecar/talaria_sidecar/methods/checkpoints.py +29 -4
  220. package/sidecar/talaria_sidecar/methods/commands.py +277 -16
  221. package/sidecar/talaria_sidecar/methods/config.py +73 -3
  222. package/sidecar/talaria_sidecar/methods/connectors.py +45 -13
  223. package/sidecar/talaria_sidecar/methods/cron.py +8 -2
  224. package/sidecar/talaria_sidecar/methods/dashboard_plugins.py +301 -0
  225. package/sidecar/talaria_sidecar/methods/gateway.py +24 -4
  226. package/sidecar/talaria_sidecar/methods/gateway_sessions.py +211 -27
  227. package/sidecar/talaria_sidecar/methods/kanban.py +2 -3
  228. package/sidecar/talaria_sidecar/methods/oauth.py +139 -11
  229. package/sidecar/talaria_sidecar/methods/plugin_catalog.py +198 -0
  230. package/sidecar/talaria_sidecar/methods/plugin_oauth.py +234 -0
  231. package/sidecar/talaria_sidecar/methods/process.py +70 -6
  232. package/sidecar/talaria_sidecar/methods/profiles.py +4 -3
  233. package/sidecar/talaria_sidecar/methods/runtime.py +2 -0
  234. package/sidecar/talaria_sidecar/methods/skills.py +22 -7
  235. package/sidecar/talaria_sidecar/methods/state_db.py +128 -42
  236. package/sidecar/talaria_sidecar/methods/toolsets.py +4 -2
  237. package/sidecar/talaria_sidecar/methods/worktree.py +121 -2
  238. package/sidecar/talaria_sidecar/rpc.py +25 -3
  239. package/sidecar/talaria_sidecar/runtime.py +42 -3
  240. package/sidecar/talaria_sidecar/speed.py +13 -1
  241. package/sidecar/talaria_sidecar/startup_timing.py +119 -0
  242. package/static/dist/FILES.txt +241 -213
  243. package/static/dist/THIRD_PARTY_LICENSES.md +6 -22
  244. package/static/dist/assets/AppShell-3isgvoro.js +8 -0
  245. package/static/dist/assets/Button-f4n6dapf.js +1 -0
  246. package/static/dist/assets/ChatPage-k7b6xfax.js +4 -0
  247. package/static/dist/assets/ConnectTargets-juqvt5lm.js +1 -0
  248. package/static/dist/assets/Dialog-hta5utr3.js +1 -0
  249. package/static/dist/assets/ExtensionRoute-zrhjn0wm.js +1 -0
  250. package/static/dist/assets/Field-b6ez48oz.js +1 -0
  251. package/static/dist/assets/HubRoute-iwvso2tz.js +2 -0
  252. package/static/dist/assets/LinkCheckContext-ix6zqqkk.js +1 -0
  253. package/static/dist/assets/Markdown-jdeth7zq.js +2 -0
  254. package/static/dist/assets/MarkdownRenderer-fbswsgld.js +302 -0
  255. package/static/dist/assets/MarkdownRenderer-i9o6nr38.css +1 -0
  256. package/static/dist/assets/OAuthSignIn-bpd8hom3.js +1 -0
  257. package/static/dist/assets/ResourceSwitch-odi6dy2o.js +1 -0
  258. package/static/dist/assets/RightPanel-eu6enlo6.js +1 -0
  259. package/static/dist/assets/Select-kr07928w.js +1 -0
  260. package/static/dist/assets/SessionListPanel-etb2i54l.js +1 -0
  261. package/static/dist/assets/SettingsLayout-eu3xxp23.js +1 -0
  262. package/static/dist/assets/States-icab19yg.js +1 -0
  263. package/static/dist/assets/TerminalPanel-hngmsr8b.js +16 -0
  264. package/static/dist/assets/Toaster-nb2uzjw1.js +1 -0
  265. package/static/dist/assets/_app-ga1moszl.js +1 -0
  266. package/static/dist/assets/_app.ext._extensionId-na62087t.js +1 -0
  267. package/static/dist/assets/_app.index-jypgdqoi.js +1 -0
  268. package/static/dist/assets/_app.insights-kabejdtm.js +1 -0
  269. package/static/dist/assets/_app.kanban-m19kfcmx.js +1 -0
  270. package/static/dist/assets/_app.logs-iw4m96sz.js +1 -0
  271. package/static/dist/assets/_app.memory-jq5frwcg.js +1 -0
  272. package/static/dist/assets/_app.profiles-kwa3m7rp.js +1 -0
  273. package/static/dist/assets/_app.session._sessionId-o5lyeipj.js +1 -0
  274. package/static/dist/assets/_app.settings-ckl8hdll.js +1 -0
  275. package/static/dist/assets/_app.settings._section-dtdjk98a.js +3 -0
  276. package/static/dist/assets/_app.skills-n016rihd.js +1 -0
  277. package/static/dist/assets/_app.tasks-n8zuqsqe.js +1 -0
  278. package/static/dist/assets/_app.todos-btew5v2s.js +1 -0
  279. package/static/dist/assets/_app.workspaces-lnfk95ch.js +1 -0
  280. package/static/dist/assets/abnfDiagram-VCTEODGH-dttheegb.js +1 -0
  281. package/static/dist/assets/{angular-html-nu6l31w3.js → angular-html-l28bd5fo.js} +1 -1
  282. package/static/dist/assets/{angular-ts-fbx4f8mm.js → angular-ts-ffzx1vj0.js} +1 -1
  283. package/static/dist/assets/{apl-bx2xr7dh.js → apl-b7h7mmy4.js} +1 -1
  284. package/static/dist/assets/{arc-7popfhbk.js → arc-fk1b21lm.js} +1 -1
  285. package/static/dist/assets/architecture-7GRP2DOG-ic435919.js +1 -0
  286. package/static/dist/assets/architectureDiagram-5GKGNRK7-8r3glgiq.js +36 -0
  287. package/static/dist/assets/archive-il7c6053.js +1 -0
  288. package/static/dist/assets/{astro-g77i97cb.js → astro-k117fy0t.js} +1 -1
  289. package/static/dist/assets/back-gu3nu279.js +1 -0
  290. package/static/dist/assets/{blade-boo0u92y.js → blade-npefsmn8.js} +1 -1
  291. package/static/dist/assets/{blockDiagram-I7D4REHJ-joedghi3.js → blockDiagram-I7D4REHJ-jenyos6c.js} +4 -4
  292. package/static/dist/assets/{c-cat0w3sc.js → c-2umg6kbn.js} +1 -1
  293. package/static/dist/assets/{c4Diagram-7LVT6UL2-hyrjrohe.js → c4Diagram-7LVT6UL2-nzlx7yc4.js} +1 -1
  294. package/static/dist/assets/cancel-bsnfo36u.js +1 -0
  295. package/static/dist/assets/channel-dsbtk01y.js +1 -0
  296. package/static/dist/assets/{chunk-4HAMMTFA-koouqbut.js → chunk-4HAMMTFA-g0cru63b.js} +20 -20
  297. package/static/dist/assets/{chunk-75Z2AOVW-bl4bhdnt.js → chunk-75Z2AOVW-7rkswkjw.js} +1 -1
  298. package/static/dist/assets/chunk-DU6HZSFF-fn73jzdo.js +128 -0
  299. package/static/dist/assets/{chunk-F27PBJKO-d8xxi0zi.js → chunk-F27PBJKO-oey8e72m.js} +1 -1
  300. package/static/dist/assets/{chunk-FOHPRMQF-opuflp73.js → chunk-FOHPRMQF-ipewusgj.js} +8 -8
  301. package/static/dist/assets/{chunk-GMAD6QVW-jro0hl73.js → chunk-GMAD6QVW-l2a32wiy.js} +6 -6
  302. package/static/dist/assets/{chunk-GVQU2GXP-djn6gbyu.js → chunk-GVQU2GXP-o9dwzh8h.js} +1 -1
  303. package/static/dist/assets/{chunk-IMKFNOWR-vw4p5p7o.js → chunk-IMKFNOWR-eblwj20b.js} +5 -5
  304. package/static/dist/assets/{chunk-L3NEJ4N5-n1hj77q8.js → chunk-L3NEJ4N5-o2mufspb.js} +1 -1
  305. package/static/dist/assets/chunk-OSK3NFVY-ijp9pw29.js +10 -0
  306. package/static/dist/assets/{chunk-P2QGCYS3-onf1hz6c.js → chunk-P2QGCYS3-hs9hqwl4.js} +1 -1
  307. package/static/dist/assets/{chunk-POPQ4Y6H-m8sqn7cs.js → chunk-POPQ4Y6H-bp9rsqez.js} +1 -1
  308. package/static/dist/assets/{chunk-PWAF6VOD-ek4smc8x.js → chunk-PWAF6VOD-brkprov6.js} +1 -1
  309. package/static/dist/assets/{chunk-SHT3W25Y-g9gfvefo.js → chunk-SHT3W25Y-gmpv0pxz.js} +13 -13
  310. package/static/dist/assets/{chunk-SVP7TREG-f9nfv2cu.js → chunk-SVP7TREG-oldm08fu.js} +4 -4
  311. package/static/dist/assets/{chunk-TICWLB2K-bnk3m1mq.js → chunk-TICWLB2K-ooqilfv7.js} +6 -6
  312. package/static/dist/assets/{chunk-XXDRQBXY-n40pf9vy.js → chunk-XXDRQBXY-jkmnqu65.js} +1 -1
  313. package/static/dist/assets/classDiagram-ZZMXUADV-fvgcv24g.js +1 -0
  314. package/static/dist/assets/classDiagram-v2-VYDZK3BY-fvgcv24g.js +1 -0
  315. package/static/dist/assets/{cobol-go622aep.js → cobol-ssf9hkh7.js} +1 -1
  316. package/static/dist/assets/{coffee-jwo1xjhq.js → coffee-lsi9fwae.js} +1 -1
  317. package/static/dist/assets/copied-bz3gkazh.js +1 -0
  318. package/static/dist/assets/copy-jo006coq.js +1 -0
  319. package/static/dist/assets/cose-bilkent-JH36ORCC-layy2r27.js +1 -0
  320. package/static/dist/assets/{cpp-nre7j0kq.js → cpp-lfrh4y55.js} +1 -1
  321. package/static/dist/assets/{crystal-lviso2je.js → crystal-efh4pte7.js} +1 -1
  322. package/static/dist/assets/{css-ywz9j4hb.js → css-i82qdo89.js} +1 -1
  323. package/static/dist/assets/cynefin-OW5HDTMX-d47op7qs.js +1 -0
  324. package/static/dist/assets/cynefinDiagram-5FMLGOSQ-doa7594y.js +62 -0
  325. package/static/dist/assets/cytoscape.esm-fjn2algp.js +321 -0
  326. package/static/dist/assets/dagre-GXQ25YYZ-os768dp4.js +4 -0
  327. package/static/dist/assets/dagre-o5jmjz46.js +1 -0
  328. package/static/dist/assets/{diagram-S7CK7UJ4-m6aeoe7c.js → diagram-S7CK7UJ4-fyi81nxd.js} +4 -4
  329. package/static/dist/assets/diagram-UQ7AKVKN-j3exggaq.js +41 -0
  330. package/static/dist/assets/{diagram-VSXAHHWV-hbawi7hn.js → diagram-VSXAHHWV-omo1en2l.js} +1 -1
  331. package/static/dist/assets/diagram-VX7I27RA-oro2cyu0.js +24 -0
  332. package/static/dist/assets/diagram-Z3DM3KII-byt9vdb7.js +24 -0
  333. package/static/dist/assets/dist-crc46di9.js +7 -0
  334. package/static/dist/assets/dist-etpoyscq.js +44 -0
  335. package/static/dist/assets/{dist-c4c2m3kv.js → dist-nb670eu6.js} +1 -1
  336. package/static/dist/assets/ebnfDiagram-PWID7BFC-ee3haugh.js +1 -0
  337. package/static/dist/assets/{edge-dvv35byi.js → edge-lrob635n.js} +1 -1
  338. package/static/dist/assets/{elixir-i2f1ki1g.js → elixir-kdpxeq93.js} +1 -1
  339. package/static/dist/assets/{elm-cpepyjm4.js → elm-c8irj9uh.js} +1 -1
  340. package/static/dist/assets/erDiagram-RLTQ6QDP-d1e93yff.js +99 -0
  341. package/static/dist/assets/{erb-idlyfheg.js → erb-flwsv146.js} +1 -1
  342. package/static/dist/assets/eventmodeling-NTZA5JFV-i1ihb40c.js +1 -0
  343. package/static/dist/assets/flowDiagram-HODETNUW-jahfpiod.js +1 -0
  344. package/static/dist/assets/folder-plus-ivoglq98.js +1 -0
  345. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ma6jxj5i.js → ganttDiagram-EL5Y4UJY-m7g0jud4.js} +1 -1
  346. package/static/dist/assets/{git-rebase-d9lpfpbn.js → git-rebase-bli19aoq.js} +1 -1
  347. package/static/dist/assets/gitGraph-4MIJSDKK-hzdj07yy.js +1 -0
  348. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-hzltvsod.js → gitGraphDiagram-WWUBYQGX-dm9agbna.js} +1 -1
  349. package/static/dist/assets/{glimmer-js-39i0urfb.js → glimmer-js-d4icg38p.js} +1 -1
  350. package/static/dist/assets/{glimmer-ts-gdtd6n73.js → glimmer-ts-bk75772u.js} +1 -1
  351. package/static/dist/assets/{glsl-o1djm5pp.js → glsl-lqormp2b.js} +1 -1
  352. package/static/dist/assets/{graphql-kj6rz55t.js → graphql-hw6s5qzd.js} +1 -1
  353. package/static/dist/assets/{hack-pe1qqr2i.js → hack-ozrv6l4d.js} +1 -1
  354. package/static/dist/assets/{haml-cknnbcqs.js → haml-gytfosdt.js} +1 -1
  355. package/static/dist/assets/{handlebars-hwq64orh.js → handlebars-b6g7jffd.js} +1 -1
  356. package/static/dist/assets/highlighted-body-KPVGNVTW-mmgz2czu.js +1 -0
  357. package/static/dist/assets/host-fr6lni1p.js +1 -0
  358. package/static/dist/assets/{html-derivative-h2i2pyjc.js → html-derivative-f1tqcroo.js} +1 -1
  359. package/static/dist/assets/{html-ha05xtvt.js → html-on6oj6yj.js} +1 -1
  360. package/static/dist/assets/{http-cf94zu27.js → http-odhufsl7.js} +1 -1
  361. package/static/dist/assets/{hurl-lslax568.js → hurl-mu680336.js} +1 -1
  362. package/static/dist/assets/index-73649w7o.js +10 -0
  363. package/static/dist/assets/index-lhf1fsdf.css +2 -0
  364. package/static/dist/assets/info-A6RAGUB7-lll1dqmm.js +1 -0
  365. package/static/dist/assets/infoDiagram-27XIBGKW-k7esc639.js +2 -0
  366. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-e7bnwq7h.js → ishikawaDiagram-5VMMS53U-lgr6k9wo.js} +6 -6
  367. package/static/dist/assets/{java-ndjf2o89.js → java-nxbe8uju.js} +1 -1
  368. package/static/dist/assets/{javascript-coftlwy3.js → javascript-lz3kxcyz.js} +1 -1
  369. package/static/dist/assets/{jinja-etei9wy7.js → jinja-l5pk84gt.js} +1 -1
  370. package/static/dist/assets/{jison-hyekgft1.js → jison-jdutdwyy.js} +1 -1
  371. package/static/dist/assets/{journeyDiagram-3NMN7TZE-etd1bi6p.js → journeyDiagram-3NMN7TZE-ndmbxuqk.js} +1 -1
  372. package/static/dist/assets/{json-lok0yaho.js → json-dnge4ssf.js} +1 -1
  373. package/static/dist/assets/{jsx-b64w0nbx.js → jsx-c3lwhkep.js} +1 -1
  374. package/static/dist/assets/{jsx-runtime-eu92a5pu.js → jsx-runtime-o40ukmjl.js} +1 -1
  375. package/static/dist/assets/{julia-jxxzz2yn.js → julia-hwd4rrcb.js} +1 -1
  376. package/static/dist/assets/{just-mc29iw9t.js → just-nka3oxdx.js} +1 -1
  377. package/static/dist/assets/{kanban-definition-UXKFOSKX-emb1g9dz.js → kanban-definition-UXKFOSKX-mjxym0jp.js} +28 -28
  378. package/static/dist/assets/katex-cbdd1ohw.css +1 -0
  379. package/static/dist/assets/katex-pbij2oo6.js +257 -0
  380. package/static/dist/assets/{latex-pafa03ft.js → latex-iqmr7e40.js} +1 -1
  381. package/static/dist/assets/lazyRouteComponent-cwb6l7xh.js +1 -0
  382. package/static/dist/assets/{line-h0ojzsvs.js → line-mtr5k6il.js} +1 -1
  383. package/static/dist/assets/linear-jlwrvvy6.js +1 -0
  384. package/static/dist/assets/link-iadw4ghw.js +2 -0
  385. package/static/dist/assets/{liquid-hz5c2pov.js → liquid-okmk9tbp.js} +1 -1
  386. package/static/dist/assets/locale-cs-n41hptse.js +5 -0
  387. package/static/dist/assets/locale-de-k7ht47ew.js +5 -0
  388. package/static/dist/assets/locale-es-ih078lco.js +5 -0
  389. package/static/dist/assets/locale-fr-ojabnf41.js +5 -0
  390. package/static/dist/assets/locale-it-kb96jufy.js +5 -0
  391. package/static/dist/assets/locale-ja-jpsp3xs1.js +7 -0
  392. package/static/dist/assets/locale-ko-cpzuy0n7.js +5 -0
  393. package/static/dist/assets/locale-pl-dbap4leu.js +5 -0
  394. package/static/dist/assets/locale-pt-nci7w72x.js +5 -0
  395. package/static/dist/assets/locale-ru-soghe4cs.js +7 -0
  396. package/static/dist/assets/locale-tr-ks00mu68.js +5 -0
  397. package/static/dist/assets/locale-vi-ij7twfm6.js +5 -0
  398. package/static/dist/assets/locale-zh-Hant-feakwr7a.js +5 -0
  399. package/static/dist/assets/locale-zh-ren0vjyv.js +7 -0
  400. package/static/dist/assets/login-lkhtzfcl.js +1 -0
  401. package/static/dist/assets/{lua-leyf8mlb.js → lua-krx4xj5h.js} +1 -1
  402. package/static/dist/assets/{marko-cn1fzpdv.js → marko-gbvpfehz.js} +1 -1
  403. package/static/dist/assets/{mdc-huhy88df.js → mdc-c21zlyi5.js} +1 -1
  404. package/static/dist/assets/mermaid-HWGCJPDP-jx0q5dm2.js +1 -0
  405. package/static/dist/assets/mermaid-parser.core-k91vdnvi.js +7 -0
  406. package/static/dist/assets/micromark-factory-space-fnmnpi6s.js +1 -0
  407. package/static/dist/assets/{mindmap-definition-YA3MSWOX-o1vathns.js → mindmap-definition-YA3MSWOX-ehztfea4.js} +28 -28
  408. package/static/dist/assets/{nginx-e7lvhsmn.js → nginx-f3d68ed4.js} +1 -1
  409. package/static/dist/assets/{nim-dun5d8mk.js → nim-dw0jcy70.js} +1 -1
  410. package/static/dist/assets/onboarding-f67z0a2r.js +1 -0
  411. package/static/dist/assets/packet-AYTQ26CC-ok2xrblz.js +1 -0
  412. package/static/dist/assets/pegDiagram-XKGWAZYB-ec3rpj0m.js +1 -0
  413. package/static/dist/assets/{perl-bj3oqtet.js → perl-3c0r45c8.js} +1 -1
  414. package/static/dist/assets/{php-buzp12r0.js → php-rznhidvp.js} +1 -1
  415. package/static/dist/assets/pie-WAS4IAKB-iwkir23u.js +1 -0
  416. package/static/dist/assets/pieDiagram-E7YTZNPT-fcpqpekh.js +39 -0
  417. package/static/dist/assets/{pug-mjad7he9.js → pug-f2le4f3y.js} +1 -1
  418. package/static/dist/assets/{qml-d1gdp10s.js → qml-e2wbq9cr.js} +1 -1
  419. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-owqc4uqn.js → quadrantDiagram-AXDQQJYC-lqsync43.js} +1 -1
  420. package/static/dist/assets/queryKeys-ed4n2egc.js +9 -0
  421. package/static/dist/assets/{r-o3r9c7ao.js → r-epfzuw6q.js} +1 -1
  422. package/static/dist/assets/radar-RG4KPBEZ-hje4jbk6.js +1 -0
  423. package/static/dist/assets/railroad-74A4TZTK-gll9n6qi.js +1 -0
  424. package/static/dist/assets/railroad-abnf-HS5TGJTU-o6h6lpn4.js +1 -0
  425. package/static/dist/assets/railroad-ebnf-LZEXJU2U-juaefj6o.js +1 -0
  426. package/static/dist/assets/railroad-peg-WCYAUIDC-cgfhvpah.js +1 -0
  427. package/static/dist/assets/railroadDiagram-O6MQD6OU-lp3w7awf.js +1 -0
  428. package/static/dist/assets/{razor-ilc585yr.js → razor-pea1u1d0.js} +1 -1
  429. package/static/dist/assets/refresh-cw-iccm7a4e.js +1 -0
  430. package/static/dist/assets/refresh-i9ya9dvl.js +1 -0
  431. package/static/dist/assets/{regexp-mpjtcvwm.js → regexp-hzte0a5x.js} +1 -1
  432. package/static/dist/assets/registry-g7w3353u.js +1 -0
  433. package/static/dist/assets/requirementDiagram-BXWQKSXE-ha8eibcf.js +84 -0
  434. package/static/dist/assets/rolldown-runtime-bnnw9q4a.js +1 -0
  435. package/static/dist/assets/{rst-jrkwjdyi.js → rst-m6tugj5o.js} +1 -1
  436. package/static/dist/assets/{ruby-n4tjcbpz.js → ruby-l19vq3w1.js} +1 -1
  437. package/static/dist/assets/runtime-nlxpnegr.js +1 -0
  438. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-iqc02emv.js → sankeyDiagram-P5KCCOFB-oxf31pr9.js} +1 -1
  439. package/static/dist/assets/{sas-hjo6y93k.js → sas-ktdxo397.js} +1 -1
  440. package/static/dist/assets/{scss-ncidjv48.js → scss-kn3oxfwf.js} +1 -1
  441. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-i0v116q0.js → sequenceDiagram-WJ2MYXX4-jiq8lhyo.js} +11 -11
  442. package/static/dist/assets/settings_section_extensions_title-fadc7o65.js +1 -0
  443. package/static/dist/assets/share._token-o76ki0ly.js +2 -0
  444. package/static/dist/assets/{shellscript-m8whjpqd.js → shellscript-l100jtrg.js} +1 -1
  445. package/static/dist/assets/{shellsession-i5bj6syz.js → shellsession-m5qukwow.js} +1 -1
  446. package/static/dist/assets/sizeCapture-INFHLROL-dk1io13w.js +1 -0
  447. package/static/dist/assets/sliders-horizontal-ddcs88av.js +1 -0
  448. package/static/dist/assets/{soy-hhh36ysq.js → soy-mexlscur.js} +1 -1
  449. package/static/dist/assets/{sql-j21bzcl6.js → sql-o0ho5suh.js} +1 -1
  450. package/static/dist/assets/src-h89s0hs4.js +1 -0
  451. package/static/dist/assets/{stata-bbu2onh1.js → stata-be9ck1q3.js} +1 -1
  452. package/static/dist/assets/stateDiagram-D77RDMKH-f3fslnon.js +1 -0
  453. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hlt6yphw.js +1 -0
  454. package/static/dist/assets/{surrealql-l5vczuco.js → surrealql-gk1xkkyy.js} +1 -1
  455. package/static/dist/assets/{svelte-idn422g6.js → svelte-k6ecjilf.js} +1 -1
  456. package/static/dist/assets/swimlanes-42K2YHIH-k1hmk7jf.js +1 -0
  457. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-hd5t9tnt.js +8 -0
  458. package/static/dist/assets/{templ-e22qou3c.js → templ-gf3bftk6.js} +1 -1
  459. package/static/dist/assets/{tex-deqpc5ee.js → tex-d21fqv1z.js} +1 -1
  460. package/static/dist/assets/{timeline-definition-24CTP7MA-ol2hnvma.js → timeline-definition-24CTP7MA-n3ncfy9r.js} +8 -8
  461. package/static/dist/assets/toast-liparz2j.js +56 -0
  462. package/static/dist/assets/treeView-Q6P3EWNA-l3y2pejt.js +1 -0
  463. package/static/dist/assets/treemap-WGGIJYW6-jown9axh.js +1 -0
  464. package/static/dist/assets/{ts-tags-ctnibc27.js → ts-tags-n0g61gr6.js} +1 -1
  465. package/static/dist/assets/{tsx-i163n32g.js → tsx-cvv7wv6q.js} +1 -1
  466. package/static/dist/assets/{twig-x0fi7wm8.js → twig-n1f67204.js} +1 -1
  467. package/static/dist/assets/{typescript-g18c34xz.js → typescript-og7o55cx.js} +1 -1
  468. package/static/dist/assets/untitled-mj86r4gz.js +1 -0
  469. package/static/dist/assets/useDebounced-g8r0ggve.js +1 -0
  470. package/static/dist/assets/useForm-o4uu74mx.js +1 -0
  471. package/static/dist/assets/{useParams-gjc70qru.js → useParams-ftsveu84.js} +1 -1
  472. package/static/dist/assets/{useSearch-e6ae539u.js → useSearch-crqueqga.js} +1 -1
  473. package/static/dist/assets/vennDiagram-4TSXK5OY-dh59uq1c.js +34 -0
  474. package/static/dist/assets/{vue-mwu5l1bb.js → vue-ej4xpwqw.js} +1 -1
  475. package/static/dist/assets/{vue-html-nskj3du0.js → vue-html-d48xsjuo.js} +1 -1
  476. package/static/dist/assets/{vue-vine-cqe77smq.js → vue-vine-pev88ip6.js} +1 -1
  477. package/static/dist/assets/wardley-WFR3VGLG-e6d74b0j.js +1 -0
  478. package/static/dist/assets/wardleyDiagram-VM6X3IG4-hnofh78f.js +78 -0
  479. package/static/dist/assets/with-selector-nu1b5z0v.js +1 -0
  480. package/static/dist/assets/{xml-gau7gvrv.js → xml-ifxp6gqb.js} +1 -1
  481. package/static/dist/assets/{xsl-ew09zq8b.js → xsl-fug2cckc.js} +1 -1
  482. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-aqbgvujg.js → xychartDiagram-S5SC5T6Z-bpymjelb.js} +6 -6
  483. package/static/dist/assets/{yaml-ofnrxtvf.js → yaml-fjmafnlo.js} +1 -1
  484. package/static/dist/extension-sdk.js +1 -1
  485. package/static/dist/index.html +1 -1
  486. package/static/dist/sw.js +180 -136
  487. package/static/dist/assets/AppShell-cey33v9s.js +0 -8
  488. package/static/dist/assets/ChatPage-i8wnugr7.js +0 -18
  489. package/static/dist/assets/ConnectTargets-hg19553x.js +0 -1
  490. package/static/dist/assets/ExtensionRoute-cly4apm5.js +0 -1
  491. package/static/dist/assets/Field-j8uqoigz.js +0 -9
  492. package/static/dist/assets/HubRoute-e4bgz41j.js +0 -2
  493. package/static/dist/assets/Markdown-lahuichm.js +0 -598
  494. package/static/dist/assets/Markdown-lwviulmg.css +0 -1
  495. package/static/dist/assets/RightPanel-lbo4i0fn.js +0 -1
  496. package/static/dist/assets/Select-gqatm2e0.js +0 -1
  497. package/static/dist/assets/SessionListPanel-mfqayfjh.js +0 -1
  498. package/static/dist/assets/SettingsLayout-b8980u7k.js +0 -1
  499. package/static/dist/assets/States-e7dkgtxf.js +0 -1
  500. package/static/dist/assets/Toaster-bfqqy786.js +0 -1
  501. package/static/dist/assets/_app-n1n6pfx1.js +0 -1
  502. package/static/dist/assets/_app.ext._extensionId-buxjxav3.js +0 -1
  503. package/static/dist/assets/_app.index-jt8zwiu2.js +0 -1
  504. package/static/dist/assets/_app.insights-cv76ml71.js +0 -1
  505. package/static/dist/assets/_app.kanban-h6ts53j7.js +0 -1
  506. package/static/dist/assets/_app.logs-dm873z90.js +0 -1
  507. package/static/dist/assets/_app.memory-glk5y6m8.js +0 -1
  508. package/static/dist/assets/_app.profiles-ka6zpstq.js +0 -1
  509. package/static/dist/assets/_app.session._sessionId-g2vzghvc.js +0 -1
  510. package/static/dist/assets/_app.settings-hgjdhzcd.js +0 -1
  511. package/static/dist/assets/_app.settings._section-jf2kmhae.js +0 -3
  512. package/static/dist/assets/_app.skills-cii852yq.js +0 -1
  513. package/static/dist/assets/_app.tasks-fp9q7atm.js +0 -1
  514. package/static/dist/assets/_app.todos-dw94qzue.js +0 -1
  515. package/static/dist/assets/_app.workspaces-inyygzze.js +0 -1
  516. package/static/dist/assets/abnfDiagram-VCTEODGH-gjayq3ra.js +0 -1
  517. package/static/dist/assets/architecture-7GRP2DOG-lp56972r.js +0 -1
  518. package/static/dist/assets/architectureDiagram-5GKGNRK7-nwb3xovn.js +0 -36
  519. package/static/dist/assets/archive-b1j004vn.js +0 -1
  520. package/static/dist/assets/back-g74gkozu.js +0 -1
  521. package/static/dist/assets/channel-es95o4y8.js +0 -1
  522. package/static/dist/assets/chunk-DU6HZSFF-gpedttsp.js +0 -128
  523. package/static/dist/assets/chunk-OSK3NFVY-oy0gwn2n.js +0 -10
  524. package/static/dist/assets/classDiagram-ZZMXUADV-kmihoqxh.js +0 -1
  525. package/static/dist/assets/classDiagram-v2-VYDZK3BY-kmihoqxh.js +0 -1
  526. package/static/dist/assets/copied-bli9tbcx.js +0 -1
  527. package/static/dist/assets/copy-jamtd1pf.js +0 -1
  528. package/static/dist/assets/cose-bilkent-JH36ORCC-eclcy6ja.js +0 -1
  529. package/static/dist/assets/cynefin-OW5HDTMX-f8xfdd3f.js +0 -1
  530. package/static/dist/assets/cynefinDiagram-5FMLGOSQ-n9lb99h9.js +0 -62
  531. package/static/dist/assets/cytoscape.esm-bqu53u12.js +0 -321
  532. package/static/dist/assets/dagre-GXQ25YYZ-dgailddi.js +0 -4
  533. package/static/dist/assets/dagre-lkoknbjn.js +0 -1
  534. package/static/dist/assets/diagram-UQ7AKVKN-jall3j0x.js +0 -41
  535. package/static/dist/assets/diagram-VX7I27RA-hendl2tt.js +0 -24
  536. package/static/dist/assets/diagram-Z3DM3KII-igwc35p3.js +0 -24
  537. package/static/dist/assets/ebnfDiagram-PWID7BFC-c64knz08.js +0 -1
  538. package/static/dist/assets/erDiagram-RLTQ6QDP-brdu9lcm.js +0 -99
  539. package/static/dist/assets/eventmodeling-NTZA5JFV-njk1j8wl.js +0 -1
  540. package/static/dist/assets/flowDiagram-HODETNUW-nma6f0ga.js +0 -1
  541. package/static/dist/assets/folder-plus-lvt9kudb.js +0 -1
  542. package/static/dist/assets/gitGraph-4MIJSDKK-naa9w6xa.js +0 -1
  543. package/static/dist/assets/highlighted-body-KPVGNVTW-ghoavj8d.js +0 -1
  544. package/static/dist/assets/host-hzm8n85n.js +0 -1
  545. package/static/dist/assets/index-drdcaqeq.css +0 -2
  546. package/static/dist/assets/index-jzm844he.js +0 -11
  547. package/static/dist/assets/info-A6RAGUB7-mn2ut5ko.js +0 -1
  548. package/static/dist/assets/infoDiagram-27XIBGKW-h6kuqvsb.js +0 -2
  549. package/static/dist/assets/lazyRouteComponent-o816ytzl.js +0 -1
  550. package/static/dist/assets/linear-becwlnhk.js +0 -1
  551. package/static/dist/assets/link-k01ryaf9.js +0 -1
  552. package/static/dist/assets/login-bsae49ew.js +0 -1
  553. package/static/dist/assets/mermaid-HWGCJPDP-ihl34s74.js +0 -1
  554. package/static/dist/assets/mermaid-parser.core-bnre27u8.js +0 -7
  555. package/static/dist/assets/not-found-ccnnac39.js +0 -1
  556. package/static/dist/assets/onboarding-c65tysya.js +0 -1
  557. package/static/dist/assets/onboarding_model_label-gpn28uix.js +0 -1
  558. package/static/dist/assets/packet-AYTQ26CC-hatq7m2e.js +0 -1
  559. package/static/dist/assets/pegDiagram-XKGWAZYB-gwisz8jv.js +0 -1
  560. package/static/dist/assets/pie-WAS4IAKB-fpftf0mk.js +0 -1
  561. package/static/dist/assets/pieDiagram-E7YTZNPT-khm1o9a6.js +0 -39
  562. package/static/dist/assets/radar-RG4KPBEZ-cntfwjpq.js +0 -1
  563. package/static/dist/assets/railroad-74A4TZTK-a7t5eibp.js +0 -1
  564. package/static/dist/assets/railroad-abnf-HS5TGJTU-ol8cdktf.js +0 -1
  565. package/static/dist/assets/railroad-ebnf-LZEXJU2U-i8vzd429.js +0 -1
  566. package/static/dist/assets/railroad-peg-WCYAUIDC-nw3ulewz.js +0 -1
  567. package/static/dist/assets/railroadDiagram-O6MQD6OU-etuh0qa0.js +0 -1
  568. package/static/dist/assets/refresh-cw-mqbkv2a3.js +0 -1
  569. package/static/dist/assets/refresh-l29ljp76.js +0 -1
  570. package/static/dist/assets/requirementDiagram-BXWQKSXE-oq2ulk22.js +0 -84
  571. package/static/dist/assets/rolldown-runtime-b9miacsj.js +0 -1
  572. package/static/dist/assets/root-mbklpuv4.js +0 -1
  573. package/static/dist/assets/saved-ndd4t2xb.js +0 -1
  574. package/static/dist/assets/settings_section_extensions_title-jhfo6o8z.js +0 -1
  575. package/static/dist/assets/share._token-c1ri0hy1.js +0 -2
  576. package/static/dist/assets/sizeCapture-INFHLROL-g612l48j.js +0 -1
  577. package/static/dist/assets/sliders-horizontal-j3jjt9tv.js +0 -1
  578. package/static/dist/assets/src-hjyib4mu.js +0 -1
  579. package/static/dist/assets/stateDiagram-D77RDMKH-dnpt1ato.js +0 -1
  580. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-bxjacpk6.js +0 -1
  581. package/static/dist/assets/swimlanes-42K2YHIH-loqbdazb.js +0 -1
  582. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-id62p9hz.js +0 -8
  583. package/static/dist/assets/toast-i3h34v6o.js +0 -64
  584. package/static/dist/assets/treeView-Q6P3EWNA-idrj6iox.js +0 -1
  585. package/static/dist/assets/treemap-WGGIJYW6-lzr49vv5.js +0 -1
  586. package/static/dist/assets/untitled-oaoya4qj.js +0 -1
  587. package/static/dist/assets/useForm-nfaxgmsk.js +0 -1
  588. package/static/dist/assets/vennDiagram-4TSXK5OY-bvwr9o2z.js +0 -34
  589. package/static/dist/assets/wardley-WFR3VGLG-31ui8tma.js +0 -1
  590. package/static/dist/assets/wardleyDiagram-VM6X3IG4-k5wqtx4b.js +0 -78
  591. /package/static/dist/assets/{ChatPage-gmu5c7lf.css → TerminalPanel-gmu5c7lf.css} +0 -0
@@ -19,6 +19,7 @@ import inspect
19
19
  import json
20
20
  import logging
21
21
  import os
22
+ import queue
22
23
  import re
23
24
  import threading
24
25
  import time
@@ -26,13 +27,15 @@ import uuid
26
27
  from collections import OrderedDict
27
28
  from functools import lru_cache
28
29
  from pathlib import Path
29
- from typing import Any
30
+ from typing import Any, Callable
31
+ from types import SimpleNamespace
30
32
 
31
33
  from ..errors import InvalidParams, RpcError
32
34
  from ..home import profile_home_param, scoped_home, session_workspace
33
35
  from ..rpc import CallContext
34
36
  from .checkpoints import checkpoint_kwargs
35
37
  from .providers import runtime_failure
38
+ from .state_db import release_all, release_home, release_session_db
36
39
  from .toolsets import disabled_toolsets
37
40
 
38
41
  log = logging.getLogger("talaria_sidecar.chat")
@@ -65,6 +68,7 @@ class _Run:
65
68
  # agent this run binds afterwards forks no review and stays out of the cache.
66
69
  self.session_ended = False
67
70
  self.home = None # the profile home its agent runs under, set by ``start``
71
+ self.mcp_discovery_outcome = "ok"
68
72
 
69
73
 
70
74
  class _ClarifyEntry:
@@ -86,13 +90,22 @@ _RUNS_LOCK = threading.Lock()
86
90
  # session id -> (agent, signature, profile home).
87
91
  _AGENT_CACHE: "OrderedDict[str, tuple[Any, str, Any]]" = OrderedDict()
88
92
  _AGENT_CACHE_LOCK = threading.Lock()
89
- # Running release thread -> the profile home it runs under.
90
- _RELEASES: dict[threading.Thread, Any] = {}
93
+ # Running or queued release work -> the profile home it holds.
94
+ _RELEASES: dict[threading.Thread | _ArchiveCallback, Any] = {}
91
95
  _RELEASES_LOCK = threading.Lock()
92
- # Agents inside ``chat.commit_memory`` or ``chat.refine`` (guarded by ``_AGENT_CACHE_LOCK``): like a turn, each defers their release.
96
+ _ARCHIVE_MAX_WORKERS = 4
97
+ _ARCHIVE_QUEUE: queue.Queue[_ArchiveCallback | None] = queue.Queue(maxsize=256)
98
+ _ARCHIVE_WORKERS: list[threading.Thread] = []
99
+ # Non-turn ``(agent, home)`` holders for memory commits, refine, context reads and MCP refresh (under
100
+ # ``_AGENT_CACHE_LOCK``): each defers the agent's release, and deleting ``home`` waits for it.
93
101
  _COMMITTING: list = []
94
102
  # Set (under ``_RUNS_LOCK``) when shutdown drains: no turn is admitted after the drain's snapshot of ``_RUNS``.
95
103
  _DRAINING = threading.Event()
104
+ _MCP_DISCOVERIES: dict[str, threading.Event] = {}
105
+ _MCP_DISCOVERY_LOCK = threading.Lock()
106
+ _MCP_CLOSING_HOMES: set[str] = set()
107
+ _MCP_REDISCOVERY_WAIT = 2.0
108
+ _MCP_PENDING_KEY = "mcp_discovery_pending"
96
109
 
97
110
 
98
111
  def _snippet(raw: Any, limit: int = _TOOL_RESULT_SNIPPET_MAX) -> str:
@@ -111,7 +124,17 @@ def _snippet(raw: Any, limit: int = _TOOL_RESULT_SNIPPET_MAX) -> str:
111
124
 
112
125
 
113
126
  _RAW_RESULT_MAX_KEYS = 64
114
- _RAW_RESULT_OUTCOME_KEYS = ("error", "exit_code", "exitCode", "success")
127
+ _RAW_RESULT_OUTCOME_KEYS = ("error", "exit_code", "exitCode", "success", "status", "results")
128
+ # TAL-1261: a ``results`` batch keeps up to 64 items, each projected like the result with 16 fields of 400 characters.
129
+ _RAW_RESULT_ITEM_KEYS = 16
130
+ _RAW_RESULT_ITEM_MAX = 400
131
+
132
+
133
+ def _without_error(item: Any) -> bool:
134
+ """An item the server's ``presentError`` reads as error-free: not an object, or its ``error`` is blank text, ``false``,
135
+ ``null``, a number, or an empty object or list."""
136
+ error = item.get("error") if isinstance(item, dict) else None
137
+ return not (error is True or (isinstance(error, str) and error.strip()) or (isinstance(error, (dict, list)) and error))
115
138
 
116
139
 
117
140
  def _result_diff(raw: Any) -> str | None:
@@ -149,10 +172,11 @@ def _edit_metadata(name: str, args: Any, result: Any, snapshot: Any) -> dict:
149
172
  return {"diff": diff} if diff else {}
150
173
 
151
174
 
152
- def _raw_result(raw: Any, limit: int = _TOOL_RESULT_SNIPPET_MAX) -> Any:
175
+ def _raw_result(raw: Any, limit: int = _TOOL_RESULT_SNIPPET_MAX, max_keys: int = _RAW_RESULT_MAX_KEYS, items: bool = True) -> Any:
153
176
  """The tool result as the server's outcome rule reads it, bounded: a dict (or JSON-object text) keeps its first
154
- ``_RAW_RESULT_MAX_KEYS`` top-level fields plus its outcome fields, scalars as they are, text and non-empty nested
155
- values as capped (JSON) text; anything else is the capped text. The sidecar decides nothing; the server does."""
177
+ ``max_keys`` top-level fields plus its outcome fields, scalars as they are, text and non-empty nested values as capped
178
+ (JSON) text, except a ``results`` list, whose object items keep the same shape one level down (TAL-1261); anything
179
+ else is the capped text. The sidecar decides nothing; the server does."""
156
180
  try:
157
181
  data = raw if isinstance(raw, dict) else json.loads(str(raw or ""))
158
182
  except Exception: # noqa: BLE001
@@ -161,9 +185,15 @@ def _raw_result(raw: Any, limit: int = _TOOL_RESULT_SNIPPET_MAX) -> Any:
161
185
  return str(raw if raw is not None else "")[:limit]
162
186
  out: dict = {}
163
187
  # The server's outcome rule reads these, wherever they sit in the result.
164
- kept = list(data.items())[:_RAW_RESULT_MAX_KEYS] + [(k, data[k]) for k in _RAW_RESULT_OUTCOME_KEYS if k in data]
188
+ kept = list(data.items())[:max_keys] + [(k, data[k]) for k in _RAW_RESULT_OUTCOME_KEYS if k in data]
165
189
  for key, value in kept:
166
- if value is None or isinstance(value, (bool, int)) or (isinstance(value, float) and value == value and abs(value) != float("inf")):
190
+ if items and key == "results" and isinstance(value, list) and value:
191
+ # Past the cap, the first item without an error rides along, so a long batch is judged like the whole one.
192
+ batch = value[:_RAW_RESULT_MAX_KEYS] + [item for item in value[_RAW_RESULT_MAX_KEYS:] if _without_error(item)][:1]
193
+ # A non-object item stays text, even JSON-object text: only object items count toward the batch's outcome.
194
+ out[key] = [_raw_result(item, _RAW_RESULT_ITEM_MAX, _RAW_RESULT_ITEM_KEYS, False) if isinstance(item, dict)
195
+ else (item if isinstance(item, str) else json.dumps(item, default=str))[:_RAW_RESULT_ITEM_MAX] for item in batch]
196
+ elif value is None or isinstance(value, (bool, int)) or (isinstance(value, float) and value == value and abs(value) != float("inf")):
167
197
  out[str(key)] = value
168
198
  elif isinstance(value, (dict, list)) and not value:
169
199
  out[str(key)] = {} if isinstance(value, dict) else []
@@ -726,56 +756,293 @@ def _turn_identity(session_id: str, workspace: str):
726
756
  _LEGACY_TOOLSET_ALIASES = {"hermes": ("hermes-cli", "hermes-api-server")}
727
757
 
728
758
 
729
- def _turn_toolsets(toolsets) -> list[str]:
759
+ def _turn_toolsets(toolsets, cfg: dict | None = None) -> list[str]:
730
760
  """The session's override as sent, else the profile's configured toolsets. ``None`` would hand the Agent every
731
761
  registered toolset."""
732
- return toolsets if isinstance(toolsets, list) else _profile_toolsets()
762
+ return toolsets if isinstance(toolsets, list) else _profile_toolsets(cfg)
733
763
 
734
764
 
735
- def _profile_toolsets() -> list[str]:
765
+ def _profile_toolsets(cfg: dict | None = None) -> list[str]:
736
766
  """The profile's ``platform_toolsets.cli`` through the Agent's own resolver (default-off toolsets stay off), like
737
767
  ``hermes chat``. Runs under the call's ``scoped_home``."""
738
768
  from hermes_cli.config import load_config
739
769
  from hermes_cli.tools_config import _get_platform_tools
740
770
 
741
- resolved = sorted(_get_platform_tools(load_config() or {}, "cli"))
771
+ resolved = sorted(_get_platform_tools((load_config() or {}) if cfg is None else cfg, "cli"))
742
772
  return list(dict.fromkeys(name for raw in resolved for name in _LEGACY_TOOLSET_ALIASES.get(raw, (raw,))))
743
773
 
744
774
 
745
- def _discover_mcp_tools(run: _Run) -> None:
746
- """Connect the profile's MCP servers before the turn's agent snapshots its tools, like the predecessor's turn start.
747
- The Agent keys connections by profile scope and skips live ones, so later turns are cheap. Runs under the call's
748
- ``scoped_home``; a failure leaves the turn without MCP tools rather than failing it. A Stop does not wait for a slow
749
- server: the turn moves on to its cancelled end while the connect finishes in the background."""
750
- from .commands import discover_profile_mcp_tools
775
+ def _discover_mcp_tools(run: _Run) -> threading.Event | None:
776
+ """Wait for the profile's first discovery; later turns give a shared background retry at most two seconds."""
777
+ from .commands import discover_profile_mcp_tools, profile_mcp_tools_discovered
751
778
 
752
- done = threading.Event()
779
+ profile_home = run.home
780
+ home = os.path.realpath(profile_home)
781
+ deadline = time.monotonic() + _MCP_REDISCOVERY_WAIT if profile_mcp_tools_discovered(home) else None
753
782
 
754
783
  def discover() -> None:
755
784
  try:
756
- discover_profile_mcp_tools()
785
+ discovered = discover_profile_mcp_tools()
786
+ refresh_profile_agents_mcp_tools(profile_home)
787
+ done.startup_outcome = "ok" if discovered else "noop"
757
788
  except Exception: # noqa: BLE001
789
+ done.startup_outcome = "error"
758
790
  log.warning("MCP discovery failed", exc_info=True)
759
791
  finally:
792
+ with _RELEASES_LOCK:
793
+ _RELEASES.pop(worker, None)
760
794
  done.set()
761
795
 
762
- # The copied context carries the turn's home override and secret scope into the worker.
763
- threading.Thread(target=contextvars.copy_context().run, args=(discover,), daemon=True, name=f"chat-mcp-{run.stream_id[:8]}").start()
764
- while not done.wait(0.25):
796
+ with _MCP_DISCOVERY_LOCK:
797
+ if _DRAINING.is_set():
798
+ raise RpcError("The sidecar is shutting down.", condition="sidecar_unavailable")
799
+ if home in _MCP_CLOSING_HOMES:
800
+ raise RpcError("The profile is being deleted.", condition="profile_unavailable")
801
+ done = _MCP_DISCOVERIES.get(home)
802
+ if done is None or done.is_set():
803
+ done = threading.Event()
804
+ done.startup_outcome = "unknown"
805
+ _MCP_DISCOVERIES[home] = done
806
+ # The copied context carries the turn's home override and secret scope into the worker.
807
+ worker = threading.Thread(target=contextvars.copy_context().run, args=(discover,), daemon=True, name=f"chat-mcp-{run.stream_id[:8]}")
808
+ worker.failed = False
809
+ # Existing profile/shutdown custody includes this worker after its originating turn has ended.
810
+ with _RELEASES_LOCK:
811
+ _RELEASES[worker] = profile_home
812
+ try:
813
+ worker.start()
814
+ except Exception:
815
+ with _RELEASES_LOCK:
816
+ _RELEASES.pop(worker, None)
817
+ done.set()
818
+ raise
819
+ while not done.is_set():
765
820
  if run.cancel.is_set() or run.ctx.cancelled:
821
+ run.mcp_discovery_outcome = "cancelled"
822
+ return None
823
+ remaining = None if deadline is None else deadline - time.monotonic()
824
+ if remaining is not None and remaining <= 0:
825
+ run.mcp_discovery_outcome = "timeout"
826
+ run.ctx.emit("notice", {"key": _MCP_PENDING_KEY, "level": "info", "kind": "sticky",
827
+ "text": "Some tools are still connecting. Using available tools for this turn.",
828
+ "lapses_with_agent": False})
829
+ return done
830
+ done.wait(0.25 if remaining is None else min(0.25, remaining))
831
+ run.mcp_discovery_outcome = getattr(done, "startup_outcome", "ok")
832
+ return None
833
+
834
+
835
+ # Serializes every hook change the sidecar makes, so two turns or a turn and a profile deletion never interleave a
836
+ # drop with a registration.
837
+ _HOOKS_LOCK = threading.Lock()
838
+ _HOOK_CALLBACK_PREFIXES = {"agent.shell_hooks": "shell_hook[", "agent.outbound_webhooks": "outbound_webhook["}
839
+
840
+
841
+ def _drop_home_callbacks(*modules) -> None:
842
+ """Forget this home's registrations in each Agent hook module and remove their callbacks from the home's plugin
843
+ manager; the Agent has no teardown for either registry. Runs under the call's ``scoped_home`` and ``_HOOKS_LOCK``."""
844
+ from agent import shell_hooks
845
+ from hermes_cli.plugins import get_plugin_manager
846
+
847
+ prefixes = tuple(_HOOK_CALLBACK_PREFIXES[module.__name__] for module in modules)
848
+ for module in modules:
849
+ shell_hooks._forget_home_registrations(module._registered, module._registered_lock)
850
+ manager = get_plugin_manager()
851
+ with shell_hooks._registered_lock:
852
+ for event, callbacks in list(manager._hooks.items()):
853
+ # Replaced, never edited in place: a concurrent invoke_hook keeps iterating the old list.
854
+ manager._hooks[event] = [cb for cb in callbacks if not getattr(cb, "__name__", "").startswith(prefixes)]
855
+
856
+
857
+ def _hook_state_trusted(shell_hooks, cfg: dict) -> bool:
858
+ """``sync_from_config``'s removal guard for Agents without it: an empty (failed-open) config, an unreadable
859
+ allowlist or a ``hooks:`` entry the Agent could not parse must never disarm a registered guard."""
860
+ if not cfg:
861
+ return False
862
+ try:
863
+ allowlist = json.loads(shell_hooks.allowlist_path().read_text(encoding="utf-8-sig"))
864
+ except FileNotFoundError:
865
+ allowlist = {"approvals": []}
866
+ except (OSError, ValueError):
867
+ return False
868
+ if not isinstance(allowlist, dict) or not isinstance(allowlist.get("approvals"), list):
869
+ return False
870
+ hooks = cfg.get("hooks")
871
+ if hooks is None:
872
+ return True
873
+ if not isinstance(hooks, dict):
874
+ return False
875
+ events = [entries for event, entries in hooks.items() if event not in ("output_spill", "outbound") and entries is not None]
876
+ if not all(isinstance(entries, list) for entries in events):
877
+ return False
878
+ return len(shell_hooks.iter_configured_hooks(cfg)) == sum(len(entries) for entries in events)
879
+
880
+
881
+ def _replace_shell_hooks(shell_hooks, cfg: dict) -> None:
882
+ """Agents without ``sync_from_config``: wire the home's consented hooks from ``cfg`` and drop the rest, so a hook
883
+ removed from config or revoked stops, as it does on v0.22.3. The stale callbacks go only after the current set is
884
+ wired, so a concurrent turn's tool call always meets its guard. Add-only when the hook state is not trusted."""
885
+ if not _hook_state_trusted(shell_hooks, cfg):
886
+ shell_hooks.register_from_config(cfg, accept_hooks=False)
887
+ return
888
+ from hermes_cli.plugins import get_plugin_manager
889
+
890
+ manager = get_plugin_manager()
891
+ stale = [cb for callbacks in manager._hooks.values() for cb in callbacks if getattr(cb, "__name__", "").startswith("shell_hook[")]
892
+ shell_hooks._forget_home_registrations(shell_hooks._registered, shell_hooks._registered_lock)
893
+ shell_hooks.register_from_config(cfg, accept_hooks=False)
894
+ with shell_hooks._registered_lock:
895
+ for event, callbacks in list(manager._hooks.items()):
896
+ manager._hooks[event] = [cb for cb in callbacks if not any(cb is old for old in stale)]
897
+
898
+
899
+ def _register_profile_hooks(home: Path) -> None:
900
+ """Wire the profile's consented shell hooks and current outbound webhooks before the turn's tools run, like the
901
+ gateway on every session open (TAL-1258). Hooks added or approved since the last call join this one; hooks removed
902
+ from config or revoked stop, through ``sync_from_config`` or its sidecar twin for older Agents. Outbound webhooks
903
+ are add-only on every Agent, so each call replaces the home's set with the configured one (a concurrent turn's
904
+ notification can miss that instant; webhooks only observe). Consent comes from the allowlist or an opt-in in
905
+ force: stdin is the RPC pipe, so nothing prompts. A failure is logged and never blocks the turn."""
906
+ try:
907
+ from agent import outbound_webhooks, shell_hooks
908
+ from hermes_cli.config import load_config
909
+
910
+ with _HOOKS_LOCK:
911
+ sync = getattr(shell_hooks, "sync_from_config", None)
912
+ if sync is not None:
913
+ sync(load_config)
914
+ else:
915
+ _replace_shell_hooks(shell_hooks, load_config())
916
+ _drop_home_callbacks(outbound_webhooks)
917
+ outbound_webhooks.register_from_config(load_config())
918
+ except Exception: # noqa: BLE001
919
+ log.warning("shell-hook sync failed; keeping the registered hooks", exc_info=True)
920
+
921
+
922
+ def forget_profile_hooks(home: Path) -> None:
923
+ """Profile deletion: drop the home's shell hooks and outbound webhooks. The Agent keys both registries and its
924
+ plugin manager by path, so a profile recreated at that path would otherwise fire hooks its new config and allowlist
925
+ never approved. Raises rather than let the profile go with its hooks still wired."""
926
+ try:
927
+ from agent import outbound_webhooks, shell_hooks
928
+ except ImportError: # no Agent, so no hook was ever registered
929
+ return
930
+ with _HOOKS_LOCK, scoped_home(home):
931
+ _drop_home_callbacks(shell_hooks, outbound_webhooks)
932
+
933
+
934
+ @contextlib.contextmanager
935
+ def agent_scope(home: Path):
936
+ """``scoped_home`` for a call that runs Agent work: the profile's hooks are wired before any of it (TAL-1258), so
937
+ the call's first LLM request, tool call or lifecycle event already sees them, even as a session's first work after
938
+ a sidecar restart."""
939
+ with scoped_home(home):
940
+ _register_profile_hooks(home)
941
+ yield
942
+
943
+
944
+ class _ArchiveCallback:
945
+ """Queued work shares the release threads' join/liveness contract without owning an OS thread."""
946
+ name = "session-archive"
947
+ failed = False
948
+
949
+ def __init__(self, callback: Callable[[], None]):
950
+ self.callback = callback
951
+ self.done = threading.Event()
952
+
953
+ def run(self) -> None:
954
+ try:
955
+ self.callback()
956
+ finally:
957
+ self.done.set()
958
+
959
+ def join(self, timeout=None) -> None:
960
+ self.done.wait(timeout)
961
+
962
+ def is_alive(self) -> bool:
963
+ return not self.done.is_set()
964
+
965
+
966
+ def _archive_worker(pending) -> None:
967
+ while True:
968
+ work = pending.get()
969
+ try:
970
+ if work is None:
971
+ return
972
+ try:
973
+ work.run()
974
+ except BaseException: # a plugin must not kill a shared worker
975
+ log.warning("Session archive notification worker failed")
976
+ finally:
977
+ pending.task_done()
978
+ del work
979
+
980
+
981
+ def _schedule_archive(work: _ArchiveCallback, home: Path) -> None:
982
+ # Admission shares the profile-deletion/shutdown fence; queued work uses the existing release custody roster.
983
+ with _MCP_DISCOVERY_LOCK:
984
+ if _DRAINING.is_set() or os.path.realpath(home) in _MCP_CLOSING_HOMES:
985
+ log.warning("Session archive notification unavailable during profile or sidecar shutdown")
766
986
  return
987
+ if len(_ARCHIVE_WORKERS) < _ARCHIVE_MAX_WORKERS:
988
+ thread = threading.Thread(target=_archive_worker, args=(_ARCHIVE_QUEUE,), name="session-archive-worker", daemon=True)
989
+ try:
990
+ thread.start()
991
+ except RuntimeError:
992
+ log.warning("Session archive notification worker could not start")
993
+ if not _ARCHIVE_WORKERS:
994
+ return
995
+ else:
996
+ _ARCHIVE_WORKERS.append(thread)
997
+ with _RELEASES_LOCK:
998
+ _RELEASES[work] = home
999
+ try:
1000
+ _ARCHIVE_QUEUE.put_nowait(work)
1001
+ except queue.Full:
1002
+ _RELEASES.pop(work, None)
1003
+ log.warning("Session archive notification queue is full")
767
1004
 
768
1005
 
769
- def _profile_fallback_chain() -> list[dict] | None:
1006
+ def _notify_archive(home: Path, session_id: str, lineage: list[str] | None) -> None:
1007
+ import hermes_state_common
1008
+
1009
+ emit = getattr(hermes_state_common, "emit_session_deleted", None)
1010
+ if emit is None:
1011
+ return
1012
+ # Upstream hook warnings can include arbitrary callback exceptions. Keep one payload-free diagnostic.
1013
+ thread_id, warned = threading.get_ident(), False
1014
+
1015
+ def bounded_diagnostic(record):
1016
+ nonlocal warned
1017
+ if record.thread != thread_id:
1018
+ return True
1019
+ warned = warned or record.levelno >= logging.WARNING
1020
+ return False
1021
+
1022
+ handlers = list(logging.getLogger().handlers)
1023
+ for handler in handlers:
1024
+ handler.addFilter(bounded_diagnostic)
1025
+ try:
1026
+ # The emitter only needs db_path for profile identity. Opening SessionDB would migrate an owner's store.
1027
+ emit(SimpleNamespace(db_path=home / "state.db"), [session_id, *(lineage or [])],
1028
+ reason="archive", primary=session_id, sessions_dir=home / "sessions")
1029
+ finally:
1030
+ for handler in handlers:
1031
+ handler.removeFilter(bounded_diagnostic)
1032
+ if warned:
1033
+ log.warning("Session archive notification reported a callback failure")
1034
+
1035
+
1036
+ def _profile_fallback_chain(cfg: dict | None = None) -> list[dict] | None:
770
1037
  """The profile's fallback routes (``fallback_providers`` first, then legacy ``fallback_model``, deduplicated)
771
1038
  through the Agent's own resolver, like the CLI and gateway. Runs under the call's ``scoped_home``."""
772
1039
  from hermes_cli.config import load_config
773
1040
  from hermes_cli.fallback_config import get_fallback_chain
774
1041
 
775
- return get_fallback_chain(load_config() or {}) or None
1042
+ return copy.deepcopy(get_fallback_chain((load_config() or {}) if cfg is None else cfg)) or None
776
1043
 
777
1044
 
778
- def _profile_prefill_messages() -> list[dict] | None:
1045
+ def _profile_prefill_messages(cfg: dict | None = None) -> list[dict] | None:
779
1046
  """The profile's few-shot prefill (``HERMES_PREFILL_MESSAGES_FILE``, then ``prefill_messages_file``, then legacy
780
1047
  ``agent.prefill_messages_file``; relative to the profile) through the Agent's own loader, like the CLI, gateway and
781
1048
  Desktop. A missing or empty file is None. Runs under the call's ``scoped_home``."""
@@ -783,15 +1050,15 @@ def _profile_prefill_messages() -> list[dict] | None:
783
1050
  from hermes_cli.config import load_config
784
1051
  from hermes_constants import get_hermes_home
785
1052
 
786
- return _load_prefill_messages(_resolve_prefill_messages_file(load_config() or {}), get_hermes_home()) or None
1053
+ return _load_prefill_messages(_resolve_prefill_messages_file((load_config() or {}) if cfg is None else cfg), get_hermes_home()) or None
787
1054
 
788
1055
 
789
- def _main_model_request_overrides(model: str, provider, base_url: str = "") -> dict | None:
1056
+ def _main_model_request_overrides(model: str, provider, base_url: str = "", cfg: dict | None = None) -> dict | None:
790
1057
  """Predecessor ``_main_model_request_overrides``: the profile's ``model.service_tier`` and ``model.extra_body``
791
1058
  (written by ``/api/model/set``) apply only to turns on that configured main model. Runs under ``scoped_home``."""
792
1059
  from hermes_cli.config import load_config
793
1060
 
794
- model_cfg = (load_config() or {}).get("model")
1061
+ model_cfg = ((load_config() or {}) if cfg is None else cfg).get("model")
795
1062
  if not isinstance(model_cfg, dict):
796
1063
  return None
797
1064
  main_model = str(model_cfg.get("default") or model_cfg.get("name") or "").strip()
@@ -808,15 +1075,15 @@ def _main_model_request_overrides(model: str, provider, base_url: str = "") -> d
808
1075
  overrides = fast_overrides(model, str(provider or ""), base_url, str(model_cfg.get("service_tier") or "").strip().lower()) or {}
809
1076
  extra_body = model_cfg.get("extra_body")
810
1077
  if isinstance(extra_body, dict) and extra_body:
811
- overrides["extra_body"] = extra_body
1078
+ overrides["extra_body"] = copy.deepcopy(extra_body)
812
1079
  return overrides or None
813
1080
 
814
1081
 
815
- def _request_overrides(model: str, provider, runtime: dict) -> dict | None:
1082
+ def _request_overrides(model: str, provider, runtime: dict, cfg: dict | None = None) -> dict | None:
816
1083
  """The resolver's ``request_overrides`` (a custom provider entry's ``extra_body``) under the main-model ones, whose
817
1084
  ``extra_body`` wins key by key, the Agent's own merge order. Runs under ``scoped_home``."""
818
1085
  merged = dict(runtime.get("request_overrides") or {})
819
- main = _main_model_request_overrides(model, provider, str(runtime.get("base_url") or "")) or {}
1086
+ main = _main_model_request_overrides(model, provider, str(runtime.get("base_url") or ""), cfg) or {}
820
1087
  extra_body = {**(merged.get("extra_body") or {}), **(main.get("extra_body") or {})}
821
1088
  merged.update(main)
822
1089
  if extra_body:
@@ -824,6 +1091,31 @@ def _request_overrides(model: str, provider, runtime: dict) -> dict | None:
824
1091
  return merged or None
825
1092
 
826
1093
 
1094
+ def _provider_routing_kwargs(cfg: dict | None = None) -> dict:
1095
+ """The profile's OpenRouter ``provider_routing`` and ``openrouter.min_coding_score`` as ``AIAgent`` kwargs, with the
1096
+ Agent's own mapping (``gateway/session_worker_construct.routing_agent_kwargs``). The score is coerced like the CLI's
1097
+ (``cli_init_mixin``), so a quoted number counts and a value outside 0..1 stays unset. Runs under ``scoped_home``."""
1098
+ from hermes_cli.config import load_config
1099
+
1100
+ if cfg is None:
1101
+ cfg = load_config() or {}
1102
+ routing = cfg.get("provider_routing")
1103
+ routing = routing if isinstance(routing, dict) else {}
1104
+ openrouter = cfg.get("openrouter")
1105
+ raw_score = openrouter.get("min_coding_score") if isinstance(openrouter, dict) else None
1106
+ try:
1107
+ score = float(raw_score) if raw_score not in (None, "") else None
1108
+ except (TypeError, ValueError):
1109
+ score = None
1110
+ return copy.deepcopy({
1111
+ "providers_allowed": routing.get("only"), "providers_ignored": routing.get("ignore"),
1112
+ "providers_order": routing.get("order"), "provider_sort": routing.get("sort"),
1113
+ "provider_require_parameters": routing.get("require_parameters", False),
1114
+ "provider_data_collection": routing.get("data_collection"),
1115
+ "openrouter_min_coding_score": score if score is not None and 0.0 <= score <= 1.0 else None,
1116
+ })
1117
+
1118
+
827
1119
  def _agent_signature(model: str, provider, runtime: dict, toolsets, home: str, kwargs: dict) -> str:
828
1120
  """Cache identity of an ``AIAgent``: everything its constructor bound from the resolved runtime, so a rotated key,
829
1121
  a different API mode, ACP command, or credential pool never reuses an agent built for the old bundle. The key
@@ -841,6 +1133,9 @@ def _agent_signature(model: str, provider, runtime: dict, toolsets, home: str, k
841
1133
  # Bound at construction too: a reasoning-effort change from the composer must build a fresh agent.
842
1134
  "reasoning_config": kwargs.get("reasoning_config"),
843
1135
  "request_overrides": kwargs.get("request_overrides"),
1136
+ "provider_routing": [kwargs.get(name) for name in (
1137
+ "providers_allowed", "providers_ignored", "providers_order", "provider_sort", "provider_require_parameters",
1138
+ "provider_data_collection", "openrouter_min_coding_score")],
844
1139
  "checkpoints": [kwargs.get(name) for name in ("checkpoints_enabled", "checkpoint_max_snapshots", "checkpoint_max_total_size_mb", "checkpoint_max_file_size_mb")],
845
1140
  # Entries may carry their own ``api_key``.
846
1141
  "fallback_model": hashlib.sha256(json.dumps(kwargs.get("fallback_model"), sort_keys=True, default=str).encode("utf-8")).hexdigest()[:16],
@@ -850,7 +1145,7 @@ def _agent_signature(model: str, provider, runtime: dict, toolsets, home: str, k
850
1145
  return json.dumps(bundle, sort_keys=True, default=str)
851
1146
 
852
1147
 
853
- def _agent_kwargs(AIAgent, params: dict, session_id: str, model: str, provider, runtime: dict, toolsets) -> dict:
1148
+ def _agent_kwargs(AIAgent, params: dict, session_id: str, model: str, provider, runtime: dict, toolsets, cfg: dict | None = None) -> dict:
854
1149
  """The ``AIAgent`` arguments of a session's agent apart from a turn's callbacks: the resolved runtime, the session's
855
1150
  toolsets and budgets, and the profile's fallback chain, request overrides, prefill and checkpoints. Everything
856
1151
  ``_agent_signature`` reads is here, so an agent ``/refine`` builds (TAL-902) is the one the next turn reuses. Runs under
@@ -863,7 +1158,7 @@ def _agent_kwargs(AIAgent, params: dict, session_id: str, model: str, provider,
863
1158
  platform="webui",
864
1159
  quiet_mode=True,
865
1160
  enabled_toolsets=toolsets,
866
- disabled_toolsets=disabled_toolsets(),
1161
+ disabled_toolsets=disabled_toolsets(cfg),
867
1162
  session_id=session_id,
868
1163
  )
869
1164
  for name, value in (
@@ -886,17 +1181,21 @@ def _agent_kwargs(AIAgent, params: dict, session_id: str, model: str, provider,
886
1181
  reasoning_config = params.get("reasoning_config")
887
1182
  if isinstance(reasoning_config, dict) and _supported(AIAgent, "reasoning_config"):
888
1183
  kwargs["reasoning_config"] = reasoning_config
889
- fallback_chain = _profile_fallback_chain()
1184
+ fallback_chain = _profile_fallback_chain(cfg)
890
1185
  if fallback_chain and _supported(AIAgent, "fallback_model"):
891
1186
  kwargs["fallback_model"] = fallback_chain
892
- request_overrides = _request_overrides(model, provider, runtime)
1187
+ request_overrides = _request_overrides(model, provider, runtime, cfg)
893
1188
  if request_overrides and _supported(AIAgent, "request_overrides"):
894
1189
  kwargs["request_overrides"] = request_overrides
895
- prefill_messages = _profile_prefill_messages()
1190
+ prefill_messages = _profile_prefill_messages(cfg)
896
1191
  if prefill_messages and _supported(AIAgent, "prefill_messages"):
897
1192
  kwargs["prefill_messages"] = prefill_messages
1193
+ # TAL-976: the profile's OpenRouter provider routing, as the Agent's own CLI and gateway turns read it.
1194
+ for name, value in _provider_routing_kwargs(cfg).items():
1195
+ if _supported(AIAgent, name):
1196
+ kwargs[name] = value
898
1197
  # TAL-913: the profile's ``checkpoints`` section, as the Agent's own CLI and gateway turns read it.
899
- for name, value in checkpoint_kwargs().items():
1198
+ for name, value in checkpoint_kwargs(cfg).items():
900
1199
  if _supported(AIAgent, name):
901
1200
  kwargs[name] = value
902
1201
  return kwargs
@@ -911,7 +1210,7 @@ def _build_agent(AIAgent, kwargs: dict, home, session_id: str, state_db: bool =
911
1210
  try:
912
1211
  agent = AIAgent(**kwargs)
913
1212
  except BaseException:
914
- _release_session_db(session_db)
1213
+ release_session_db(session_db)
915
1214
  raise
916
1215
  agent._talaria_session_db = session_db # released with the agent (``_release_agent``)
917
1216
  return agent
@@ -963,7 +1262,7 @@ def _release_agent(agent, home) -> threading.Thread:
963
1262
  if not released:
964
1263
  # The memory flush needs the profile; the LLM clients and the Codex child close regardless.
965
1264
  call("release_clients")
966
- _release_session_db(getattr(agent, "_talaria_session_db", None))
1265
+ release_session_db(getattr(agent, "_talaria_session_db", None))
967
1266
  with _RELEASES_LOCK:
968
1267
  _RELEASES.pop(thread, None)
969
1268
 
@@ -979,7 +1278,7 @@ def _release_agents_locked(entries) -> list[threading.Thread]:
979
1278
  """Release cache entries that just left the cache (caller holds ``_AGENT_CACHE_LOCK``). An agent a turn still holds
980
1279
  keeps its memory provider and clients; that turn releases it when it ends."""
981
1280
  with _RUNS_LOCK:
982
- held = [run.agent for run in _RUNS.values()] + _COMMITTING
1281
+ held = [run.agent for run in _RUNS.values()] + [agent for agent, _home in _COMMITTING]
983
1282
  return [_release_agent(agent, home) for agent, _signature, home in entries if not any(agent is other for other in held)]
984
1283
 
985
1284
 
@@ -1075,7 +1374,7 @@ def drain_agents(timeout: float = _AGENT_RELEASE_TIMEOUT) -> None:
1075
1374
  """Shutdown: release every cached agent, stop running turns (like a Stop) so each releases the agent it holds, and
1076
1375
  wait, bounded, for those turns and every release still running."""
1077
1376
  deadline = time.monotonic() + timeout
1078
- with _AGENT_CACHE_LOCK, _RUNS_LOCK:
1377
+ with _MCP_DISCOVERY_LOCK, _AGENT_CACHE_LOCK, _RUNS_LOCK:
1079
1378
  # Admission closes with the snapshot, so no later turn forks a review outside it.
1080
1379
  _DRAINING.set()
1081
1380
  runs = list(_RUNS.values())
@@ -1096,6 +1395,8 @@ def drain_agents(timeout: float = _AGENT_RELEASE_TIMEOUT) -> None:
1096
1395
  with _RELEASES_LOCK:
1097
1396
  threads = list(_RELEASES)
1098
1397
  _join(threads, deadline)
1398
+ # TAL-1191: the state.db handles state_db writes keep between turns; each final release checkpoints once.
1399
+ release_all()
1099
1400
 
1100
1401
 
1101
1402
  def refresh_profile_agents_mcp_tools(home) -> None:
@@ -1108,34 +1409,69 @@ def refresh_profile_agents_mcp_tools(home) -> None:
1108
1409
 
1109
1410
  target = os.path.realpath(home)
1110
1411
  with _AGENT_CACHE_LOCK:
1111
- failed = []
1112
- for session_id, entry in list(_AGENT_CACHE.items()):
1113
- if os.path.realpath(entry[2]) != target:
1114
- continue
1412
+ entries = [(sid, entry) for sid, entry in _AGENT_CACHE.items() if os.path.realpath(entry[2]) == target]
1413
+ _COMMITTING.extend((entry[0], entry[2]) for _sid, entry in entries)
1414
+ try:
1415
+ for session_id, (agent, _signature, _home) in entries:
1115
1416
  try:
1116
- refresh_agent_mcp_tools(entry[0], quiet_mode=True, content_aware=True)
1417
+ refresh_agent_mcp_tools(agent, quiet_mode=True, content_aware=True)
1117
1418
  except Exception: # noqa: BLE001
1118
- log.warning("Failed to refresh MCP tools for session %s; evicting its agent", session_id, exc_info=True)
1119
- failed.append(_AGENT_CACHE.pop(session_id))
1120
- _release_agents_locked(failed)
1419
+ log.warning("Failed to refresh MCP tools for session %s", session_id, exc_info=True)
1420
+ with _AGENT_CACHE_LOCK:
1421
+ cached = _AGENT_CACHE.get(session_id)
1422
+ # A turn may have replaced or evicted this agent while its refresh ran.
1423
+ if cached is not None and cached[0] is agent:
1424
+ _AGENT_CACHE.pop(session_id)
1425
+ finally:
1426
+ with _AGENT_CACHE_LOCK:
1427
+ for _sid, (agent, _signature, owner) in entries:
1428
+ _COMMITTING.remove((agent, owner))
1429
+ _release_if_dropped_locked(agent, owner)
1430
+
1121
1431
 
1432
+ @contextlib.contextmanager
1433
+ def release_profile_agents(home):
1434
+ """Fence discovery and drain this profile's agents and background work before the caller removes its home."""
1435
+ from .commands import forget_profile_mcp_tools
1122
1436
 
1123
- def release_profile_agents(home) -> None:
1124
- """Profile deletion: release the profile's cached agents, cancel their background reviews, and wait for both and
1125
- every release running under its home. Fails rather than let the home go while a memory flush still runs under it."""
1126
1437
  target = os.path.realpath(home)
1438
+ with _MCP_DISCOVERY_LOCK:
1439
+ if target in _MCP_CLOSING_HOMES:
1440
+ raise RpcError("The profile is already being deleted.", condition="profile_unavailable")
1441
+ _MCP_CLOSING_HOMES.add(target)
1442
+ try:
1443
+ with _AGENT_CACHE_LOCK:
1444
+ entries = [_AGENT_CACHE.pop(sid) for sid, entry in list(_AGENT_CACHE.items()) if os.path.realpath(entry[2]) == target]
1445
+ started = _release_agents_locked(entries)
1446
+ started += _cancel_background_reviews([(entry[0], entry[2]) for entry in entries], "profile deleted")
1447
+ deadline = time.monotonic() + _PROFILE_RELEASE_TIMEOUT
1448
+ # Non-turn holders keep their agent past the cache; its release starts when the last holder lets go.
1449
+ while (holding := _holding_home(target)) and time.monotonic() < deadline:
1450
+ time.sleep(0.05)
1451
+ # The threads started here count even once they left ``_RELEASES``: a fast failure must still block the delete.
1452
+ with _RELEASES_LOCK:
1453
+ threads = list(dict.fromkeys(started + [thread for thread, owner in _RELEASES.items() if owner is not None and os.path.realpath(owner) == target]))
1454
+ _join(threads, deadline)
1455
+ if any(thread.is_alive() and thread.name.startswith("chat-mcp-") for thread in threads):
1456
+ raise RpcError("The profile is still connecting MCP tools. Retry in a moment.")
1457
+ if holding or any(thread.is_alive() for thread in threads):
1458
+ raise RpcError("The profile is still saving memory from its chats. Retry in a moment.")
1459
+ if any(thread.failed for thread in threads):
1460
+ raise RpcError("Memory from the profile's chats could not be saved; see the sidecar log. Retry to delete the profile anyway.")
1461
+ release_home(target)
1462
+ yield
1463
+ # The old generation has no worker left that could restore its readiness after deletion.
1464
+ forget_profile_mcp_tools(target)
1465
+ with _MCP_DISCOVERY_LOCK:
1466
+ _MCP_DISCOVERIES.pop(target, None)
1467
+ finally:
1468
+ with _MCP_DISCOVERY_LOCK:
1469
+ _MCP_CLOSING_HOMES.discard(target)
1470
+
1471
+
1472
+ def _holding_home(target: str) -> bool:
1127
1473
  with _AGENT_CACHE_LOCK:
1128
- entries = [_AGENT_CACHE.pop(sid) for sid, entry in list(_AGENT_CACHE.items()) if os.path.realpath(entry[2]) == target]
1129
- started = _release_agents_locked(entries)
1130
- started += _cancel_background_reviews([(entry[0], entry[2]) for entry in entries], "profile deleted")
1131
- # The threads started here count even once they left ``_RELEASES``: a fast failure must still block the delete.
1132
- with _RELEASES_LOCK:
1133
- threads = list(dict.fromkeys(started + [thread for thread, owner in _RELEASES.items() if owner is not None and os.path.realpath(owner) == target]))
1134
- _join(threads, time.monotonic() + _PROFILE_RELEASE_TIMEOUT)
1135
- if any(thread.is_alive() for thread in threads):
1136
- raise RpcError("The profile is still saving memory from its chats. Retry in a moment.")
1137
- if any(thread.failed for thread in threads):
1138
- raise RpcError("Memory from the profile's chats could not be saved; see the sidecar log. Retry to delete the profile anyway.")
1474
+ return any(os.path.realpath(home) == target for _agent, home in _COMMITTING)
1139
1475
 
1140
1476
 
1141
1477
  def _acquire_session_db(home):
@@ -1151,17 +1487,6 @@ def _acquire_session_db(home):
1151
1487
  return None
1152
1488
 
1153
1489
 
1154
- def _release_session_db(db) -> None:
1155
- if db is None:
1156
- return
1157
- try:
1158
- from hermes_state_registry import release_or_close
1159
-
1160
- release_or_close(db)
1161
- except Exception: # noqa: BLE001
1162
- log.warning("releasing an agent's state.db handle failed", exc_info=True)
1163
-
1164
-
1165
1490
  def _execution_session_id(db, session_id: str) -> str:
1166
1491
  """Predecessor ``_resolve_agent_execution_session_id``: a new agent for a session the Agent rotated by compression
1167
1492
  (the old agent went with a sidecar restart or a runtime change) runs on the live tip, with the server's history,
@@ -1187,6 +1512,20 @@ def _agent_class():
1187
1512
  return AIAgent
1188
1513
 
1189
1514
 
1515
+ def _require_data_use_support(history: list) -> frozenset[str]:
1516
+ """A downgraded Agent must never send history whose connector privacy requirements it cannot enforce."""
1517
+ if not any(isinstance(row, dict) and row.get("data_use") for row in history):
1518
+ return frozenset()
1519
+ try:
1520
+ from agent.message_metadata import DATA_USE, data_use_requirement
1521
+ except ImportError:
1522
+ pass
1523
+ else:
1524
+ if DATA_USE == "data_use" and callable(data_use_requirement):
1525
+ return data_use_requirement(history)
1526
+ raise RpcError("This Agent release cannot enforce connector privacy requirements. Update Hermes Agent before continuing, compressing or refining this chat.", condition="agent_incompatible")
1527
+
1528
+
1190
1529
  def _max_iterations_summary_request() -> str:
1191
1530
  """The user row the Agent appends to ask for a summary once its tool budget runs out; the server drops it."""
1192
1531
  try:
@@ -1326,7 +1665,12 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
1326
1665
  history = []
1327
1666
  if not isinstance(history, list):
1328
1667
  raise InvalidParams("conversation_history must be a list")
1329
- toolsets = _turn_toolsets(params.get("enabled_toolsets"))
1668
+ _require_data_use_support(history)
1669
+ from hermes_cli.config import load_config_readonly
1670
+
1671
+ # Borrow one snapshot per turn; helpers copy values retained by the Agent.
1672
+ cfg = load_config_readonly() or {}
1673
+ toolsets = _turn_toolsets(params.get("enabled_toolsets"), cfg)
1330
1674
  system_message = params.get("system_message")
1331
1675
  home = profile_home_param(params)
1332
1676
  run = _Run(stream_id, session_id, ctx)
@@ -1348,8 +1692,12 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
1348
1692
  raw_emit = ctx.emit
1349
1693
  steer_state = {"last": None}
1350
1694
  usage_state = {"last": None}
1695
+ pending_mcp = False
1696
+ token_parts: list[str] = []
1697
+ last_token_flush: float | None = None
1698
+ emit_lock = threading.Lock()
1351
1699
 
1352
- def emit(event, data=None):
1700
+ def emit_frame(event, data=None):
1353
1701
  """Predecessor ``_webui_steer_events_before``: before content frames, report the Agent's pending steer text
1354
1702
  whenever it changed so the server can mark consumed steers live rather than only after ``done``. TAL-397: the
1355
1703
  Agent's session counters ride the same boundary, so the server meters cost and tokens while the turn runs."""
@@ -1363,9 +1711,38 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
1363
1711
  usage_state["last"] = usage
1364
1712
  raw_emit("usage", usage)
1365
1713
  raw_emit(event, data)
1714
+
1715
+ def flush_tokens():
1716
+ nonlocal last_token_flush
1717
+ if token_parts:
1718
+ text = "".join(token_parts)
1719
+ token_parts.clear()
1720
+ last_token_flush = time.monotonic()
1721
+ emit_frame("token", {"text": text})
1722
+
1723
+ def emit(event, data=None):
1724
+ # Batch before snapshots/serialization; the lock keeps tool-worker and watcher frames in order.
1725
+ with emit_lock:
1726
+ if event == "token":
1727
+ token_parts.append(data["text"])
1728
+ if last_token_flush is None or time.monotonic() - last_token_flush >= 0.016:
1729
+ flush_tokens()
1730
+ else:
1731
+ flush_tokens()
1732
+ emit_frame(event, data)
1733
+
1734
+ timing = getattr(ctx, "timing", None)
1366
1735
  try:
1367
- _discover_mcp_tools(run)
1368
- runtime = _resolve_runtime(provider, model)
1736
+ if timing:
1737
+ timing.begin("mcp_discovery")
1738
+ mcp_discovery = _discover_mcp_tools(run)
1739
+ pending_mcp = mcp_discovery is not None
1740
+ if timing:
1741
+ timing.end("mcp_discovery", "cancelled" if ctx.cancelled or run.cancel.is_set() else "timeout" if pending_mcp else run.mcp_discovery_outcome)
1742
+ with timing.span("runtime_resolution") if timing else contextlib.nullcontext():
1743
+ runtime = _resolve_runtime(provider, model)
1744
+ if timing:
1745
+ timing.begin("agent_binding")
1369
1746
  resolved_model = model or str(runtime.get("model") or "")
1370
1747
  resolved_provider = provider or runtime.get("provider")
1371
1748
  AIAgent = _agent_class()
@@ -1449,7 +1826,12 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
1449
1826
  return metadata
1450
1827
 
1451
1828
  def on_tool(*cb_args, **cb_kwargs):
1452
- # Structured callbacks carry tool cards; the progress callback only feeds reasoning text on older builds.
1829
+ # Structured callbacks carry tool cards; the Agent already throttles MCP progress.
1830
+ if len(cb_args) >= 3 and cb_args[0] == "tool.progress":
1831
+ emit("tool_progress", {"tool_call_id": str(cb_kwargs.get("tool_call_id") or ""), "message": cb_args[2],
1832
+ "progress": cb_kwargs.get("progress"), "total": cb_kwargs.get("total"),
1833
+ "percent": cb_kwargs.get("percent")})
1834
+ return
1453
1835
  if len(cb_args) >= 3 and cb_args[0] in ("reasoning.available", "_thinking"):
1454
1836
  text = cb_args[2] if cb_args[0] == "reasoning.available" else cb_args[1]
1455
1837
  if text:
@@ -1634,7 +2016,7 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
1634
2016
  register_gateway_notify = unregister_gateway_notify = None
1635
2017
  approval_cb = _approval_notifier(session_id, emit) if register_gateway_notify is not None else None
1636
2018
 
1637
- kwargs = _agent_kwargs(AIAgent, params, session_id, resolved_model, resolved_provider, runtime, toolsets)
2019
+ kwargs = _agent_kwargs(AIAgent, params, session_id, resolved_model, resolved_provider, runtime, toolsets, cfg)
1638
2020
  kwargs.update(stream_delta_callback=on_token, reasoning_callback=on_reasoning, tool_progress_callback=on_tool, clarify_callback=clarify_callback)
1639
2021
  for name, value in (
1640
2022
  ("interim_assistant_callback", on_interim),
@@ -1666,6 +2048,7 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
1666
2048
  agent = cached[0]
1667
2049
  _AGENT_CACHE.move_to_end(session_id)
1668
2050
  bind(agent)
2051
+ cached_agent = agent is not None
1669
2052
  if agent is not None:
1670
2053
  for name in ("stream_delta_callback", "reasoning_callback", "tool_progress_callback", "clarify_callback", "interim_assistant_callback", "tool_start_callback", "tool_complete_callback", "tool_result_metadata_callback", "status_callback", "thinking_callback", "notice_callback", "notice_clear_callback"):
1671
2054
  if name in kwargs and hasattr(agent, name):
@@ -1692,6 +2075,9 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
1692
2075
  _AGENT_CACHE[session_id] = (agent, signature, home)
1693
2076
  _release_agents_locked([replaced] if replaced else [])
1694
2077
  _evict_idle_agents_locked()
2078
+ if timing:
2079
+ timing.end("agent_binding", "cache_hit" if cached_agent else "ok")
2080
+ timing.begin("conversation_setup")
1695
2081
  # Predecessor ``agent.ephemeral_system_prompt``: personality, surface context, progress guidance, and delivery
1696
2082
  # hints travel as runtime instructions that are never persisted to history.
1697
2083
  ephemeral = params.get("ephemeral_system_prompt")
@@ -1717,7 +2103,19 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
1717
2103
  log.debug("agent.clear_interrupt failed", exc_info=True)
1718
2104
 
1719
2105
  def _watch_cancel():
2106
+ nonlocal pending_mcp
1720
2107
  while not run.cancel.wait(0.25):
2108
+ if pending_mcp and mcp_discovery.is_set():
2109
+ # Discovery can finish while this agent is being built, after the worker's cache refresh.
2110
+ try:
2111
+ with scoped_home(home):
2112
+ refresh_profile_agents_mcp_tools(home)
2113
+ except Exception: # noqa: BLE001 - refresh failure must not disable Stop
2114
+ log.warning("Late MCP refresh failed", exc_info=True)
2115
+ with run.lock:
2116
+ if pending_mcp:
2117
+ emit("notice_clear", {"key": _MCP_PENDING_KEY})
2118
+ pending_mcp = False
1721
2119
  if run.finished.is_set():
1722
2120
  return
1723
2121
  if ctx.cancelled:
@@ -1765,6 +2163,10 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
1765
2163
  bind_turn_secret_capture = None
1766
2164
  log.debug("Agent lacks turn-local skill secret capture; secure setup is unavailable")
1767
2165
  with bind_turn_secret_capture(skill_secret_prompt) if bind_turn_secret_capture and params.get("skill_secret") is True else contextlib.nullcontext():
2166
+ if timing:
2167
+ timing.end("conversation_setup")
2168
+ timing.observe("conversation_entry")
2169
+ timing.emit("conversation_entry")
1768
2170
  raw = agent.run_conversation(**run_kwargs)
1769
2171
  result = raw if isinstance(raw, dict) else {}
1770
2172
  except Exception as exc: # noqa: BLE001
@@ -1827,6 +2229,12 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
1827
2229
  "live_tool_calls": live_tool_calls,
1828
2230
  }
1829
2231
  finally:
2232
+ with emit_lock:
2233
+ flush_tokens()
2234
+ with run.lock:
2235
+ if pending_mcp:
2236
+ emit("notice_clear", {"key": _MCP_PENDING_KEY})
2237
+ pending_mcp = False
1830
2238
  with _AGENT_CACHE_LOCK:
1831
2239
  with _RUNS_LOCK:
1832
2240
  _RUNS.pop(stream_id, None)
@@ -1900,6 +2308,7 @@ def compress(ctx: CallContext, params: dict) -> dict:
1900
2308
  history = params.get("conversation_history")
1901
2309
  if not isinstance(history, list):
1902
2310
  raise InvalidParams("conversation_history must be a list")
2311
+ _require_data_use_support(history)
1903
2312
  model = str(params.get("model") or "").strip()
1904
2313
  provider = str(params.get("model_provider") or "").strip() or None
1905
2314
  focus_topic = str(params.get("focus_topic") or "").strip()[:500] or None
@@ -1987,7 +2396,7 @@ def _refine_agent(params: dict, session_id: str, home):
1987
2396
  return None
1988
2397
  cached = _AGENT_CACHE.get(session_id)
1989
2398
  if cached is not None:
1990
- _COMMITTING.append(cached[0])
2399
+ _COMMITTING.append((cached[0], home))
1991
2400
  return cached[0]
1992
2401
  model = str(params.get("model") or "").strip()
1993
2402
  provider = str(params.get("model_provider") or "").strip() or None
@@ -2012,7 +2421,7 @@ def _refine_agent(params: dict, session_id: str, home):
2012
2421
  cached = _AGENT_CACHE.get(session_id)
2013
2422
  agent = None if running else cached[0] if cached else built
2014
2423
  if agent is not None:
2015
- _COMMITTING.append(agent)
2424
+ _COMMITTING.append((agent, home))
2016
2425
  if agent is built:
2017
2426
  built._talaria_unannounced = True # its first turn announces it (``start``)
2018
2427
  _AGENT_CACHE[session_id] = (built, signature, home)
@@ -2030,6 +2439,7 @@ def refine(params: dict) -> str:
2030
2439
  messages = params.get("messages")
2031
2440
  if not session_id or not isinstance(messages, list):
2032
2441
  raise InvalidParams("session_id and messages are required")
2442
+ _require_data_use_support(messages)
2033
2443
  focus = str(params.get("focus") or "").strip()
2034
2444
  with _RUNS_LOCK:
2035
2445
  if _session_running_locked(session_id):
@@ -2049,7 +2459,7 @@ def refine(params: dict) -> str:
2049
2459
  return f"/refine failed to start: {exc}"
2050
2460
  finally:
2051
2461
  with _AGENT_CACHE_LOCK:
2052
- _COMMITTING.remove(agent)
2462
+ _COMMITTING.remove((agent, home))
2053
2463
  _release_if_dropped_locked(agent, home)
2054
2464
  tail = f" (focus: {focus})" if focus else ""
2055
2465
  return f"⚗ Reviewing this conversation in the background{tail} — any memory/skill updates will be reported when done."
@@ -2074,18 +2484,18 @@ def register(registry) -> None:
2074
2484
  workspace = params.get("workspace")
2075
2485
  if not isinstance(workspace, str) or not workspace.strip():
2076
2486
  raise InvalidParams("workspace is required")
2077
- with scoped_home(profile_home_param(params)):
2487
+ with agent_scope(profile_home_param(params)):
2078
2488
  with _turn_identity(session_id, workspace):
2079
2489
  return start(ctx, params)
2080
2490
 
2081
2491
  @registry.method("chat.compress")
2082
2492
  def compress_(ctx: CallContext, params: dict) -> dict:
2083
- with scoped_home(profile_home_param(params)):
2493
+ with agent_scope(profile_home_param(params)):
2084
2494
  return compress(ctx, params)
2085
2495
 
2086
2496
  @registry.method("chat.refine")
2087
2497
  def refine_(ctx: CallContext, params: dict) -> dict:
2088
- with scoped_home(profile_home_param(params)):
2498
+ with agent_scope(profile_home_param(params)):
2089
2499
  return {"output": refine(params)}
2090
2500
 
2091
2501
  @registry.method("chat.compress_finalize", requires_agent=False)
@@ -2156,15 +2566,22 @@ def register(registry) -> None:
2156
2566
  def finalize_(ctx: CallContext, params: dict) -> dict:
2157
2567
  session_id = str(params.get("session_id") or "").strip()
2158
2568
  reason = params.get("reason")
2159
- if not session_id or reason not in ("clear", "delete"):
2160
- raise InvalidParams("session_id and reason (clear/delete) are required")
2569
+ if not session_id or reason not in ("clear", "delete", "archive"):
2570
+ raise InvalidParams("session_id and reason (clear/delete/archive) are required")
2571
+ lineage = params.get("lineage")
2572
+ if lineage is not None and (not isinstance(lineage, list) or len(lineage) > 10_000
2573
+ or any(not isinstance(sid, str) or not sid for sid in lineage)):
2574
+ raise InvalidParams("lineage must contain session ids")
2161
2575
  home = profile_home_param(params)
2162
2576
  messages: list[str] = []
2163
2577
 
2164
2578
  def finalize() -> None:
2165
2579
  try:
2166
2580
  # Scope inside the worker: a timed-out plugin keeps its own profile until it finishes.
2167
- with scoped_home(home):
2581
+ with agent_scope(home):
2582
+ if reason == "archive":
2583
+ _notify_archive(home, session_id, lineage)
2584
+ return
2168
2585
  from hermes_cli import lifecycle
2169
2586
 
2170
2587
  callback = getattr(lifecycle, "finalize_session", None)
@@ -2175,11 +2592,15 @@ def register(registry) -> None:
2175
2592
  if extract is not None:
2176
2593
  messages.extend(extract(results))
2177
2594
  except Exception: # noqa: BLE001 - hooks must never block the boundary
2178
- log.warning("Session finalize failed for %s (%s)", session_id, reason, exc_info=True)
2595
+ log.warning("Session lifecycle callback failed (%s)", reason)
2179
2596
  finally:
2180
2597
  with _RELEASES_LOCK:
2181
2598
  _RELEASES.pop(worker, None)
2182
2599
 
2600
+ if reason == "archive":
2601
+ worker = _ArchiveCallback(finalize)
2602
+ _schedule_archive(worker, home)
2603
+ return {"messages": []}
2183
2604
  worker = threading.Thread(target=finalize, name="session-finalize", daemon=True)
2184
2605
  worker.failed = False # type: ignore[attr-defined] - hook failures do not prevent profile deletion
2185
2606
  with _RELEASES_LOCK:
@@ -2187,7 +2608,7 @@ def register(registry) -> None:
2187
2608
  worker.start()
2188
2609
  worker.join(_SESSION_FINALIZE_TIMEOUT)
2189
2610
  if worker.is_alive():
2190
- log.warning("Session finalize exceeded %ss for %s (%s)", _SESSION_FINALIZE_TIMEOUT, session_id, reason)
2611
+ log.warning("Session lifecycle callback exceeded deadline (%s)", reason)
2191
2612
  return {"messages": []}
2192
2613
  return {"messages": messages}
2193
2614
 
@@ -2219,12 +2640,52 @@ def register(registry) -> None:
2219
2640
  _release_vault_session(session_id)
2220
2641
  return {"evicted": entry is not None}
2221
2642
 
2643
+ @registry.method("chat.context_detail", requires_agent=False)
2644
+ def context_detail_(ctx: CallContext, params: dict) -> dict:
2645
+ from ..context_detail import project, unavailable
2646
+ home = profile_home_param(params).resolve()
2647
+ sid = str(params.get("session_id") or "")
2648
+ with _AGENT_CACHE_LOCK:
2649
+ cached = _AGENT_CACHE.get(sid)
2650
+ if cached is None:
2651
+ return unavailable("not_built")
2652
+ agent = cached[0]
2653
+ if (Path(cached[2]).resolve() != home or getattr(agent, "model", None) != params.get("model")
2654
+ or getattr(agent, "provider", None) != params.get("model_provider")):
2655
+ return unavailable("changed")
2656
+ def marks():
2657
+ comp = getattr(agent, "context_compressor", None)
2658
+ return (getattr(agent, "model", None), getattr(agent, "provider", None),
2659
+ getattr(comp, "compression_count", None), getattr(comp, "last_prompt_tokens", None),
2660
+ len(getattr(agent, "_session_messages", None) or []))
2661
+ before = marks()
2662
+ # Pin the cache entry across the read just like refine/memory, so eviction defers its release.
2663
+ _COMMITTING.append((agent, cached[2]))
2664
+ try:
2665
+ from agent.context_breakdown import compute_session_context_breakdown
2666
+ from agent.context_file_sources import context_file_sources_for_agent
2667
+ with scoped_home(home), session_workspace(params.get("workspace")):
2668
+ payload = compute_session_context_breakdown(agent, list(getattr(agent, "_session_messages", None) or []))
2669
+ payload['context_files'] = context_file_sources_for_agent(agent)
2670
+ with _AGENT_CACHE_LOCK:
2671
+ if _AGENT_CACHE.get(sid) is not cached or before != marks():
2672
+ return unavailable("changed")
2673
+ return project(payload)
2674
+ except ImportError:
2675
+ return unavailable("unsupported")
2676
+ except Exception: # never expose Agent diagnostics, prompt contents or paths
2677
+ return unavailable()
2678
+ finally:
2679
+ with _AGENT_CACHE_LOCK:
2680
+ _COMMITTING.remove((agent, cached[2]))
2681
+ _release_if_dropped_locked(agent, cached[2])
2682
+
2222
2683
  @registry.method("chat.commit_memory", requires_agent=False)
2223
2684
  def commit_memory_(ctx: CallContext, params: dict) -> dict:
2224
2685
  """Predecessor ``commit_session_memory``: flush the cached Agent's memory for a session the user just left.
2225
- Providers get the session's transcript and run under its profile's ``scoped_home``."""
2686
+ Providers get the session's transcript and run under the ``scoped_home`` the Agent was built in, which v0.22.3
2687
+ binds them to (like ``_release_agent``), not the request's ``profile_home``."""
2226
2688
  session_id = str(params.get("session_id") or "").strip()
2227
- home = profile_home_param(params)
2228
2689
  with _AGENT_CACHE_LOCK:
2229
2690
  cached = _AGENT_CACHE.get(session_id)
2230
2691
  with _RUNS_LOCK:
@@ -2233,16 +2694,16 @@ def register(registry) -> None:
2233
2694
  commit = getattr(agent, "commit_memory_session", None) if agent is not None else None
2234
2695
  if commit is None or busy:
2235
2696
  return {"committed": False}
2236
- _COMMITTING.append(agent)
2697
+ _COMMITTING.append((agent, cached[2]))
2237
2698
  try:
2238
- with scoped_home(home):
2699
+ with scoped_home(cached[2]):
2239
2700
  commit(list(getattr(agent, "_session_messages", None) or []))
2240
2701
  except Exception as exc: # noqa: BLE001
2241
2702
  log.warning("commit_memory_session() failed for session %s: %s", session_id, exc)
2242
2703
  return {"committed": False}
2243
2704
  finally:
2244
2705
  with _AGENT_CACHE_LOCK:
2245
- _COMMITTING.remove(agent)
2706
+ _COMMITTING.remove((agent, cached[2]))
2246
2707
  _release_if_dropped_locked(agent, cached[2])
2247
2708
  return {"committed": True}
2248
2709
 
@@ -2259,7 +2720,7 @@ def register(registry) -> None:
2259
2720
  raise RpcError(f"approval module unavailable: {exc}", condition="agent_unavailable") from exc
2260
2721
  with scoped_home(profile_home_param(params)):
2261
2722
  entries = approval_mod.list_gateway_approvals(session_id)
2262
- targets = [e for e in entries if not request_id or e.get("request_id") == request_id]
2723
+ targets = [e for e in entries if e.get("request_id") == request_id] if request_id else entries[:1]
2263
2724
  keys = []
2264
2725
  for entry in targets:
2265
2726
  keys.extend(entry.get("pattern_keys") or [entry.get("pattern_key", "")])
@@ -2267,17 +2728,21 @@ def register(registry) -> None:
2267
2728
  if choice == "session":
2268
2729
  for k in keys:
2269
2730
  approval_mod.approve_session(session_id, k)
2270
- elif choice == "always":
2731
+ elif choice == "always" and keys:
2732
+ permanent_set = getattr(approval_mod, "_permanent_set", None)
2733
+ baseline_key = getattr(approval_mod, "_baseline_key", None)
2734
+ baselines = getattr(approval_mod, "_permanent_baseline_by_home", None)
2735
+ if not callable(permanent_set) or not callable(baseline_key) or not isinstance(baselines, dict):
2736
+ raise RpcError("Agent cannot safely persist command approvals", condition="agent_incompatible")
2271
2737
  for k in keys:
2272
2738
  approval_mod.approve_session(session_id, k)
2273
2739
  approval_mod.approve_permanent(k)
2274
2740
  try:
2275
- # Persist the set that governs THIS home (per-profile under the home override), not the launch
2276
- # profile's module-level set, which would overwrite a named profile's allowlist with the wrong keys.
2277
- permanent_set = getattr(approval_mod, "_permanent_set", None)
2741
+ # Older retained Agents' _persist_choice saves full snapshots. Capture only unsaved grants
2742
+ # against this home's baseline, so a concurrent revocation cannot become fresh approval intent.
2278
2743
  with approval_mod._lock:
2279
- snapshot = set(permanent_set()) if callable(permanent_set) else set(approval_mod._permanent_approved)
2280
- approval_mod.save_permanent_allowlist(snapshot)
2744
+ grants = permanent_set() - baselines.get(baseline_key(), set())
2745
+ approval_mod.save_permanent_allowlist(grants)
2281
2746
  except Exception: # noqa: BLE001
2282
2747
  log.debug("save_permanent_allowlist failed", exc_info=True)
2283
2748
  resolved = approval_mod.resolve_gateway_approval(session_id, choice, request_id=request_id)