@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
@@ -17,7 +17,7 @@ log = logging.getLogger("talaria_sidecar.mcp")
17
17
 
18
18
  def status() -> list[dict]:
19
19
  try:
20
- from tools.mcp_tool import get_mcp_status
20
+ from tools.mcp_tool_discovery import get_mcp_status
21
21
 
22
22
  statuses = get_mcp_status()
23
23
  except Exception: # noqa: BLE001
@@ -8,11 +8,12 @@ import json
8
8
  import logging
9
9
  import queue
10
10
  import sqlite3
11
+ import threading
11
12
  import time
12
13
  from contextlib import closing
13
14
  from pathlib import Path
14
15
 
15
- from ..errors import InvalidParams
16
+ from ..errors import InvalidParams, RpcError
16
17
  from ..home import profile_home_param, scoped_home
17
18
  from ..rpc import CallContext
18
19
 
@@ -42,18 +43,80 @@ def _event_row(evt: dict) -> dict:
42
43
  if not row.get("origin_ui_session_id"):
43
44
  row["origin_ui_session_id"] = str(getattr(proc, "origin_ui_session_id", "") or "") or str(getattr(proc, "spawn_session_id", "") or "")
44
45
  try:
45
- row["consumed"] = bool(registry is not None and process_id and row.get("type") != "async_delegation" and registry.is_completion_consumed(process_id))
46
+ # Only the exit is consumed by the agent's wait or poll; a watch notice from the same process still wakes (TAL-724).
47
+ row["consumed"] = bool(registry is not None and process_id and row.get("type") in (None, "completion") and registry.is_completion_consumed(process_id))
46
48
  except Exception: # noqa: BLE001
47
49
  row["consumed"] = False
48
50
  return row
49
51
 
50
52
 
53
+ # Recovered sessions, held so their exit is seen even when an Agent call other than drain prunes them first.
54
+ _RECOVERED: dict[str, object] = {}
55
+ _RECOVERED_LOCK = threading.Lock()
56
+
57
+
58
+ def _reports_recovered_exits(registry) -> bool:
59
+ """Agent 0.21.6 (hermes-agent 7550800d) closes or prunes a recovered session whose PID is gone without queuing a
60
+ completion, since it never collected an exit status. Talaria still wakes the session with an unknown exit code."""
61
+ return callable(getattr(registry, "_detached_host_fate", None)) and callable(getattr(registry, "_publish_finished", None))
62
+
63
+
64
+ def track_recovered(registry) -> None:
65
+ if not _reports_recovered_exits(registry):
66
+ return
67
+ try:
68
+ with registry._lock:
69
+ detached = [s for s in registry._running.values() if getattr(s, "detached", False)]
70
+ except Exception: # noqa: BLE001 - recovery itself succeeded; only the exit report is lost
71
+ log.warning("Failed to track recovered processes", exc_info=True)
72
+ return
73
+ with _RECOVERED_LOCK:
74
+ _RECOVERED.update((s.id, s) for s in detached)
75
+
76
+
77
+ def _publish_recovered_exits(registry) -> None:
78
+ with _RECOVERED_LOCK:
79
+ exited = [s for s in _RECOVERED.values() if s.exited]
80
+ for session in exited:
81
+ del _RECOVERED[session.id]
82
+ for session in exited:
83
+ # A kill (or any path that collected a status) already queued its own completion through _move_to_finished.
84
+ if session.exit_code is not None or getattr(session, "_finish_claimed", False):
85
+ continue
86
+ try:
87
+ # Best effort, as in the Agent: a durable result must not block the live completion.
88
+ from tools.process_registry_results import save_completed_result
89
+
90
+ save_completed_result(session)
91
+ except Exception: # noqa: BLE001
92
+ log.debug("Failed to retain the result of recovered process %s", session.id, exc_info=True)
93
+ try:
94
+ with registry._lock:
95
+ registry._running.pop(session.id, None)
96
+ session.exited_at = session.exited_at or time.time()
97
+ registry._finished[session.id] = session
98
+ registry._publish_finished(session, True)
99
+ except Exception: # noqa: BLE001 - retried on the next drain
100
+ log.warning("Failed to report the exit of recovered process %s", session.id, exc_info=True)
101
+ with _RECOVERED_LOCK:
102
+ _RECOVERED.setdefault(session.id, session)
103
+
104
+
51
105
  def drain(max_events: int) -> list[dict]:
