@maudecode/talaria-web 1.16.1 → 1.18.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 (445) hide show
  1. package/_release.json +8 -8
  2. package/dist/api/auth-raw.d.ts +1 -1
  3. package/dist/api/auth-raw.js +4 -4
  4. package/dist/api/auth-raw.js.map +1 -1
  5. package/dist/api/automation-raw.d.ts +6 -0
  6. package/dist/api/automation-raw.js +59 -9
  7. package/dist/api/automation-raw.js.map +1 -1
  8. package/dist/api/automation-router.d.ts +163 -10
  9. package/dist/api/automation-router.js +25 -8
  10. package/dist/api/automation-router.js.map +1 -1
  11. package/dist/api/chat-router.d.ts +700 -15
  12. package/dist/api/chat-router.js +74 -55
  13. package/dist/api/chat-router.js.map +1 -1
  14. package/dist/api/git-router.d.ts +47 -2
  15. package/dist/api/git-router.js +31 -22
  16. package/dist/api/git-router.js.map +1 -1
  17. package/dist/api/raw-routes.d.ts +14 -0
  18. package/dist/api/raw-routes.js +277 -36
  19. package/dist/api/raw-routes.js.map +1 -1
  20. package/dist/api/router.d.ts +10 -0
  21. package/dist/api/router.js +10 -8
  22. package/dist/api/router.js.map +1 -1
  23. package/dist/api/sessions-router.d.ts +1710 -73
  24. package/dist/api/sessions-router.js +164 -10
  25. package/dist/api/sessions-router.js.map +1 -1
  26. package/dist/api/settings-router.d.ts +132 -2
  27. package/dist/api/settings-router.js +15 -5
  28. package/dist/api/settings-router.js.map +1 -1
  29. package/dist/api/sse-routes.d.ts +16 -3
  30. package/dist/api/sse-routes.js +194 -67
  31. package/dist/api/sse-routes.js.map +1 -1
  32. package/dist/api/tools-raw.js +12 -25
  33. package/dist/api/tools-raw.js.map +1 -1
  34. package/dist/api/tools-router.d.ts +425 -19
  35. package/dist/api/tools-router.js +157 -26
  36. package/dist/api/tools-router.js.map +1 -1
  37. package/dist/app.d.ts +0 -9377
  38. package/dist/app.js +42 -17
  39. package/dist/app.js.map +1 -1
  40. package/dist/auth/gate.js +8 -2
  41. package/dist/auth/gate.js.map +1 -1
  42. package/dist/auth/oidc.d.ts +5 -2
  43. package/dist/auth/oidc.js +8 -7
  44. package/dist/auth/oidc.js.map +1 -1
  45. package/dist/bin/talaria-web.js +1 -1
  46. package/dist/bin/talaria-web.js.map +1 -1
  47. package/dist/cli/dotenv.js +1 -0
  48. package/dist/cli/dotenv.js.map +1 -1
  49. package/dist/config/agent-config.d.ts +2 -0
  50. package/dist/config/agent-config.js +12 -2
  51. package/dist/config/agent-config.js.map +1 -1
  52. package/dist/config.d.ts +2 -0
  53. package/dist/config.js +5 -1
  54. package/dist/config.js.map +1 -1
  55. package/dist/http/context.d.ts +20 -3
  56. package/dist/http/context.js +41 -8
  57. package/dist/http/context.js.map +1 -1
  58. package/dist/http/csp.d.ts +2 -1
  59. package/dist/http/csp.js +3 -2
  60. package/dist/http/csp.js.map +1 -1
  61. package/dist/onboarding.js +4 -0
  62. package/dist/onboarding.js.map +1 -1
  63. package/dist/profiles/profiles.d.ts +10 -0
  64. package/dist/profiles/profiles.js +40 -2
  65. package/dist/profiles/profiles.js.map +1 -1
  66. package/dist/providers/catalog.d.ts +101 -8
  67. package/dist/providers/catalog.js +463 -123
  68. package/dist/providers/catalog.js.map +1 -1
  69. package/dist/raster-image.d.ts +7 -0
  70. package/dist/raster-image.js +267 -0
  71. package/dist/raster-image.js.map +1 -0
  72. package/dist/redact.d.ts +1 -0
  73. package/dist/redact.js +354 -44
  74. package/dist/redact.js.map +1 -1
  75. package/dist/runtime.js +111 -27
  76. package/dist/runtime.js.map +1 -1
  77. package/dist/server.js +5 -0
  78. package/dist/server.js.map +1 -1
  79. package/dist/sessions/anchor.d.ts +2 -0
  80. package/dist/sessions/anchor.js +8 -3
  81. package/dist/sessions/anchor.js.map +1 -1
  82. package/dist/sessions/background-tasks.d.ts +2 -9
  83. package/dist/sessions/background-tasks.js +2 -12
  84. package/dist/sessions/background-tasks.js.map +1 -1
  85. package/dist/sessions/claude-code.d.ts +19 -0
  86. package/dist/sessions/claude-code.js +250 -0
  87. package/dist/sessions/claude-code.js.map +1 -0
  88. package/dist/sessions/cli-sessions.d.ts +15 -4
  89. package/dist/sessions/cli-sessions.js +120 -29
  90. package/dist/sessions/cli-sessions.js.map +1 -1
  91. package/dist/sessions/completions.d.ts +61 -9
  92. package/dist/sessions/completions.js +282 -34
  93. package/dist/sessions/completions.js.map +1 -1
  94. package/dist/sessions/compress.d.ts +14 -1
  95. package/dist/sessions/compress.js +52 -11
  96. package/dist/sessions/compress.js.map +1 -1
  97. package/dist/sessions/export.d.ts +5 -0
  98. package/dist/sessions/export.js +30 -0
  99. package/dist/sessions/export.js.map +1 -1
  100. package/dist/sessions/gateway-watcher.d.ts +7 -3
  101. package/dist/sessions/gateway-watcher.js +31 -19
  102. package/dist/sessions/gateway-watcher.js.map +1 -1
  103. package/dist/sessions/handoff.d.ts +32 -0
  104. package/dist/sessions/handoff.js +114 -0
  105. package/dist/sessions/handoff.js.map +1 -0
  106. package/dist/sessions/journal.d.ts +8 -0
  107. package/dist/sessions/journal.js +44 -10
  108. package/dist/sessions/journal.js.map +1 -1
  109. package/dist/sessions/list.d.ts +13 -3
  110. package/dist/sessions/list.js +46 -15
  111. package/dist/sessions/list.js.map +1 -1
  112. package/dist/sessions/merge.d.ts +53 -5
  113. package/dist/sessions/merge.js +219 -23
  114. package/dist/sessions/merge.js.map +1 -1
  115. package/dist/sessions/recovery.d.ts +22 -0
  116. package/dist/sessions/recovery.js +506 -0
  117. package/dist/sessions/recovery.js.map +1 -0
  118. package/dist/sessions/relay.d.ts +21 -2
  119. package/dist/sessions/relay.js +156 -11
  120. package/dist/sessions/relay.js.map +1 -1
  121. package/dist/sessions/service.d.ts +152 -8
  122. package/dist/sessions/service.js +640 -72
  123. package/dist/sessions/service.js.map +1 -1
  124. package/dist/sessions/session.d.ts +17 -1
  125. package/dist/sessions/session.js +63 -4
  126. package/dist/sessions/session.js.map +1 -1
  127. package/dist/sessions/state-db-worker.d.ts +1 -0
  128. package/dist/sessions/state-db-worker.js +33 -0
  129. package/dist/sessions/state-db-worker.js.map +1 -0
  130. package/dist/sessions/state-db.d.ts +87 -4
  131. package/dist/sessions/state-db.js +417 -8
  132. package/dist/sessions/state-db.js.map +1 -1
  133. package/dist/sessions/store.d.ts +7 -0
  134. package/dist/sessions/store.js +36 -22
  135. package/dist/sessions/store.js.map +1 -1
  136. package/dist/sessions/streams.d.ts +10 -0
  137. package/dist/sessions/streams.js +14 -3
  138. package/dist/sessions/streams.js.map +1 -1
  139. package/dist/sessions/titles.d.ts +2 -0
  140. package/dist/sessions/titles.js +4 -0
  141. package/dist/sessions/titles.js.map +1 -1
  142. package/dist/sessions/todo.d.ts +6 -1
  143. package/dist/sessions/todo.js +11 -4
  144. package/dist/sessions/todo.js.map +1 -1
  145. package/dist/sessions/tool-display.d.ts +12 -1
  146. package/dist/sessions/tool-display.js +50 -1
  147. package/dist/sessions/tool-display.js.map +1 -1
  148. package/dist/sessions/turn-context.d.ts +1 -1
  149. package/dist/sessions/turn-context.js +11 -7
  150. package/dist/sessions/turn-context.js.map +1 -1
  151. package/dist/sessions/turn.d.ts +67 -11
  152. package/dist/sessions/turn.js +413 -111
  153. package/dist/sessions/turn.js.map +1 -1
  154. package/dist/sessions/wakeup-pause.d.ts +59 -0
  155. package/dist/sessions/wakeup-pause.js +181 -0
  156. package/dist/sessions/wakeup-pause.js.map +1 -0
  157. package/dist/settings.d.ts +20 -2
  158. package/dist/settings.js +45 -9
  159. package/dist/settings.js.map +1 -1
  160. package/dist/sidecar/fake.d.ts +4 -4
  161. package/dist/sidecar/fake.js +5 -2
  162. package/dist/sidecar/fake.js.map +1 -1
  163. package/dist/spa.js +2 -1
  164. package/dist/spa.js.map +1 -1
  165. package/dist/startup.d.ts +13 -5
  166. package/dist/startup.js +48 -0
  167. package/dist/startup.js.map +1 -1
  168. package/dist/test/harness.js +2 -0
  169. package/dist/test/harness.js.map +1 -1
  170. package/dist/test/ts-worker-hooks.d.ts +1 -0
  171. package/dist/test/ts-worker-hooks.js +24 -0
  172. package/dist/test/ts-worker-hooks.js.map +1 -0
  173. package/dist/text/diff.d.ts +9 -14
  174. package/dist/text/diff.js +74 -108
  175. package/dist/text/diff.js.map +1 -1
  176. package/dist/tools/crons.js +45 -8
  177. package/dist/tools/crons.js.map +1 -1
  178. package/dist/tools/dashboard-plugins.d.ts +53 -0
  179. package/dist/tools/dashboard-plugins.js +188 -0
  180. package/dist/tools/dashboard-plugins.js.map +1 -0
  181. package/dist/tools/extensions.d.ts +13 -1
  182. package/dist/tools/extensions.js +132 -60
  183. package/dist/tools/extensions.js.map +1 -1
  184. package/dist/tools/health.d.ts +32 -0
  185. package/dist/tools/health.js +111 -19
  186. package/dist/tools/health.js.map +1 -1
  187. package/dist/tools/insights.d.ts +8 -2
  188. package/dist/tools/insights.js +18 -2
  189. package/dist/tools/insights.js.map +1 -1
  190. package/dist/tools/kanban.d.ts +41 -1
  191. package/dist/tools/kanban.js +113 -2
  192. package/dist/tools/kanban.js.map +1 -1
  193. package/dist/tools/mcp.d.ts +15 -1
  194. package/dist/tools/mcp.js +99 -5
  195. package/dist/tools/mcp.js.map +1 -1
  196. package/dist/tools/project-os.d.ts +11 -0
  197. package/dist/tools/project-os.js +267 -0
  198. package/dist/tools/project-os.js.map +1 -0
  199. package/dist/tools/skills.d.ts +5 -1
  200. package/dist/tools/skills.js +5 -3
  201. package/dist/tools/skills.js.map +1 -1
  202. package/dist/tools/terminal.d.ts +1 -0
  203. package/dist/tools/terminal.js +1 -1
  204. package/dist/tools/terminal.js.map +1 -1
  205. package/dist/tools/updates.d.ts +33 -8
  206. package/dist/tools/updates.js +236 -57
  207. package/dist/tools/updates.js.map +1 -1
  208. package/dist/tools/wiki.d.ts +68 -0
  209. package/dist/tools/wiki.js +413 -0
  210. package/dist/tools/wiki.js.map +1 -0
  211. package/dist/workspace/escape.d.ts +61 -0
  212. package/dist/workspace/escape.js +149 -0
  213. package/dist/workspace/escape.js.map +1 -0
  214. package/dist/workspace/extract.d.ts +22 -0
  215. package/dist/workspace/extract.js +308 -0
  216. package/dist/workspace/extract.js.map +1 -0
  217. package/dist/workspace/fs.d.ts +19 -3
  218. package/dist/workspace/fs.js +96 -23
  219. package/dist/workspace/fs.js.map +1 -1
  220. package/dist/workspace/git.d.ts +18 -0
  221. package/dist/workspace/git.js +38 -25
  222. package/dist/workspace/git.js.map +1 -1
  223. package/dist/workspace/media.d.ts +4 -4
  224. package/dist/workspace/media.js +18 -45
  225. package/dist/workspace/media.js.map +1 -1
  226. package/dist/workspace/preview.d.ts +28 -0
  227. package/dist/workspace/preview.js +110 -0
  228. package/dist/workspace/preview.js.map +1 -0
  229. package/dist/workspace/rollback.d.ts +36 -21
  230. package/dist/workspace/rollback.js +179 -270
  231. package/dist/workspace/rollback.js.map +1 -1
  232. package/dist/workspace/unzip.js +4 -1
  233. package/dist/workspace/unzip.js.map +1 -1
  234. package/dist/workspace/upload.d.ts +9 -4
  235. package/dist/workspace/upload.js +9 -3
  236. package/dist/workspace/upload.js.map +1 -1
  237. package/dist/workspace/worktrees.d.ts +2 -1
  238. package/dist/workspace/worktrees.js +14 -15
  239. package/dist/workspace/worktrees.js.map +1 -1
  240. package/package.json +4 -2
  241. package/sidecar/agent_dependency.json +4 -4
  242. package/sidecar/scripts/replay_sidecar.py +25 -1
  243. package/sidecar/talaria_sidecar/__init__.py +1 -1
  244. package/sidecar/talaria_sidecar/__main__.py +7 -1
  245. package/sidecar/talaria_sidecar/home.py +52 -2
  246. package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
  247. package/sidecar/talaria_sidecar/methods/aux.py +39 -5
  248. package/sidecar/talaria_sidecar/methods/chat.py +431 -43
  249. package/sidecar/talaria_sidecar/methods/checkpoints.py +89 -0
  250. package/sidecar/talaria_sidecar/methods/commands.py +96 -2
  251. package/sidecar/talaria_sidecar/methods/cron.py +39 -8
  252. package/sidecar/talaria_sidecar/methods/gateway.py +32 -15
  253. package/sidecar/talaria_sidecar/methods/kanban.py +140 -19
  254. package/sidecar/talaria_sidecar/methods/mcp.py +1 -1
  255. package/sidecar/talaria_sidecar/methods/process.py +115 -2
  256. package/sidecar/talaria_sidecar/methods/profiles.py +84 -8
  257. package/sidecar/talaria_sidecar/methods/providers.py +27 -8
  258. package/sidecar/talaria_sidecar/methods/runtime.py +3 -0
  259. package/sidecar/talaria_sidecar/methods/state_db.py +39 -5
  260. package/sidecar/talaria_sidecar/methods/text.py +17 -1
  261. package/sidecar/talaria_sidecar/methods/usage.py +257 -4
  262. package/sidecar/talaria_sidecar/runtime.py +17 -1
  263. package/static/dist/FILES.txt +127 -125
  264. package/static/dist/assets/AppShell-g3bkgnvt.js +1 -0
  265. package/static/dist/assets/ChatPage-oo7bdan6.js +25 -0
  266. package/static/dist/assets/Dialog-g355qxhp.js +1 -0
  267. package/static/dist/assets/ExtensionRoute-i2y9fvjm.js +1 -0
  268. package/static/dist/assets/HubRoute-lmkrbw47.js +2 -0
  269. package/static/dist/assets/{Markdown-jqrx5es0.js → Markdown-j4kut4j2.js} +125 -125
  270. package/static/dist/assets/RightPanel-ohlsdzfl.js +1 -0
  271. package/static/dist/assets/Select-kddq75qc.js +1 -0
  272. package/static/dist/assets/SessionListPanel-lu3oy0hw.js +1 -0
  273. package/static/dist/assets/SettingsLayout-o9bv4mgb.js +1 -0
  274. package/static/dist/assets/States-eq6fe29m.js +1 -0
  275. package/static/dist/assets/Toaster-pc247u9f.js +1 -0
  276. package/static/dist/assets/_app-f8n1i0m1.js +1 -0
  277. package/static/dist/assets/_app.ext._extensionId-gfbzpoou.js +1 -0
  278. package/static/dist/assets/_app.index-bzzv80hk.js +1 -0
  279. package/static/dist/assets/_app.insights-dzixbryi.js +1 -0
  280. package/static/dist/assets/_app.kanban-c6hep2us.js +1 -0
  281. package/static/dist/assets/_app.logs-i69qds98.js +1 -0
  282. package/static/dist/assets/_app.memory-kl4vf0gd.js +1 -0
  283. package/static/dist/assets/_app.profiles-b8zinmyp.js +1 -0
  284. package/static/dist/assets/{_app.session._sessionId-hh27wwu2.js → _app.session._sessionId-fn1bhde6.js} +1 -1
  285. package/static/dist/assets/_app.settings-cg8plwmf.js +1 -0
  286. package/static/dist/assets/_app.settings._section-bkwbi9zn.js +3 -0
  287. package/static/dist/assets/_app.skills-cs47sbvs.js +1 -0
  288. package/static/dist/assets/_app.tasks-dwvk64tc.js +1 -0
  289. package/static/dist/assets/_app.todos-c8xjm8p3.js +1 -0
  290. package/static/dist/assets/_app.workspaces-ehvd1gds.js +1 -0
  291. package/static/dist/assets/{abnfDiagram-VCTEODGH-oity9y4o.js → abnfDiagram-VCTEODGH-c8kdixke.js} +1 -1
  292. package/static/dist/assets/architecture-7GRP2DOG-6crvnylg.js +1 -0
  293. package/static/dist/assets/{architectureDiagram-5GKGNRK7-ceaepnyr.js → architectureDiagram-5GKGNRK7-cku06x1p.js} +1 -1
  294. package/static/dist/assets/{back-eu64w89w.js → back-h7aun7ew.js} +1 -1
  295. package/static/dist/assets/{blockDiagram-I7D4REHJ-n79st4pj.js → blockDiagram-I7D4REHJ-jzthp1wa.js} +1 -1
  296. package/static/dist/assets/{c4Diagram-7LVT6UL2-mi73akzk.js → c4Diagram-7LVT6UL2-mwr67mt8.js} +1 -1
  297. package/static/dist/assets/{cancel-mzvkdjg8.js → cancel-fhi0vcnu.js} +1 -1
  298. package/static/dist/assets/channel-b2zo73z1.js +1 -0
  299. package/static/dist/assets/{chunk-4HAMMTFA-laat0g1q.js → chunk-4HAMMTFA-gpq1y228.js} +1 -1
  300. package/static/dist/assets/{chunk-75Z2AOVW-0psy1tr2.js → chunk-75Z2AOVW-ktje58c4.js} +1 -1
  301. package/static/dist/assets/{chunk-DU6HZSFF-lw8uox8x.js → chunk-DU6HZSFF-ofajdixh.js} +3 -3
  302. package/static/dist/assets/{chunk-F27PBJKO-erassib3.js → chunk-F27PBJKO-cmev85ti.js} +1 -1
  303. package/static/dist/assets/{chunk-GMAD6QVW-fz7rx619.js → chunk-GMAD6QVW-htjgt22y.js} +1 -1
  304. package/static/dist/assets/{chunk-GVQU2GXP-gdb0epb2.js → chunk-GVQU2GXP-qndvoxl7.js} +1 -1
  305. package/static/dist/assets/{chunk-IMKFNOWR-ea8t26qw.js → chunk-IMKFNOWR-e5bkd5q6.js} +1 -1
  306. package/static/dist/assets/{chunk-L3NEJ4N5-mb1gu9kz.js → chunk-L3NEJ4N5-mj1155r6.js} +1 -1
  307. package/static/dist/assets/{chunk-OSK3NFVY-eovmccvy.js → chunk-OSK3NFVY-e7gkikcw.js} +1 -1
  308. package/static/dist/assets/{chunk-P2QGCYS3-bo55zjo5.js → chunk-P2QGCYS3-f7t54xfm.js} +1 -1
  309. package/static/dist/assets/{chunk-POPQ4Y6H-ja7ohe4h.js → chunk-POPQ4Y6H-i927v9oa.js} +1 -1
  310. package/static/dist/assets/{chunk-PWAF6VOD-e30g9nus.js → chunk-PWAF6VOD-m84lmnyt.js} +1 -1
  311. package/static/dist/assets/{chunk-SHT3W25Y-gbb798f7.js → chunk-SHT3W25Y-mi0tlcu9.js} +1 -1
  312. package/static/dist/assets/{chunk-SVP7TREG-h2iw8urm.js → chunk-SVP7TREG-dnau702j.js} +1 -1
  313. package/static/dist/assets/{chunk-TICWLB2K-fd2cottz.js → chunk-TICWLB2K-edem1gkg.js} +1 -1
  314. package/static/dist/assets/classDiagram-ZZMXUADV-oe99st8x.js +1 -0
  315. package/static/dist/assets/classDiagram-v2-VYDZK3BY-oe99st8x.js +1 -0
  316. package/static/dist/assets/{copied-h65fc3cx.js → copied-hcplv46p.js} +1 -1
  317. package/static/dist/assets/copy-magtofyp.js +1 -0
  318. package/static/dist/assets/{cynefin-OW5HDTMX-lbltyl5j.js → cynefin-OW5HDTMX-siqlulkl.js} +1 -1
  319. package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-lxhsqq4a.js → cynefinDiagram-5FMLGOSQ-i648tah9.js} +1 -1
  320. package/static/dist/assets/{dagre-GXQ25YYZ-gn4nnaln.js → dagre-GXQ25YYZ-mwn6uc17.js} +1 -1
  321. package/static/dist/assets/{diagram-S7CK7UJ4-pa3ij6q7.js → diagram-S7CK7UJ4-l8abxaes.js} +1 -1
  322. package/static/dist/assets/{diagram-UQ7AKVKN-zbipyz57.js → diagram-UQ7AKVKN-mekq7zgq.js} +1 -1
  323. package/static/dist/assets/{diagram-VSXAHHWV-d1kbpzgf.js → diagram-VSXAHHWV-fnky6lvc.js} +1 -1
  324. package/static/dist/assets/{diagram-VX7I27RA-i3ty14xl.js → diagram-VX7I27RA-xe6r5qfw.js} +1 -1
  325. package/static/dist/assets/{diagram-Z3DM3KII-m8xefqa7.js → diagram-Z3DM3KII-nq07dg7k.js} +1 -1
  326. package/static/dist/assets/{ebnfDiagram-PWID7BFC-nz7wj05l.js → ebnfDiagram-PWID7BFC-c62ipm3g.js} +1 -1
  327. package/static/dist/assets/endpoints-klbxod4t.js +64 -0
  328. package/static/dist/assets/{erDiagram-RLTQ6QDP-fa41ib83.js → erDiagram-RLTQ6QDP-h39kmnzy.js} +1 -1
  329. package/static/dist/assets/eventmodeling-NTZA5JFV-obfede4u.js +1 -0
  330. package/static/dist/assets/flowDiagram-HODETNUW-kdpsp1je.js +1 -0
  331. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-n7zq01qu.js → ganttDiagram-EL5Y4UJY-ngbsyp37.js} +1 -1
  332. package/static/dist/assets/{gitGraph-4MIJSDKK-c89emb07.js → gitGraph-4MIJSDKK-fr2i7caa.js} +1 -1
  333. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-cz7cgr6w.js → gitGraphDiagram-WWUBYQGX-eravdti4.js} +1 -1
  334. package/static/dist/assets/{highlighted-body-KPVGNVTW-d9bapmni.js → highlighted-body-KPVGNVTW-jwpop9p8.js} +1 -1
  335. package/static/dist/assets/host-i828xuo7.js +1 -0
  336. package/static/dist/assets/index-eh8zilnv.css +2 -0
  337. package/static/dist/assets/index-r648f4o1.js +11 -0
  338. package/static/dist/assets/{info-A6RAGUB7-ndddhhhh.js → info-A6RAGUB7-h11x0dxo.js} +1 -1
  339. package/static/dist/assets/{infoDiagram-27XIBGKW-ccxigcpf.js → infoDiagram-27XIBGKW-dfh47dw9.js} +1 -1
  340. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-le0z9vtc.js → ishikawaDiagram-5VMMS53U-i6ypq351.js} +1 -1
  341. package/static/dist/assets/{journeyDiagram-3NMN7TZE-ivsmb26o.js → journeyDiagram-3NMN7TZE-lduf1z95.js} +1 -1
  342. package/static/dist/assets/{kanban-definition-UXKFOSKX-h6n2awt0.js → kanban-definition-UXKFOSKX-e18bu4pg.js} +1 -1
  343. package/static/dist/assets/lazyRouteComponent-e1y4t2i9.js +1 -0
  344. package/static/dist/assets/{line-dmzlfe0u.js → line-meizqsvn.js} +1 -1
  345. package/static/dist/assets/link-oot2yn73.js +1 -0
  346. package/static/dist/assets/login-gu28l9ou.js +1 -0
  347. package/static/dist/assets/mermaid-HWGCJPDP-mtwrj8fn.js +1 -0
  348. package/static/dist/assets/{mermaid-parser.core-gwyd5bnq.js → mermaid-parser.core-ntne45i9.js} +3 -3
  349. package/static/dist/assets/{mindmap-definition-YA3MSWOX-l68gg5h7.js → mindmap-definition-YA3MSWOX-27b26eok.js} +1 -1
  350. package/static/dist/assets/onboarding-lkphzu1s.js +1 -0
  351. package/static/dist/assets/onboarding_model_label-ftpjdwqk.js +1 -0
  352. package/static/dist/assets/{packet-AYTQ26CC-gpv06065.js → packet-AYTQ26CC-l6jdka8p.js} +1 -1
  353. package/static/dist/assets/{pegDiagram-XKGWAZYB-e52vs23u.js → pegDiagram-XKGWAZYB-g1er3bxj.js} +1 -1
  354. package/static/dist/assets/{pie-WAS4IAKB-l6m7nz5s.js → pie-WAS4IAKB-i0ylcrt9.js} +1 -1
  355. package/static/dist/assets/{pieDiagram-E7YTZNPT-b4is36bq.js → pieDiagram-E7YTZNPT-fbz1e55z.js} +1 -1
  356. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-m8vt2788.js → quadrantDiagram-AXDQQJYC-hhue7d8o.js} +1 -1
  357. package/static/dist/assets/{radar-RG4KPBEZ-nxbrhiov.js → radar-RG4KPBEZ-f9b70aa4.js} +1 -1
  358. package/static/dist/assets/{railroad-74A4TZTK-222mu9hi.js → railroad-74A4TZTK-lgsuoeda.js} +1 -1
  359. package/static/dist/assets/railroad-abnf-HS5TGJTU-nf9d6pdx.js +1 -0
  360. package/static/dist/assets/railroad-ebnf-LZEXJU2U-f7ummkt5.js +1 -0
  361. package/static/dist/assets/railroad-peg-WCYAUIDC-oczq5tqs.js +1 -0
  362. package/static/dist/assets/{railroadDiagram-O6MQD6OU-o8lldfzv.js → railroadDiagram-O6MQD6OU-eqpy9fnz.js} +1 -1
  363. package/static/dist/assets/refresh-abtu93ke.js +1 -0
  364. package/static/dist/assets/refresh-cw-hlwb02qv.js +1 -0
  365. package/static/dist/assets/{requirementDiagram-BXWQKSXE-ovyq38kv.js → requirementDiagram-BXWQKSXE-ogf6puhi.js} +1 -1
  366. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-ou7lznor.js → sankeyDiagram-P5KCCOFB-fqb8xcjf.js} +1 -1
  367. package/static/dist/assets/{saved-bazmcup9.js → saved-girnabkf.js} +1 -1
  368. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-ovckdzf2.js → sequenceDiagram-WJ2MYXX4-dlcz6qjz.js} +1 -1
  369. package/static/dist/assets/{settings_section_extensions_title-ertwwhlx.js → settings_section_extensions_title-h14cwk6f.js} +1 -1
  370. package/static/dist/assets/share._token-kl0r7ido.js +2 -0
  371. package/static/dist/assets/sliders-horizontal-n1zm34qq.js +1 -0
  372. package/static/dist/assets/sse-iwku0sjo.js +1 -0
  373. package/static/dist/assets/{stateDiagram-D77RDMKH-fcrexkkv.js → stateDiagram-D77RDMKH-fyoznpcs.js} +1 -1
  374. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-ixhx8r46.js +1 -0
  375. package/static/dist/assets/{swimlanes-42K2YHIH-f7usu8fw.js → swimlanes-42K2YHIH-oefldhfy.js} +1 -1
  376. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-h9ersk2e.js +8 -0
  377. package/static/dist/assets/{timeline-definition-24CTP7MA-gp1hvz9f.js → timeline-definition-24CTP7MA-b1vl2zil.js} +1 -1
  378. package/static/dist/assets/todoStore-jjr5yr78.js +1 -0
  379. package/static/dist/assets/{treeView-Q6P3EWNA-gb9gf11u.js → treeView-Q6P3EWNA-f7hcci5o.js} +1 -1
  380. package/static/dist/assets/{treemap-WGGIJYW6-nuvfnvvi.js → treemap-WGGIJYW6-nl85071a.js} +1 -1
  381. package/static/dist/assets/units-kgxzxfhi.js +1 -0
  382. package/static/dist/assets/{untitled-htelhc68.js → untitled-cikfjntn.js} +1 -1
  383. package/static/dist/assets/{useForm-h2t0u14e.js → useForm-kqoqg0k9.js} +1 -1
  384. package/static/dist/assets/useLocale-o59tbiea.js +1 -0
  385. package/static/dist/assets/{useParams-dqd64jv7.js → useParams-b4dq6n4q.js} +1 -1
  386. package/static/dist/assets/{useSearch-j30pvsy7.js → useSearch-ipfmv3he.js} +1 -1
  387. package/static/dist/assets/{vennDiagram-4TSXK5OY-fzchbzaj.js → vennDiagram-4TSXK5OY-h5hzbk0n.js} +1 -1
  388. package/static/dist/assets/{wardley-WFR3VGLG-m47mhfbh.js → wardley-WFR3VGLG-jftvpm3e.js} +1 -1
  389. package/static/dist/assets/{wardleyDiagram-VM6X3IG4-odg6ct1z.js → wardleyDiagram-VM6X3IG4-hedh85ge.js} +1 -1
  390. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-mrhms5um.js → xychartDiagram-S5SC5T6Z-e49523ab.js} +1 -1
  391. package/static/dist/index.html +1 -1
  392. package/static/dist/sw.js +2 -2
  393. package/static/dist/assets/AppShell-ef3bhu32.js +0 -1
  394. package/static/dist/assets/Button-nx37f4u1.js +0 -1
  395. package/static/dist/assets/ChatPage-cdcpykcg.js +0 -25
  396. package/static/dist/assets/Dialog-mec42hvb.js +0 -1
  397. package/static/dist/assets/HubRoute-ecb65zpr.js +0 -2
  398. package/static/dist/assets/RightPanel-d1lgvk4s.js +0 -1
  399. package/static/dist/assets/Select-h4ivl9qf.js +0 -1
  400. package/static/dist/assets/SessionListPanel-ewib046b.js +0 -1
  401. package/static/dist/assets/SettingsLayout-lx654cup.js +0 -1
  402. package/static/dist/assets/States-ehsb6lzo.js +0 -1
  403. package/static/dist/assets/Toaster-mch1pa3t.js +0 -1
  404. package/static/dist/assets/_app-m8d961ef.js +0 -1
  405. package/static/dist/assets/_app.ext._extensionId-mp33hbzi.js +0 -1
  406. package/static/dist/assets/_app.index-dk6zp7e0.js +0 -1
  407. package/static/dist/assets/_app.insights-jt2fk4i1.js +0 -1
  408. package/static/dist/assets/_app.kanban-if4pubeu.js +0 -1
  409. package/static/dist/assets/_app.logs-mfaw9m6g.js +0 -1
  410. package/static/dist/assets/_app.memory-h3eaarb6.js +0 -1
  411. package/static/dist/assets/_app.profiles-ienktovs.js +0 -1
  412. package/static/dist/assets/_app.settings-dpoe0bzf.js +0 -1
  413. package/static/dist/assets/_app.settings._section-l468ynag.js +0 -3
  414. package/static/dist/assets/_app.skills-kcbncc8i.js +0 -1
  415. package/static/dist/assets/_app.tasks-occdrqhw.js +0 -2
  416. package/static/dist/assets/_app.todos-nnke2rcc.js +0 -1
  417. package/static/dist/assets/_app.workspaces-cdlatvwj.js +0 -1
  418. package/static/dist/assets/architecture-7GRP2DOG-hj11333z.js +0 -1
  419. package/static/dist/assets/channel-mbvmf2vv.js +0 -1
  420. package/static/dist/assets/classDiagram-ZZMXUADV-o8r05k0n.js +0 -1
  421. package/static/dist/assets/classDiagram-v2-VYDZK3BY-o8r05k0n.js +0 -1
  422. package/static/dist/assets/copy-e6977uwq.js +0 -1
  423. package/static/dist/assets/endpoints-k7yf25m0.js +0 -64
  424. package/static/dist/assets/eventmodeling-NTZA5JFV-kpbq09bx.js +0 -1
  425. package/static/dist/assets/flowDiagram-HODETNUW-lcz0kx14.js +0 -1
  426. package/static/dist/assets/index-h00usru2.js +0 -11
  427. package/static/dist/assets/index-n73ijkv2.css +0 -2
  428. package/static/dist/assets/lazyRouteComponent-m1rtyk4t.js +0 -1
  429. package/static/dist/assets/link-budeg5t9.js +0 -1
  430. package/static/dist/assets/login-kjhasvp8.js +0 -1
  431. package/static/dist/assets/matchContext-f8oy3lo7.js +0 -1
  432. package/static/dist/assets/mermaid-HWGCJPDP-jb5d211j.js +0 -1
  433. package/static/dist/assets/onboarding-cdfrk0pj.js +0 -1
  434. package/static/dist/assets/railroad-abnf-HS5TGJTU-kdvgl6x8.js +0 -1
  435. package/static/dist/assets/railroad-ebnf-LZEXJU2U-itiqslby.js +0 -1
  436. package/static/dist/assets/railroad-peg-WCYAUIDC-go7kov78.js +0 -1
  437. package/static/dist/assets/refresh-cw-kbcje1uy.js +0 -1
  438. package/static/dist/assets/search-jen74z4u.js +0 -1
  439. package/static/dist/assets/share._token-dan9hgvj.js +0 -2
  440. package/static/dist/assets/sliders-horizontal-f9cb3lh3.js +0 -1
  441. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-lcfraua2.js +0 -1
  442. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-ogmanni0.js +0 -8
  443. package/static/dist/assets/todoStore-eqtid4r4.js +0 -1
  444. package/static/dist/assets/useLocale-hcsb84pj.js +0 -1
  445. package/static/dist/assets/useNavigate-nwy70ztj.js +0 -1