52
106
  """Pop every queued completion event; the server requeues what it does not own."""
53
107
  registry = _registry()
54
108
  completion_queue = getattr(registry, "completion_queue", None) if registry is not None else None
55
109
  if completion_queue is None:
56
110
  return []
111
+ try:
112
+ # A recovered process has no reader thread; only this probe notices its exit and queues its completion (TAL-533).
113
+ with registry._lock:
114
+ detached = [s for s in registry._running.values() if getattr(s, "detached", False)]
115
+ for session in detached:
116
+ registry._refresh_detached_session(session)
117
+ _publish_recovered_exits(registry)
118
+ except Exception: # noqa: BLE001
119
+ log.debug("Failed to reconcile recovered processes", exc_info=True)
57
120
  out = []
58
121
  while len(out) < max_events:
59
122
  try:
@@ -94,6 +157,22 @@ def mark_consumed(process_id: str) -> bool:
94
157
  return False
95
158
 
96
159
 
160
+ def consumed_ids(process_ids: list[str]) -> list[str]:
161
+ """The processes the agent already holds from its own turn: consumed via wait/log, or observed exiting via poll.
162
+ Checked at wakeup delivery, since the completion is usually drained before the agent's wait returns (TAL-532)."""
163
+ registry = _registry()
164
+ if registry is None:
165
+ return []
166
+ out = []
167
+ for process_id in process_ids:
168
+ try:
169
+ if registry.is_completion_consumed(process_id) or process_id in (getattr(registry, "_poll_observed", None) or ()):
170
+ out.append(process_id)
171
+ except Exception: # noqa: BLE001
172
+ log.debug("Completion consumed check failed for %r", process_id, exc_info=True)
173
+ return out
174
+
175
+
97
176
  def _delivery_api():
98
177
  """The Agent's durable delivery ledger (``tools.async_delegation``), or None on an Agent without it."""
99
178
  try:
@@ -387,12 +466,39 @@ def delegation_result(home: Path, session_id: str, delegation_id: str) -> str:
387
466
  return format_notification(event) if event else ""
388
467
 
389
468
 
469
+ def recover(base_home: Path) -> int:
470
+ """TAL-533: re-adopt every profile's checkpointed processes when a sidecar starts, so a process in a profile nobody
471
+ uses yet is watched (and its exit reported) from the start. Entering a home's scope recovers it once; a failed
472
+ recovery fails the call, so the server retries."""
473
+ from .profiles import named_profile_homes
474
+
475
+ named = [home for _, home in named_profile_homes(base_home)]
476
+ failed = []
477
+ for home in [base_home, *named]:
478
+ try:
479
+ with scoped_home(home):
480
+ pass
481
+ except RpcError as exc:
482
+ if exc.data.get("condition") != "agent_incompatible":
483
+ failed.append(str(exc))
484
+ continue
485
+ # An Agent without profile isolation refuses named profiles for good; their processes stay unadopted.
486
+ log.warning("Background process recovery skipped for %s", home, exc_info=True)
487
+ if failed:
488
+ raise RpcError("; ".join(failed))
489
+ return 1 + len(named)
490
+
491
+
390
492
  def register(registry_) -> None:
391
493
  @registry_.method("process.drain")
392
494
  def drain_(ctx: CallContext, params: dict) -> dict:
393
495
  with scoped_home(profile_home_param(params)):