@@ -12,23 +12,31 @@ from __future__ import annotations
12
12
 
13
13
  import contextlib
14
14
  import contextvars
15
+ import copy
15
16
  import hashlib
16
17
  import inspect
17
18
  import json
18
19
  import logging
20
+ import os
19
21
  import threading
20
22
  import time
21
23
  import uuid
22
24
  from collections import OrderedDict
25
+ from pathlib import Path
23
26
  from typing import Any
24
27
 
25
28
  from ..errors import InvalidParams, RpcError
26
29
  from ..home import profile_home_param, scoped_home
27
30
  from ..rpc import CallContext
31
+ from .checkpoints import checkpoint_kwargs
28
32
 
29
33
  log = logging.getLogger("talaria_sidecar.chat")
30
34
 
31
35
  _AGENT_CACHE_MAX = 32
36
+ # Shutdown waits this long for evicted agents' memory providers; the server kills the sidecar 5 s after closing stdin.
37
+ _AGENT_RELEASE_TIMEOUT = 4.0
38
+ # Profile deletion waits this long for the profile's memory flushes, under the server's 120 s call timeout.
39
+ _PROFILE_RELEASE_TIMEOUT = 60.0
32
40
  _TOOL_RESULT_SNIPPET_MAX = 4000
33
41
  # Predecessor ``_TOOL_ARG_CONTENT_KEYS`` (#4928): card content / diff-reconstruction inputs keep the long cap.
34
42
  _TOOL_ARG_CONTENT_KEYS = frozenset({"command", "cmd", "script", "code", "patch", "diff", "old_string", "new_string", "content", "path", "file_path"})
@@ -65,8 +73,16 @@ _RUNS_BY_SESSION: dict[str, str] = {}
65
73
  _APPROVAL_CB_OWNER: dict[str, str] = {}
66
74
  _APPROVAL_CB_LOCK = threading.Lock()
67
75
  _RUNS_LOCK = threading.Lock()
68
- _AGENT_CACHE: "OrderedDict[str, tuple[Any, str]]" = OrderedDict()
76
+ # session id -> (agent, signature, profile home).
77
+ _AGENT_CACHE: "OrderedDict[str, tuple[Any, str, Any]]" = OrderedDict()
69
78
  _AGENT_CACHE_LOCK = threading.Lock()
79
+ # Running release thread -> the profile home it runs under.
80
+ _RELEASES: dict[threading.Thread, Any] = {}
81
+ _RELEASES_LOCK = threading.Lock()
82
+ # Agents inside ``chat.commit_memory`` (guarded by ``_AGENT_CACHE_LOCK``): like a turn, a commit defers their release.
83
+ _COMMITTING: list = []
84
+ # Set (under ``_RUNS_LOCK``) when shutdown drains: no turn is admitted after the drain's snapshot of ``_RUNS``.
85
+ _DRAINING = threading.Event()
70
86
 