394
496
  return {"events": drain(int(params.get("max_events") or 256))}
395
497
 
498
+ @registry_.method("process.recover")
499
+ def recover_(ctx: CallContext, params: dict) -> dict:
500
+ return {"homes": recover(profile_home_param(params, "base_home"))}
501
+
396
502
  @registry_.method("process.requeue")
397
503
  def requeue_(ctx: CallContext, params: dict) -> dict:
398
504
  events = params.get("events")
@@ -407,6 +513,13 @@ def register(registry_) -> None:
407
513
  raise InvalidParams("process_id is required")
408
514
  return {"ok": mark_consumed(process_id)}
409
515
 
516
+ @registry_.method("process.consumed")
517
+ def consumed_(ctx: CallContext, params: dict) -> dict:
518
+ ids = params.get("process_ids")
519
+ if not isinstance(ids, list) or not all(isinstance(i, str) and i for i in ids):
520
+ raise InvalidParams("process_ids must be a list of process ids")
521
+ return {"consumed": consumed_ids(ids)}
522
+
410
523
  def _delivery_params(params: dict) -> dict:
411
524
  evt = params.get("event")
412
525
  if not isinstance(evt, dict):
@@ -24,6 +24,9 @@ log = logging.getLogger("talaria_sidecar.profiles")
24
24
  _PROFILE_ID_RE = re.compile(r"[a-z0-9][a-z0-9_-]{0,63}")
25
25
  _PROFILE_DIRS = ("skills", "memories", "sessions", "logs", "cron")
26
26
  _CLONE_CONFIG_FILES = ("config.yaml", ".env", "SOUL.md")
27
+ _AVATAR_EXTS = ("png", "jpg", "webp")
28
+ # The Agent's ``INTERNAL_LISTING_SOURCES``: worker sessions are never a profile's conversation.
29
+ _INTERNAL_SOURCES = ("kanban", "tool", "oneshot")
27
30
  _TERMINAL_ENV = {
28
31
  "backend": "TERMINAL_ENV", "env_type": "TERMINAL_ENV", "cwd": "TERMINAL_CWD", "timeout": "TERMINAL_TIMEOUT", "lifetime_seconds": "TERMINAL_LIFETIME_SECONDS",
29
32
  "modal_mode": "TERMINAL_MODAL_MODE", "docker_image": "TERMINAL_DOCKER_IMAGE", "docker_forward_env": "TERMINAL_DOCKER_FORWARD_ENV", "docker_env": "TERMINAL_DOCKER_ENV",
@@ -96,6 +99,58 @@ def _visible(profile_dir: Path) -> bool:
96
99
  return not (isinstance(data, dict) and data.get("visible") is False)
97
100
 
98
101
 
102
+ def _bounded(value, limit: int) -> str:
103
+ """One printable line of at most ``limit`` characters; anything but a string reads as empty."""
104
+ if not isinstance(value, str):
105
+ return ""
106
+ text = " ".join("".join(c if c.isprintable() else " " for c in value).split())
107
+ return text if len(text) <= limit else text[: limit - 1].rstrip() + "\u2026"
108
+
109
+
110
+ def bot_identity(home: Path) -> dict:
111
+ """The presentation-only Bot Mode identity from ``profile.yaml`` and ``assets/``; never the raw ``ui_meta``."""
112
+ meta = _yaml_load(home / "profile.yaml") or {}
113
+ ui_meta = meta.get("ui_meta") if isinstance(meta.get("ui_meta"), dict) else {}
114
+ bots = ui_meta.get("hermes-bots") if isinstance(ui_meta.get("hermes-bots"), dict) else {}
115
+ return {
116
+ # Bot Mode's roster name (``ui_meta['hermes-bots'].title``) wins over the profile's own display name.
117
+ "display_name": _bounded(bots.get("title"), 80) or _bounded(meta.get("display_name"), 80),
118
+ "description": _bounded(meta.get("description"), 280), "has_avatar": any((home / "assets" / f"avatar.{ext}").is_file() for ext in _AVATAR_EXTS),
119
+ }
120
+
121
+
122
+ def canonical_bot_chat(home: Path) -> dict | None:
123
+ """The profile's canonical Bot Chat: its session titled exactly ``Bot Chat`` (Bot Mode's registry) and live
124
+ compression tip, read through a read-only handle. Missing, unreadable, archived, worker-owned, or an Agent
125
+ without Bot Mode answers None; the gateway's resurrection of accidental archives is a write and stays there."""
126
+ db_path = home / "state.db"
127
+ if not db_path.is_file():
128
+ return None
129
+ try:
130
+ from hermes_state import SessionDB
131
+
132
+ title = getattr(SessionDB, "CANONICAL_BOT_CHAT_TITLE", None)
133
+ if not title:
134
+ return None
135
+ db = SessionDB(db_path=db_path, read_only=True)
136
+ except Exception: # noqa: BLE001
137
+ return None
138
+ try:
139
+ row = db.get_session_by_title(title)
140
+ if not row or row.get("title") != title or row.get("archived") or str(row.get("source") or "").strip().lower() in _INTERNAL_SOURCES:
141
+ return None
142
+ session_id = str(row["id"])
143
+ return {"session_id": session_id, "tip_session_id": str(db.get_compression_tip(session_id) or session_id)}
144
+ except Exception: # noqa: BLE001
145
+ log.debug("canonical Bot Chat lookup failed for %s", db_path, exc_info=True)
146
+ return None
147
+ finally:
148
+ try:
149
+ db.close()
150
+ except Exception: # noqa: BLE001
151
+ pass
152
+
153
+
99
154
  def _row(home: Path, name: str, is_default: bool) -> dict:
100
155
  from hermes_cli.profiles import _check_gateway_running, _read_config_model
101
156
 
@@ -111,21 +166,36 @@ def _row(home: Path, name: str, is_default: bool) -> dict:
111
166
  return {
112
167
  "name": name, "path": str(home), "is_default": is_default, "gateway_running": gateway_running, "model": model, "provider": provider,
113
168
  "has_env": (home / ".env").exists(), "visible": _visible(home), "skill_count": enabled, "enabled_skills": enabled, "total_skills": total,
169
+ **bot_identity(home), "canonical_session": canonical_bot_chat(home),
114
170
  }
115
171
 
116
172
 
117
- def list_profiles(base_home: Path) -> list[dict]:
118
- """Profile rows for the base Hermes home: the root profile plus ``profiles/*``."""
173
+ def named_profile_homes(base_home: Path) -> list[tuple[str, Path]]:
174
+ """(name, home) for the ``profiles/*`` directories named like profiles, skipping any that resolve outside the resolved
175
+ profiles root (a symlink leading elsewhere is never read as a profile; TAL-579). A contained symlink answers the
176
+ target it was checked against, so retargeting the link afterwards cannot redirect the caller."""
119
177
  from hermes_cli.profiles import _PROFILE_ID_RE as UPSTREAM_ID_RE
120
178
 
179
+ profiles_root = base_home / "profiles"
180
+ if not profiles_root.is_dir():
181
+ return []
182
+ root = profiles_root.resolve()
183
+ homes = []
184
+ for entry in sorted(profiles_root.iterdir()):
185
+ if not (entry.is_dir() and UPSTREAM_ID_RE.match(entry.name)):
186
+ continue
187
+ real = entry.resolve()
188
+ if real.is_relative_to(root):
189
+ homes.append((entry.name, real if entry.is_symlink() else entry))
190
+ return homes
191
+
192
+
193
+ def list_profiles(base_home: Path) -> list[dict]:
194
+ """Profile rows for the base Hermes home: the root profile plus ``profiles/*``."""
121
195
  rows = []
122
196
  if base_home.is_dir():
123
197
  rows.append(_row(base_home, "default", True))
124
- profiles_root = base_home / "profiles"
125
- if profiles_root.is_dir():
126
- for entry in sorted(profiles_root.iterdir()):
127
- if entry.is_dir() and UPSTREAM_ID_RE.match(entry.name):
128
- rows.append(_row(entry, entry.name, False))
198
+ rows.extend(_row(home, name, False) for name, home in named_profile_homes(base_home))
129
199
  return rows
130
200
 
131
201
 
@@ -241,7 +311,13 @@ def register(registry) -> None:
241
311
 
242
312
  @registry.method("profiles.delete")
243
313
  def delete(ctx: CallContext, params: dict) -> dict:
244
- delete_profile(profile_home_param(params, "base_home"), str(params.get("name") or "").strip())
314
+ base_home, name = profile_home_param(params, "base_home"), str(params.get("name") or "").strip()
315
+ validate_name(name)
316
+ # Idle cached agents end their memory sessions while the profile's home still exists.
317
+ from .chat import release_profile_agents
318
+
319
+ release_profile_agents(base_home / "profiles" / name)
320
+ delete_profile(base_home, name)
245
321
  return {"ok": True}
246
322
 
247
323
  @registry.method("profiles.runtime_env")
@@ -97,8 +97,10 @@ def _user_plugin_profiles() -> dict[int, tuple[Any, Path]]:
97
97
  def _load_plugin(root: Path) -> None:
98
98
  """Load the scoped profile's installed plugin that the process-wide discovery (run under the launch profile) skipped.
99
99
 
100
- Uses the Agent's own loader, as ``hermes plugins dev`` does, once per directory. A plugin never displaces a provider
101
- that is already registered, so one profile's plugin cannot change what another profile's provider id runs.
100
+ Uses the Agent's own loader, as ``hermes plugins dev`` does, once per directory. On an Agent with one process-wide
101
+ provider registry, a plugin never displaces a provider that is already registered, so one profile's plugin cannot
102
+ change what another profile's provider id runs. An Agent with per-home provider layers registers it in this home's
103
+ layer instead, so the same id is that profile's own provider.
102
104
  """
103
105
  import providers
104
106
 
@@ -121,12 +123,12 @@ def _load_plugin(root: Path) -> None:
121
123
  def installed_plugin_profiles() -> list[tuple[str, Any]]:
122
124
  """``(manifest name, profile | None)`` for each enabled model-provider plugin installed in the scoped home.
123
125
 
124
- Bundled providers are built-ins, not plugins. The Agent keeps one provider registry per process, so a profile
125
- belongs to this home only when the plugin module that registered it lives in one of this home's plugin
126
- directories; a plugin the launch discovery skipped is loaded here. ``None`` means the plugin could not be loaded:
127
- it failed to import, its provider id is taken by another plugin, or another profile's plugin has the same directory
128
- name. ponytail: ownership is read from the plugin module's globals, so a plugin that registers an unbound inline
129
- profile also reads as not loaded; track registrations in the Agent if one ever does.
126
+ Bundled providers are built-ins, not plugins. A profile belongs to this home only when the plugin module that
127
+ registered it lives in one of this home's plugin directories; a plugin the launch discovery skipped is loaded here.
128
+ ``None`` means the plugin could not be loaded: it failed to import, another plugin took its provider id (only on an
129
+ Agent with one process-wide provider registry rather than per-home layers), or another profile's plugin has the same
130
+ directory name. ponytail: ownership is read from the plugin module's globals, so a plugin that registers an unbound
131
+ inline profile also reads as not loaded; track registrations in the Agent if one ever does.
130
132
  """
131
133
  try:
132
134
  from hermes_cli.plugins_discovery import _get_disabled_plugins, _get_enabled_plugins, collect_directory_manifests, gate_manifest
@@ -168,8 +170,25 @@ def _other_profile_plugin(provider_id: str) -> bool:
168
170
  return not any(p is profile for _name, p in installed_plugin_profiles())
169
171
 
170
172
 
173
+ def _agent_knows_provider(name: str) -> bool:
174
+ """Whether the Agent's auth layer can resolve this provider id at all.
175
+
176
+ Agent 0.21.3 resolves providers from a ``PROVIDER_REGISTRY`` snapshot of its import-time plugin discovery, so a
177
+ named profile's plugin loaded here on demand is unknown to it and cannot run. Registering it there would expose it
178
+ to every profile, so it reads as unavailable instead. Newer Agents look providers up live (``_registry_lookup``).
179
+ """
180
+ try:
181
+ import hermes_cli.auth as auth
182
+ except Exception: # noqa: BLE001 - auth_status reports an unavailable auth layer
183
+ return True
184
+ registry = getattr(auth, "PROVIDER_REGISTRY", None)
185
+ return hasattr(auth, "_registry_lookup") or not isinstance(registry, dict) or name.strip().lower() in registry
186
+
187
+
171
188
  def _setup_state(profile: Any) -> str:
172
189
  """The Agent's own setup verdict for a plugin provider, without spawning its CLI or reading its credentials."""
190
+ if not _agent_knows_provider(str(profile.name)):
191
+ return "unavailable"
173
192
  try:
174
193
  status = auth_status(profile.name)
175
194
  except RpcError:
@@ -66,5 +66,8 @@ def register(registry) -> None:
66
66
  code = params.get("exit_code", 0)
67
67
  if not isinstance(code, int):
68
68
  raise InvalidParams("exit_code must be an integer")
69
+ from .chat import drain_agents
70
+
71
+ drain_agents()
69
72
  ctx.server.request_shutdown(exit_code=code)
70
73
  return {"ok": True}
@@ -95,7 +95,8 @@ def sync_session_usage(home: Path, session_id: str, *, input_tokens=0, output_to
95
95
  _close(db)
96
96
 
97
97
 
98
- def sync_session_title(home: Path, session_id: str, title: str) -> bool:
98
+ def sync_session_title(home: Path, session_id: str, title: str, *, manual: bool = False) -> bool:
99
+ """A manual (user-requested) title lands with ``user`` provenance; an auto title is ``llm`` and never replaces a user name."""
99
100
  if not title:
100
101
  return False
101
102
  db = _session_db(home)
@@ -104,15 +105,38 @@ def sync_session_title(home: Path, session_id: str, title: str) -> bool:
104
105
  try:
105
106
  db.ensure_session(session_id=session_id, source="webui")
106
107
  source = getattr(db, "TITLE_SOURCE_LLM", "llm")
108
+
109
+ def write(value: str) -> None:
110
+ if manual:
111
+ db.set_session_title(session_id, value)
112
+ else:
113
+ db.set_auto_title(session_id, value, source=source)
114
+
107
115
  try:
108
- db.set_auto_title(session_id, title, source=source)
116
+ write(title)
109
117
  except ValueError:
110
118
  alt = db.get_next_title_in_lineage(title)
111
119
  if alt and alt != title:
112
- db.set_auto_title(session_id, alt, source=source)
120
+ write(alt)
121
+ return True
122
+ except Exception: # noqa: BLE001
123
+ log.debug("Failed to sync session title", exc_info=True)
124
+ return False
125
+ finally:
126
+ _close(db)
127
+
128
+
129
+ def append_message(home: Path, session_id: str, *, role: str, content: str, tool_name=None, timestamp=None) -> bool:
130
+ """TAL-258 (predecessor ``_persist_handoff_summary_to_state_db``): one row through ``SessionDB.append_message``,
131
+ which also bumps the session's ``message_count``. False without a state.db or when the write fails."""
132
+ db = _session_db(home)
133
+ if not db:
134
+ return False
135
+ try:
136
+ db.append_message(session_id, role, content=content, tool_name=tool_name or None, timestamp=timestamp)
113
137
  return True
114
138
  except Exception: # noqa: BLE001
115
- log.debug("Failed to sync auto title", exc_info=True)
139
+ log.warning("Failed to append a %s row to state.db for %s", role, session_id, exc_info=True)
116
140
  return False
117
141
  finally:
118
142
  _close(db)
@@ -408,7 +432,17 @@ def register(registry) -> None:
408
432
 
409
433
  @registry.method("state_db.sync_title")
410
434
  def title(ctx: CallContext, params: dict) -> dict:
411
- return {"ok": sync_session_title(profile_home_param(params), _session_id(params), str(params.get("title") or ""))}
435
+ return {"ok": sync_session_title(profile_home_param(params), _session_id(params), str(params.get("title") or ""), manual=params.get("manual") is True)}
436
+
437
+ @registry.method("state_db.append_message")
438
+ def append(ctx: CallContext, params: dict) -> dict:
439
+ role = str(params.get("role") or "").strip()
440
+ if not role:
441
+ raise InvalidParams("role is required")
442
+ content = params.get("content")
443
+ if not isinstance(content, str):
444
+ raise InvalidParams("content must be a string")
445
+ return {"ok": append_message(profile_home_param(params), _session_id(params), role=role, content=content, tool_name=params.get("tool_name"), timestamp=params.get("timestamp"))}
412
446
 
413
447
  @registry.method("state_db.delete_cli_session")
414
448
  def delete(ctx: CallContext, params: dict) -> dict:
@@ -16,7 +16,7 @@ def image_mode(*, provider: str, model: str, cfg: dict | None, requested_provide
16
16
  try:
17
17
  from agent.image_routing import decide_image_input_mode
18
18
  except Exception: # noqa: BLE001
19
- return {"mode": "text", "reason": "image routing unavailable"}
19
+ return {"mode": "text", "reason": "image routing unavailable", "supports_vision": None}
20
20
  try:
21
21
  decision = decide_image_input_mode(provider, model, cfg, requested_provider=requested_provider)
22
22
  except TypeError:
@@ -34,6 +34,18 @@ def image_mode(*, provider: str, model: str, cfg: dict | None, requested_provide
34
34
  return {"mode": str(decision or "text"), "reason": "", "supports_vision": supports_vision}
35
35
 
36
36
 
37
+ def _profile_config() -> dict | None:
38
+ """The scoped profile's ``config.yaml``; None when the Agent cannot load it."""
39
+ try:
40
+ from hermes_cli.config import load_config
41
+
42
+ cfg = load_config()
43
+ except Exception: # noqa: BLE001
44
+ log.debug("image mode config load failed", exc_info=True)
45
+ return None
46
+ return cfg if isinstance(cfg, dict) else None
47
+
48
+
37
49
  def portal_tags() -> dict:
38
50
  try:
39
51
  from agent.portal_tags import conversation_tag, hermes_client_tag, nous_portal_tags
@@ -58,6 +70,10 @@ def register(registry) -> None:
58
70
  def image_mode_(ctx: CallContext, params: dict) -> dict:
59
71
  cfg = params.get("cfg") if isinstance(params.get("cfg"), dict) else None
60
72
  with scoped_home(profile_home_param(params)):
73
+ # TAL-545: without an explicit cfg the profile's config.yaml decides, so its vision overrides
74
+ # (``agent.image_input_mode``, ``supports_vision``) apply as they do for the Agent's own turn.
75
+ if cfg is None:
76
+ cfg = _profile_config()
61
77
  return image_mode(provider=str(params.get("provider") or ""), model=str(params.get("model") or ""), cfg=cfg, requested_provider=str(params.get("requested_provider") or ""))
62
78
 
63
79
  @registry.method("text.portal_tags")