71
87
 
72
88
  def _snippet(raw: Any, limit: int = _TOOL_RESULT_SNIPPET_MAX) -> str:
@@ -88,6 +104,16 @@ _RAW_RESULT_MAX_KEYS = 64
88
104
  _RAW_RESULT_OUTCOME_KEYS = ("error", "exit_code", "exitCode", "success")
89
105
 
90
106
 
107
+ def _result_diff(raw: Any) -> str | None:
108
+ """A dict (or JSON-object text) result's string ``diff`` field, whole; the server decides what it shows."""
109
+ try:
110
+ data = raw if isinstance(raw, dict) else json.loads(str(raw or ""))
111
+ except Exception: # noqa: BLE001
112
+ return None
113
+ diff = data.get("diff") if isinstance(data, dict) else None
114
+ return diff if isinstance(diff, str) else None
115
+
116
+
91
117
  def _raw_result(raw: Any, limit: int = _TOOL_RESULT_SNIPPET_MAX) -> Any:
92
118
  """The tool result as the server's outcome rule reads it, bounded: a dict (or JSON-object text) keeps its first
93
119
  ``_RAW_RESULT_MAX_KEYS`` top-level fields plus its outcome fields, scalars as they are, text and non-empty nested
@@ -232,6 +258,98 @@ def _context_length(agent: Any) -> dict:
232
258
  return out
233
259
 
234
260
 
261
+ # TAL-539: the hard cap on raw tool output kept in a compressed context (predecessor ``_hard_prune_post_compression_*``).
262
+ _TAIL_TOOL_BUDGET_TOKENS = 4096
263
+ _TAIL_TOOL_MIN_SNIPPET_TOKENS = 256
264
+ _ROUGH_TOKEN_CHARS = 4
265
+
266
+
267
+ def _content_text(content: Any) -> str:
268
+ if isinstance(content, list):
269
+ return " ".join(str(p.get("text") or "") for p in content if isinstance(p, dict))
270
+ return str(content or "")
271
+
272
+
273
+ def _rough_tokens(text: str) -> int:
274
+ """The Agent's estimate, which counts CJK and other non-ASCII text as denser; ceil(chars / 4) without it."""
275
+ try:
276
+ from agent.model_metadata import estimate_tokens_rough
277
+
278
+ return int(estimate_tokens_rough(text))
279
+ except Exception: # noqa: BLE001
280
+ return -(-len(text) // _ROUGH_TOKEN_CHARS)
281
+
282
+
283
+ def _capped_tool_result(text: str, tokens: int, keep_tokens: int) -> str:
284
+ """The kept head of an over-budget tool result plus a note on what the model no longer sees."""
285
+ prefix = "[Talaria context budget: omitted "
286
+ suffix = f" (~{tokens} rough tokens) from this tool result; the full output remains in the visible transcript.]"
287
+ if keep_tokens < _TAIL_TOOL_MIN_SNIPPET_TOKENS:
288
+ return f"{prefix}{len(text)} chars{suffix}"
289
+ room = keep_tokens - _rough_tokens(f"{prefix}{len(text)} of {len(text)} chars{suffix}") - 1
290
+ snippet = text[: len(text) * room // tokens]
291
+ # Density varies along the text: shrink until the kept head fits the remaining tokens.
292
+ while snippet and _rough_tokens(snippet) > room:
293
+ snippet = snippet[: len(snippet) * 3 // 4]
294
+ snippet = snippet.rstrip()
295
+ if not snippet:
296
+ return f"{prefix}{len(text)} chars{suffix}"
297
+ return f"{snippet}\n\n{prefix}{len(text) - len(snippet)} of {len(text)} chars{suffix}"
298
+
299
+
300
+ def _cap_tool_results(messages: list, compressor: Any) -> list:
301
+ """Newest tool results spend one shared budget; the first one over it is cut to the remainder, older ones to a note."""
302
+ budget = getattr(compressor, "tail_token_budget", None)
303
+ budget = budget if isinstance(budget, int) and budget > 0 else _TAIL_TOOL_BUDGET_TOKENS
304
+ threshold = getattr(compressor, "threshold_tokens", None)
305
+ if isinstance(threshold, int) and threshold > 0:
306
+ budget = min(budget, max(512, threshold // 2))
307
+ budget = max(512, budget)
308
+ spent = 0
309
+ out = list(messages)
310
+ for i in range(len(out) - 1, -1, -1):
311
+ msg = out[i]
312
+ if msg.get("role") != "tool":
313
+ continue
314
+ text = _content_text(msg.get("content"))
315
+ if not text.strip():
316
+ continue
317
+ tokens = _rough_tokens(text)
318
+ if tokens <= budget - spent:
319
+ spent += tokens
320
+ continue
321
+ out[i] = {**msg, "content": _capped_tool_result(text, tokens, budget - spent)}
322
+ spent = budget
323
+ return out
324
+
325
+
326
+ def _post_compression_context(agent: Any, messages: list, system_message: Any) -> tuple[list, int | None]:
327
+ """TAL-539: tool output produced after a mid-turn compression never went through the compressor's pruning. Run that
328
+ pruning plus a hard cap on the model context, and estimate the next request from it (predecessor
329
+ ``_prune_context_tool_results_after_compression`` / ``_estimate_post_compression_context_tokens``)."""
330
+ compressor = getattr(agent, "context_compressor", None)
331
+ context = messages
332
+ prune = getattr(compressor, "_prune_old_tool_results", None)
333
+ if callable(prune):
334
+ try:
335
+ pruned, count = prune(copy.deepcopy(messages), protect_tail_count=getattr(compressor, "protect_last_n", 20), protect_tail_tokens=getattr(compressor, "tail_token_budget", None))
336
+ if count and isinstance(pruned, list) and len(pruned) == len(messages):
337
+ context = pruned
338
+ except Exception: # noqa: BLE001 - the hard cap below still bounds the context
339
+ log.debug("post-compression tool-result pruning failed", exc_info=True)
340
+ context = _cap_tool_results(context, compressor)
341
+ estimate = None
342
+ try:
343
+ from agent.model_metadata import estimate_request_tokens_rough
344
+
345
+ # The request carries the ephemeral prompt (surface, personality, delivery) after the system message.
346
+ prompts = [p for p in (system_message, getattr(agent, "ephemeral_system_prompt", None)) if isinstance(p, str) and p.strip()]
347
+ estimate = estimate_request_tokens_rough(context, system_prompt="\n\n".join(prompts), tools=getattr(agent, "tools", None) or None)
348
+ except Exception: # noqa: BLE001 - display-only figure
349
+ log.debug("post-compression context estimate failed", exc_info=True)
350
+ return context, estimate if isinstance(estimate, int) and estimate > 0 else None
351
+
352
+
235
353
  def _clarify_timeout(params: dict) -> int:
236
354
  """Predecessor ``_clarify_timeout_seconds``: an explicit request value wins, else the Agent's resolver over config."""
237
355
  explicit = params.get("clarify_timeout_seconds")
@@ -379,10 +497,8 @@ def _turn_identity(session_id: str, workspace: str):
379
497
  not fatal."""
380
498
  resets: list = []
381
499
  try:
382
- try:
383
- from tools.approval_context import reset_current_session_key, set_current_session_key
384
- except ImportError:
385
- from tools.approval import reset_current_session_key, set_current_session_key # type: ignore[no-redef]
500
+ from tools.approval_context import reset_current_session_key, set_current_session_key
501
+
386
502
  token = set_current_session_key(session_id)
387
503
  resets.append(lambda: reset_current_session_key(token))
388
504
  except Exception: # noqa: BLE001
@@ -421,7 +537,7 @@ def _turn_identity(session_id: str, workspace: str):
421
537
 
422
538
 
423
539
  #: Older configs named the CLI composite ``hermes``; the Agent registers it split in two (predecessor
424
- #: ``_LEGACY_CLI_TOOLSET_ALIASES``).
540
+ #: ``_LEGACY_CLI_TOOLSET_ALIASES``). Agent 0.21.6 expands it itself; delete this once 0.21.6 is the oldest supported.
425
541
  _LEGACY_TOOLSET_ALIASES = {"hermes": ("hermes-cli", "hermes-api-server")}
426
542
 
427
543
 
@@ -477,6 +593,30 @@ def _profile_fallback_chain() -> list[dict] | None:
477
593
  return get_fallback_chain(load_config() or {}) or None
478
594
 
479
595
 
596
+ def _main_model_request_overrides(model: str, provider) -> dict | None:
597
+ """Predecessor ``_main_model_request_overrides``: the profile's ``model.service_tier`` and ``model.extra_body``
598
+ (written by ``/api/model/set``) apply only to turns on that configured main model. Runs under ``scoped_home``."""
599
+ from hermes_cli.config import load_config
600
+
601
+ model_cfg = (load_config() or {}).get("model")
602
+ if not isinstance(model_cfg, dict):
603
+ return None
604
+ main_model = str(model_cfg.get("default") or model_cfg.get("name") or "").strip()
605
+ main_provider = str(model_cfg.get("provider") or "").strip().lower()
606
+ if not main_model or main_model != model.strip():
607
+ return None
608
+ # An unset or ``auto`` main provider resolves at runtime, so the model id alone identifies the main model.
609
+ if main_provider not in ("", "auto") and main_provider != str(provider or "").strip().lower():
610
+ return None
611
+ overrides: dict = {}
612
+ if str(model_cfg.get("service_tier") or "").strip().lower() == "priority":
613
+ overrides["service_tier"] = "priority"
614
+ extra_body = model_cfg.get("extra_body")
615
+ if isinstance(extra_body, dict) and extra_body:
616
+ overrides["extra_body"] = extra_body
617
+ return overrides or None
618
+
619
+
480
620
  def _agent_signature(model: str, provider, runtime: dict, toolsets, home: str, kwargs: dict) -> str:
481
621
  """Cache identity of an ``AIAgent``: everything its constructor bound from the resolved runtime, so a rotated key,
482
622
  a different API mode, ACP command, or credential pool never reuses an agent built for the old bundle. The key
@@ -492,6 +632,8 @@ def _agent_signature(model: str, provider, runtime: dict, toolsets, home: str, k
492
632
  "max_iterations": kwargs.get("max_iterations"), "max_tokens": kwargs.get("max_tokens"),
493
633
  # Bound at construction too: a reasoning-effort change from the composer must build a fresh agent.
494
634
  "reasoning_config": kwargs.get("reasoning_config"),
635
+ "request_overrides": kwargs.get("request_overrides"),
636
+ "checkpoints": [kwargs.get(name) for name in ("checkpoints_enabled", "checkpoint_max_snapshots", "checkpoint_max_total_size_mb", "checkpoint_max_file_size_mb")],
495
637
  # Entries may carry their own ``api_key``.
496
638
  "fallback_model": hashlib.sha256(json.dumps(kwargs.get("fallback_model"), sort_keys=True, default=str).encode("utf-8")).hexdigest()[:16],
497
639
  }
@@ -504,19 +646,164 @@ def _evict_idle_agents_locked() -> None:
504
646
  return
505
647
  with _RUNS_LOCK:
506
648
  live = {sid for sid, sid_stream in _RUNS_BY_SESSION.items() if (r := _RUNS.get(sid_stream)) is not None and not r.finished.is_set()}
649
+ dropped = []
507
650
  for sid in list(_AGENT_CACHE):
508
651
  if len(_AGENT_CACHE) <= _AGENT_CACHE_MAX:
509
652
  break
510
653
  if sid not in live:
511
- _AGENT_CACHE.pop(sid, None)
654
+ dropped.append(_AGENT_CACHE.pop(sid))
655
+ _release_agents_locked(dropped)
656
+
657
+
658
+ def _release_agent(agent, home) -> threading.Thread:
659
+ """The gateway's soft eviction on a daemon thread under the owning profile: end the memory session with the
660
+ transcript (``on_session_end``, then provider shutdown), then release the LLM clients (the Codex app-server child,
661
+ httpx pools) while session tool state stays for a resumed session."""
662
+
663
+ def call(name: str, *args) -> bool:
664
+ method = getattr(agent, name, None)
665
+ if not callable(method):
666
+ return True
667
+ try:
668
+ method(*args)
669
+ except Exception: # noqa: BLE001 - one failed phase never skips the next
670
+ log.warning("evicted agent %s() failed", name, exc_info=True)
671
+ return False
672
+ return True
673
+
674
+ def release() -> None:
675
+ released = False
676
+ try:
677
+ with scoped_home(home):
678
+ messages = getattr(agent, "_session_messages", None)
679
+ flushed = call("shutdown_memory_provider", messages if isinstance(messages, list) else None)
680
+ released = True
681
+ thread.failed = not (call("release_clients") and flushed)
682
+ except Exception: # noqa: BLE001 - the profile scope could not be entered
683
+ log.warning("releasing an evicted agent under its profile failed", exc_info=True)
684
+ thread.failed = True
685
+ finally:
686
+ if not released:
687
+ # The memory flush needs the profile; the LLM clients and the Codex child close regardless.
688
+ call("release_clients")
689
+ _release_session_db(getattr(agent, "_talaria_session_db", None))
690
+ with _RELEASES_LOCK:
691
+ _RELEASES.pop(thread, None)
692
+
693
+ thread = threading.Thread(target=release, daemon=True, name="chat-agent-release")
694
+ thread.failed = False # type: ignore[attr-defined] - read by profile deletion
695
+ with _RELEASES_LOCK:
696
+ _RELEASES[thread] = home
697
+ thread.start()
698
+ return thread
699
+
700
+
701
+ def _release_agents_locked(entries) -> list[threading.Thread]:
702
+ """Release cache entries that just left the cache (caller holds ``_AGENT_CACHE_LOCK``). An agent a turn still holds
703
+ keeps its memory provider and clients; that turn releases it when it ends."""
704
+ with _RUNS_LOCK:
705
+ held = [run.agent for run in _RUNS.values()] + _COMMITTING
706
+ return [_release_agent(agent, home) for agent, _signature, home in entries if not any(agent is other for other in held)]
707
+
708
+
709
+ def _release_if_dropped_locked(agent, home) -> None:
710
+ """A holder (turn or commit) is done with ``agent``: release it if it left the cache meanwhile and nothing else holds
711
+ it (caller holds ``_AGENT_CACHE_LOCK``)."""
712
+ if agent is not None and not any(entry[0] is agent for entry in _AGENT_CACHE.values()):
713
+ _release_agents_locked([(agent, None, home)])
714
+
715
+
716
+ def _join(threads, deadline: float) -> None:
717
+ for thread in threads:
718
+ thread.join(max(0.0, deadline - time.monotonic()))
512
719
 
513
720
 
514
721
  def evict_all_agents() -> int:
515
722
  """Drop every cached agent (credentials or environment changed underneath them)."""
516
723
  with _AGENT_CACHE_LOCK:
517
- count = len(_AGENT_CACHE)
724
+ entries = list(_AGENT_CACHE.values())
518
725
  _AGENT_CACHE.clear()
519
- return count
726
+ _release_agents_locked(entries)
727
+ return len(entries)
728
+
729
+
730
+ def drain_agents(timeout: float = _AGENT_RELEASE_TIMEOUT) -> None:
731
+ """Shutdown: release every cached agent, stop running turns (like a Stop) so each releases the agent it holds, and
732
+ wait, bounded, for those turns and every release still running."""
733
+ deadline = time.monotonic() + timeout
734
+ evict_all_agents()
735
+ with _RUNS_LOCK:
736
+ _DRAINING.set()
737
+ runs = list(_RUNS.values())
738
+ for run in runs:
739
+ run.cancel.set()
740
+ while time.monotonic() < deadline:
741
+ with _AGENT_CACHE_LOCK, _RUNS_LOCK:
742
+ if not _COMMITTING and not any(_RUNS.get(run.stream_id) is run for run in runs):
743
+ break
744
+ time.sleep(0.05)
745
+ # A turn may have cached its agent after the first eviction.
746
+ evict_all_agents()
747
+ with _RELEASES_LOCK:
748
+ threads = list(_RELEASES)
749
+ _join(threads, deadline)
750
+
751
+
752
+ def release_profile_agents(home) -> None:
753
+ """Profile deletion: release the profile's cached agents and wait for every release running under its home. Fails
754
+ rather than let the home go while a memory flush still runs under it."""
755
+ target = os.path.realpath(home)
756
+ with _AGENT_CACHE_LOCK:
757
+ entries = [_AGENT_CACHE.pop(sid) for sid, entry in list(_AGENT_CACHE.items()) if os.path.realpath(entry[2]) == target]
758
+ _release_agents_locked(entries)
759
+ with _RELEASES_LOCK:
760
+ threads = [thread for thread, owner in _RELEASES.items() if os.path.realpath(owner) == target]
761
+ _join(threads, time.monotonic() + _PROFILE_RELEASE_TIMEOUT)
762
+ if any(thread.is_alive() for thread in threads):
763
+ raise RpcError("The profile is still saving memory from its chats. Retry in a moment.")
764
+ if any(thread.failed for thread in threads):
765
+ raise RpcError("Memory from the profile's chats could not be saved; see the sidecar log. Retry to delete the profile anyway.")
766
+
767
+
768
+ def _acquire_session_db(home):
769
+ """TAL-529: the profile's ``state.db`` from the Agent's shared-handle registry, one reference per agent (released with
770
+ it), so the turn's conversation reaches ``hermes sessions``, CLI resume, and ``session_search``. None when it cannot
771
+ open: the turn runs without it, like the predecessor's ``_build_session_db_for_stream``."""
772
+ try:
773
+ from hermes_state_registry import acquire
774
+
775
+ return acquire(Path(home) / "state.db")
776
+ except Exception: # noqa: BLE001
777
+ log.warning("state.db unavailable under %s; this agent's turns will not reach it", home, exc_info=True)
778
+ return None
779
+
780
+
781
+ def _release_session_db(db) -> None:
782
+ if db is None:
783
+ return
784
+ try:
785
+ from hermes_state_registry import release_or_close
786
+
787
+ release_or_close(db)
788
+ except Exception: # noqa: BLE001
789
+ log.warning("releasing an agent's state.db handle failed", exc_info=True)
790
+
791
+
792
+ def _execution_session_id(db, session_id: str) -> str:
793
+ """Predecessor ``_resolve_agent_execution_session_id``: a new agent for a session the Agent rotated by compression
794
+ (the old agent went with a sidecar restart or a runtime change) runs on the live tip, with the server's history,
795
+ rather than the closed Web id. The tip qualifies as in the Agent's own adoption: a different, still-open row. Any
796
+ doubt keeps the Web id."""
797
+ try:
798
+ tip = db.get_compression_tip(session_id)
799
+ if tip and tip != session_id:
800
+ row = db.get_session(tip)
801
+ if row and row.get("ended_at") is None:
802
+ log.info("agent for %s runs on its live compression tip %s", session_id, tip)
803
+ return str(tip)
804
+ except Exception: # noqa: BLE001
805
+ log.warning("compression tip lookup failed for %s", session_id, exc_info=True)
806
+ return session_id
520
807
 
521
808
 
522
809
  def _agent_class():
@@ -527,6 +814,15 @@ def _agent_class():
527
814
  return AIAgent
528
815
 
529
816
 
817
+ def _max_iterations_summary_request() -> str:
818
+ """The user row the Agent appends to ask for a summary once its tool budget runs out; the server drops it."""
819
+ try:
820
+ from agent.context_compressor import MAX_ITERATIONS_SUMMARY_REQUEST
821
+ except Exception: # noqa: BLE001
822
+ return ""
823
+ return str(MAX_ITERATIONS_SUMMARY_REQUEST)
824
+
825
+
530
826
  def _supported(cls, name: str) -> bool:
531
827
  """Whether ``cls.__init__`` accepts ``name`` (named, or through ``**kwargs``)."""
532
828
  try:
@@ -564,6 +860,36 @@ def _approval_notifier(session_id: str, emit):
564
860
  return notify
565
861
 
566
862
 
863
+ #: The Agent's compression-start lifecycle lines: "Compacting context" (and its heartbeat), pre-API, 413, and too-large
864
+ #: compression. Preflight and idle notices are left out: "Compacting context" follows them once compression proceeds.
865
+ _COMPRESSION_START_MARKERS = ("compacting context", "pre-api compression:", "context too large", "— compressing (", "- compressing (", "compression attempt")
866
+ _COMPRESSION_NOT_STARTED = ("skipping", "defer", "cooldown", "will not start")
867
+
868
+
869
+ def _is_compression_start(lower: str) -> bool:
870
+ """Predecessor ``_is_agent_compression_start_status``: only real start notices, never skips or post-compress chatter."""
871
+ if any(marker in lower for marker in _COMPRESSION_NOT_STARTED):
872
+ return False
873
+ if "compressed" in lower and "compressing" not in lower and "compression attempt" not in lower:
874
+ return False
875
+ return any(marker in lower for marker in _COMPRESSION_START_MARKERS)
876
+
877
+
878
+ def _status_events(kind, text: str) -> list[str]:
879
+ """Events for one Agent status line: the compressing card, a fallback warning, and the turn's terminal error."""
880
+ lower = text.lower()
881
+ events = ["terminal_error"] if "non-retryable error" in lower else []
882
+ if str(kind or "").strip().lower() == "lifecycle":
883
+ if _is_compression_start(lower):
884
+ return events + ["compressing"]
885
+ # The Agent's own compression retries ("Compressed N → M messages, retrying...") are not provider fallbacks.
886
+ if "compress" in lower or "context reduced to" in lower:
887
+ return events
888
+ if "fallback" in lower or "rate limit" in lower or ("retry" in lower and "non-retryable" not in lower):
889
+ events.append("warning")
890
+ return events
891
+
892
+
567
893
  def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn, one function
568
894
  session_id = str(params.get("session_id") or "").strip()
569
895
  stream_id = str(params.get("stream_id") or "").strip()
@@ -581,8 +907,11 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
581
907
  raise InvalidParams("conversation_history must be a list")
582
908
  toolsets = _turn_toolsets(params.get("enabled_toolsets"))
583
909
  system_message = params.get("system_message")
910
+ home = profile_home_param(params)
584
911
  run = _Run(stream_id, session_id, ctx)
585
912
  with _RUNS_LOCK:
913
+ if _DRAINING.is_set():
914
+ raise RpcError("The sidecar is shutting down.", condition="sidecar_unavailable")
586
915
  prior = _RUNS.get(_RUNS_BY_SESSION.get(session_id) or "")
587
916
  # A previous turn for this session that never returned (a tool ignoring its interrupt) still owns the cached
588
917
  # agent; this turn must not share, rebind, or un-interrupt that live instance.
@@ -591,15 +920,21 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
591
920
  _RUNS_BY_SESSION[session_id] = stream_id
592
921
  raw_emit = ctx.emit
593
922
  steer_state = {"last": None}
923
+ usage_state = {"last": None}
594
924
 
595
925
  def emit(event, data=None):
596
926
  """Predecessor ``_webui_steer_events_before``: before content frames, report the Agent's pending steer text
597
- whenever it changed so the server can mark consumed steers live rather than only after ``done``."""
927
+ whenever it changed so the server can mark consumed steers live rather than only after ``done``. TAL-397: the
928
+ Agent's session counters ride the same boundary, so the server meters cost and tokens while the turn runs."""
598
929
  if event in ("token", "reasoning", "interim_assistant", "tool", "tool_complete") and run.agent is not None:
599
930
  pending = _agent_pending_steer_text(run.agent)
600
931
  if pending != steer_state["last"]:
601
932
  steer_state["last"] = pending
602
933
  raw_emit("steer_pending", {"text": pending})
934
+ usage = _usage(run.agent)
935
+ if usage != usage_state["last"]:
936
+ usage_state["last"] = usage
937
+ raw_emit("usage", usage)
603
938
  raw_emit(event, data)
604
939
  try:
605
940
  _discover_mcp_tools(run)
@@ -653,6 +988,13 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
653
988
  call["snippet"] = snippet
654
989
  break
655
990
  payload = {"event_type": "tool.completed", "name": name, "preview": snippet, "args": _args_snapshot(args), "tid": tid, "raw_result": _raw_result(function_result)}
991
+ # TAL-397: ``raw_result`` caps nested values, so the todo tool's list travels whole for the server's ``todo_state``.
992
+ if name == "todo" and function_result is not None:
993
+ payload["todo_result"] = function_result if isinstance(function_result, str) else json.dumps(function_result, default=str)
994
+ # TAL-448: likewise a result's unified ``diff``, so the server's ``edit_diff`` counts cover the whole change.
995
+ diff = _result_diff(function_result)
996
+ if diff is not None:
997
+ payload["result_diff"] = diff
656
998
  cost = _delegation_cost_usd(name, function_result)
657
999
  if cost is not None:
658
1000
  payload["cost_usd"] = cost
@@ -671,15 +1013,13 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
671
1013
 
672
1014
  def on_status(kind, message):
673
1015
  text = str(message or "").strip()
674
- if not text:
675
- return
676
- lower = text.lower()
677
- if "compress" in lower and ("start" in lower or "compressing" in lower):
678
- emit("compressing", {"session_id": session_id, "message": "Compressing context"})
679
- elif "fallback" in lower or "rate limit" in lower or "retry" in lower:
680
- emit("warning", {"type": "fallback", "message": text})
681
- elif "non-retryable error" in lower:
682
- emit("status", {"kind": "terminal_error", "message": text})
1016
+ for event in _status_events(kind, text):
1017
+ if event == "compressing":
1018
+ emit("compressing", {"session_id": session_id, "message": "Compressing context"})
1019
+ elif event == "warning":
1020
+ emit("warning", {"type": "fallback", "message": text})
1021
+ else:
1022
+ emit("status", {"kind": "terminal_error", "message": text})
683
1023
 
684
1024
  def clarify_callback(question, choices, multi_select=False, questions=None):
685
1025
  choices_list = [str(c) for c in (choices or [])]
@@ -759,23 +1099,49 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
759
1099
  fallback_chain = _profile_fallback_chain()
760
1100
  if fallback_chain and _supported(AIAgent, "fallback_model"):
761
1101
  kwargs["fallback_model"] = fallback_chain
1102
+ request_overrides = _main_model_request_overrides(resolved_model, resolved_provider)
1103
+ if request_overrides and _supported(AIAgent, "request_overrides"):
1104
+ kwargs["request_overrides"] = request_overrides
1105
+ # TAL-913: the profile's ``checkpoints`` section, as the Agent's own CLI and gateway turns read it.
1106
+ for name, value in checkpoint_kwargs().items():
1107
+ if _supported(AIAgent, name):
1108
+ kwargs[name] = value
762
1109
  signature = _agent_signature(resolved_model, resolved_provider, runtime, toolsets, str(params.get("profile_home")), kwargs)
763
1110
  agent = None
1111
+
1112
+ def bind(bound) -> None:
1113
+ """Claim the agent for this run under ``_AGENT_CACHE_LOCK``, so an eviction never releases it mid-turn."""
1114
+ run.prior_messages = getattr(bound, "_session_messages", None)
1115
+ run.agent = bound
1116
+
764
1117
  if not session_busy:
765
1118
  with _AGENT_CACHE_LOCK:
766
1119
  cached = _AGENT_CACHE.get(session_id)
767
1120
  if cached and cached[1] == signature:
768
1121
  agent = cached[0]
769
1122
  _AGENT_CACHE.move_to_end(session_id)
1123
+ bind(agent)
770
1124
  if agent is not None:
771
1125
  for name in ("stream_delta_callback", "reasoning_callback", "tool_progress_callback", "clarify_callback", "interim_assistant_callback", "tool_start_callback", "tool_complete_callback", "status_callback"):
772
1126
  if name in kwargs and hasattr(agent, name):
773
1127
  setattr(agent, name, kwargs[name])
774
1128
  else:
775
- agent = AIAgent(**kwargs)
1129
+ # A ``/btw`` side question's throwaway session stays out of state.db.
1130
+ session_db = _acquire_session_db(home) if not params.get("ephemeral") and _supported(AIAgent, "session_db") else None
1131
+ if session_db is not None:
1132
+ kwargs["session_db"] = session_db
1133
+ kwargs["session_id"] = _execution_session_id(session_db, session_id)
1134
+ try:
1135
+ agent = AIAgent(**kwargs)
1136
+ except BaseException:
1137
+ _release_session_db(session_db)
1138
+ raise
1139
+ agent._talaria_session_db = session_db # released with the agent (``_release_agent``)
776
1140
  with _AGENT_CACHE_LOCK:
777
- _AGENT_CACHE[session_id] = (agent, signature)
778
- _AGENT_CACHE.move_to_end(session_id)
1141
+ bind(agent)
1142
+ replaced = _AGENT_CACHE.pop(session_id, None)
1143
+ _AGENT_CACHE[session_id] = (agent, signature, home)
1144
+ _release_agents_locked([replaced] if replaced else [])
779
1145
  _evict_idle_agents_locked()
780
1146
  # Predecessor ``agent.ephemeral_system_prompt``: personality, surface context, progress guidance, and delivery
781
1147
  # hints travel as runtime instructions that are never persisted to history.
@@ -789,8 +1155,7 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
789
1155
  with _APPROVAL_CB_LOCK:
790
1156
  register_gateway_notify(session_id, approval_cb)
791
1157
  _APPROVAL_CB_OWNER[session_id] = stream_id
792
- run.prior_messages = getattr(agent, "_session_messages", None)
793
- run.agent = agent
1158
+ usage_state["last"] = _usage(agent)
794
1159
  compressions_before = int(getattr(getattr(agent, "context_compressor", None), "compression_count", 0) or 0)
795
1160
 
796
1161
  # A cached agent may still carry the interrupt a previous cancel left behind; the Agent keeps a pending
@@ -830,8 +1195,12 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
830
1195
  run_params = set()
831
1196
  if system_message and ("system_message" in run_params or not run_params):
832
1197
  run_kwargs["system_message"] = system_message
833
- if "persist_user_message" in run_params and isinstance(user_message, str):
834
- run_kwargs["persist_user_message"] = user_message
1198
+ # The server's clean user text (no workspace prefix) is what state.db stores; the Agent keeps the sent bytes beside it.
1199
+ persist = params.get("persist_user_message")
1200
+ if not (isinstance(persist, str) and persist.strip()):
1201
+ persist = user_message if isinstance(user_message, str) else None
1202
+ if "persist_user_message" in run_params and persist is not None:
1203
+ run_kwargs["persist_user_message"] = persist
835
1204
  error: str | None = None
836
1205
  result: dict = {}
837
1206
  try:
@@ -868,30 +1237,40 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
868
1237
  error = str(result["final_response"])
869
1238
  status = "cancelled" if cancelled else ("error" if failed or (error and not result.get("messages")) else "completed")
870
1239
  pending_steer = str(result.get("pending_steer") or "") if isinstance(result, dict) else ""
1240
+ messages = [m for m in (result.get("messages") or []) if isinstance(m, dict)]
1241
+ compressed = compressions_after > compressions_before
1242
+ context_messages, context_estimate = _post_compression_context(agent, messages, system_message) if compressed and messages and status == "completed" else (None, None)
871
1243
  return {
872
1244
  "status": status,
873
- "messages": [m for m in (result.get("messages") or []) if isinstance(m, dict)],
1245
+ "messages": messages,
1246
+ "context_messages": context_messages,
1247
+ "post_compression_context_tokens_estimate": context_estimate,
874
1248
  "final_response": str(result.get("final_response") or ""),
875
1249
  "error": error,
876
1250
  "failed": failed,
877
1251
  "partial": partial,
878
1252
  "compression_exhausted": bool(result.get("compression_exhausted")),
879
- "tool_limit_reached": bool(result.get("tool_limit_reached") or result.get("max_iterations_reached")),
1253
+ # The pinned Agent marks an exhausted tool budget only in ``turn_exit_reason`` (``max_iterations_reached(n/n)``).
1254
+ "tool_limit_reached": bool(result.get("tool_limit_reached") or result.get("max_iterations_reached") or str(result.get("turn_exit_reason") or "").startswith("max_iterations_reached")),
1255
+ "max_iterations_summary_request": _max_iterations_summary_request(),
880
1256
  "usage": _usage(agent),
881
1257
  "context": _context_length(agent),
882
1258
  "model": str(getattr(agent, "model", None) or resolved_model),
883
1259
  "provider": str(resolved_provider or ""),
884
- "compressed": compressions_after > compressions_before,
1260
+ "compressed": compressed,
885
1261
  "agent_session_id": str(getattr(agent, "session_id", None) or session_id),
886
1262
  "token_sent": token_sent[0],
887
1263
  "pending_steer": pending_steer,
888
1264
  "live_tool_calls": live_tool_calls,
889
1265
  }
890
1266
  finally:
891
- with _RUNS_LOCK:
892
- _RUNS.pop(stream_id, None)
893
- if _RUNS_BY_SESSION.get(session_id) == stream_id:
894
- _RUNS_BY_SESSION.pop(session_id, None)
1267
+ with _AGENT_CACHE_LOCK:
1268
+ with _RUNS_LOCK:
1269
+ _RUNS.pop(stream_id, None)
1270
+ if _RUNS_BY_SESSION.get(session_id) == stream_id:
1271
+ _RUNS_BY_SESSION.pop(session_id, None)
1272
+ # An agent evicted or replaced while this turn held it is released now that the turn is over.
1273
+ _release_if_dropped_locked(run.agent, home)
895
1274
 
896
1275
 
897
1276
  def _checkpoint_required() -> bool:
@@ -1122,32 +1501,41 @@ def register(registry) -> None:
1122
1501
  run = _RUNS.get(_RUNS_BY_SESSION.get(session_id) or "")
1123
1502
  if not end_session and run is not None and not run.finished.is_set():
1124
1503
  return {"evicted": False}
1125
- evicted = _AGENT_CACHE.pop(session_id, None) is not None
1504
+ entry = _AGENT_CACHE.pop(session_id, None)
1505
+ _release_agents_locked([entry] if entry else [])
1126
1506
  if end_session:
1127
1507
  try:
1128
1508
  from tools.approval import clear_session
1129
1509
  clear_session(session_id)
1130
1510
  except Exception: # noqa: BLE001
1131
1511
  pass
1132
- return {"evicted": evicted}
1512
+ return {"evicted": entry is not None}
1133
1513
 
1134
1514
  @registry.method("chat.commit_memory", requires_agent=False)
1135
1515
  def commit_memory_(ctx: CallContext, params: dict) -> dict:
1136
- """Predecessor ``commit_session_memory``: flush the cached Agent's memory for a session the user just left."""
1516
+ """Predecessor ``commit_session_memory``: flush the cached Agent's memory for a session the user just left.
1517
+ Providers get the session's transcript and run under its profile's ``scoped_home``."""
1137
1518
  session_id = str(params.get("session_id") or "").strip()
1519
+ home = profile_home_param(params)
1138
1520
  with _AGENT_CACHE_LOCK:
1139
1521
  cached = _AGENT_CACHE.get(session_id)
1140
- with _RUNS_LOCK:
1141
- busy = (_RUNS_BY_SESSION.get(session_id) or "") in _RUNS
1142
- agent = cached[0] if cached else None
1143
- commit = getattr(agent, "commit_memory_session", None) if agent is not None else None
1144
- if commit is None or busy:
1145
- return {"committed": False}
1522
+ with _RUNS_LOCK:
1523
+ busy = _DRAINING.is_set() or (_RUNS_BY_SESSION.get(session_id) or "") in _RUNS
1524
+ agent = cached[0] if cached else None
1525
+ commit = getattr(agent, "commit_memory_session", None) if agent is not None else None
1526
+ if commit is None or busy:
1527
+ return {"committed": False}
1528
+ _COMMITTING.append(agent)
1146
1529
  try:
1147
- commit()
1530
+ with scoped_home(home):
1531
+ commit(list(getattr(agent, "_session_messages", None) or []))
1148
1532
  except Exception as exc: # noqa: BLE001
1149
1533
  log.warning("commit_memory_session() failed for session %s: %s", session_id, exc)
1150
1534
  return {"committed": False}
1535
+ finally:
1536
+ with _AGENT_CACHE_LOCK:
1537
+ _COMMITTING.remove(agent)
1538
+ _release_if_dropped_locked(agent, cached[2])
1151
1539
  return {"committed": True}
1152
1540
 
1153
1541
  @registry.method("approval.respond", requires_agent=True)