@maudecode/talaria-web 1.17.2 → 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 (387) 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 +53 -6
  7. package/dist/api/automation-raw.js.map +1 -1
  8. package/dist/api/automation-router.d.ts +26 -1
  9. package/dist/api/automation-router.js +16 -2
  10. package/dist/api/automation-router.js.map +1 -1
  11. package/dist/api/chat-router.d.ts +649 -15
  12. package/dist/api/chat-router.js +52 -50
  13. package/dist/api/chat-router.js.map +1 -1
  14. package/dist/api/git-router.d.ts +35 -1
  15. package/dist/api/git-router.js +18 -19
  16. package/dist/api/git-router.js.map +1 -1
  17. package/dist/api/raw-routes.d.ts +8 -0
  18. package/dist/api/raw-routes.js +247 -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 +1492 -115
  24. package/dist/api/sessions-router.js +134 -8
  25. package/dist/api/sessions-router.js.map +1 -1
  26. package/dist/api/settings-router.d.ts +31 -0
  27. package/dist/api/settings-router.js +8 -3
  28. package/dist/api/settings-router.js.map +1 -1
  29. package/dist/api/sse-routes.d.ts +5 -0
  30. package/dist/api/sse-routes.js +50 -3
  31. package/dist/api/sse-routes.js.map +1 -1
  32. package/dist/api/tools-raw.js +6 -23
  33. package/dist/api/tools-raw.js.map +1 -1
  34. package/dist/api/tools-router.d.ts +356 -15
  35. package/dist/api/tools-router.js +148 -19
  36. package/dist/api/tools-router.js.map +1 -1
  37. package/dist/app.js +35 -15
  38. package/dist/app.js.map +1 -1
  39. package/dist/auth/gate.js +5 -2
  40. package/dist/auth/gate.js.map +1 -1
  41. package/dist/auth/oidc.d.ts +5 -2
  42. package/dist/auth/oidc.js +8 -7
  43. package/dist/auth/oidc.js.map +1 -1
  44. package/dist/bin/talaria-web.js +1 -1
  45. package/dist/bin/talaria-web.js.map +1 -1
  46. package/dist/cli/dotenv.js +1 -0
  47. package/dist/cli/dotenv.js.map +1 -1
  48. package/dist/config/agent-config.js +3 -2
  49. package/dist/config/agent-config.js.map +1 -1
  50. package/dist/config.d.ts +2 -0
  51. package/dist/config.js +5 -1
  52. package/dist/config.js.map +1 -1
  53. package/dist/http/context.d.ts +17 -3
  54. package/dist/http/context.js +31 -4
  55. package/dist/http/context.js.map +1 -1
  56. package/dist/http/csp.d.ts +2 -1
  57. package/dist/http/csp.js +3 -2
  58. package/dist/http/csp.js.map +1 -1
  59. package/dist/profiles/profiles.d.ts +10 -0
  60. package/dist/profiles/profiles.js +40 -2
  61. package/dist/profiles/profiles.js.map +1 -1
  62. package/dist/providers/catalog.d.ts +6 -2
  63. package/dist/providers/catalog.js +30 -11
  64. package/dist/providers/catalog.js.map +1 -1
  65. package/dist/raster-image.d.ts +7 -0
  66. package/dist/raster-image.js +267 -0
  67. package/dist/raster-image.js.map +1 -0
  68. package/dist/redact.js +327 -40
  69. package/dist/redact.js.map +1 -1
  70. package/dist/runtime.js +89 -23
  71. package/dist/runtime.js.map +1 -1
  72. package/dist/server.js +5 -0
  73. package/dist/server.js.map +1 -1
  74. package/dist/sessions/background-tasks.d.ts +2 -9
  75. package/dist/sessions/background-tasks.js +2 -12
  76. package/dist/sessions/background-tasks.js.map +1 -1
  77. package/dist/sessions/cli-sessions.d.ts +15 -4
  78. package/dist/sessions/cli-sessions.js +120 -29
  79. package/dist/sessions/cli-sessions.js.map +1 -1
  80. package/dist/sessions/completions.d.ts +48 -8
  81. package/dist/sessions/completions.js +222 -26
  82. package/dist/sessions/completions.js.map +1 -1
  83. package/dist/sessions/export.d.ts +5 -0
  84. package/dist/sessions/export.js +30 -0
  85. package/dist/sessions/export.js.map +1 -1
  86. package/dist/sessions/gateway-watcher.d.ts +7 -3
  87. package/dist/sessions/gateway-watcher.js +31 -19
  88. package/dist/sessions/gateway-watcher.js.map +1 -1
  89. package/dist/sessions/handoff.d.ts +32 -0
  90. package/dist/sessions/handoff.js +114 -0
  91. package/dist/sessions/handoff.js.map +1 -0
  92. package/dist/sessions/list.d.ts +7 -1
  93. package/dist/sessions/list.js +13 -5
  94. package/dist/sessions/list.js.map +1 -1
  95. package/dist/sessions/merge.d.ts +2 -4
  96. package/dist/sessions/merge.js +8 -15
  97. package/dist/sessions/merge.js.map +1 -1
  98. package/dist/sessions/recovery.d.ts +22 -0
  99. package/dist/sessions/recovery.js +506 -0
  100. package/dist/sessions/recovery.js.map +1 -0
  101. package/dist/sessions/relay.d.ts +21 -2
  102. package/dist/sessions/relay.js +156 -11
  103. package/dist/sessions/relay.js.map +1 -1
  104. package/dist/sessions/service.d.ts +108 -6
  105. package/dist/sessions/service.js +403 -29
  106. package/dist/sessions/service.js.map +1 -1
  107. package/dist/sessions/session.d.ts +6 -0
  108. package/dist/sessions/session.js +40 -2
  109. package/dist/sessions/session.js.map +1 -1
  110. package/dist/sessions/state-db-worker.d.ts +1 -0
  111. package/dist/sessions/state-db-worker.js +33 -0
  112. package/dist/sessions/state-db-worker.js.map +1 -0
  113. package/dist/sessions/state-db.d.ts +61 -0
  114. package/dist/sessions/state-db.js +291 -0
  115. package/dist/sessions/state-db.js.map +1 -1
  116. package/dist/sessions/store.d.ts +7 -0
  117. package/dist/sessions/store.js +31 -22
  118. package/dist/sessions/store.js.map +1 -1
  119. package/dist/sessions/titles.d.ts +2 -0
  120. package/dist/sessions/titles.js +4 -0
  121. package/dist/sessions/titles.js.map +1 -1
  122. package/dist/sessions/tool-display.d.ts +3 -3
  123. package/dist/sessions/tool-display.js +11 -37
  124. package/dist/sessions/tool-display.js.map +1 -1
  125. package/dist/sessions/turn-context.d.ts +1 -1
  126. package/dist/sessions/turn-context.js +11 -7
  127. package/dist/sessions/turn-context.js.map +1 -1
  128. package/dist/sessions/turn.d.ts +37 -2
  129. package/dist/sessions/turn.js +182 -49
  130. package/dist/sessions/turn.js.map +1 -1
  131. package/dist/sessions/wakeup-pause.d.ts +59 -0
  132. package/dist/sessions/wakeup-pause.js +181 -0
  133. package/dist/sessions/wakeup-pause.js.map +1 -0
  134. package/dist/settings.js +5 -7
  135. package/dist/settings.js.map +1 -1
  136. package/dist/sidecar/fake.d.ts +2 -2
  137. package/dist/sidecar/fake.js.map +1 -1
  138. package/dist/startup.d.ts +13 -5
  139. package/dist/startup.js +48 -0
  140. package/dist/startup.js.map +1 -1
  141. package/dist/test/ts-worker-hooks.d.ts +1 -0
  142. package/dist/test/ts-worker-hooks.js +24 -0
  143. package/dist/test/ts-worker-hooks.js.map +1 -0
  144. package/dist/text/diff.d.ts +9 -14
  145. package/dist/text/diff.js +74 -108
  146. package/dist/text/diff.js.map +1 -1
  147. package/dist/tools/crons.js +45 -8
  148. package/dist/tools/crons.js.map +1 -1
  149. package/dist/tools/dashboard-plugins.d.ts +53 -0
  150. package/dist/tools/dashboard-plugins.js +188 -0
  151. package/dist/tools/dashboard-plugins.js.map +1 -0
  152. package/dist/tools/extensions.d.ts +13 -1
  153. package/dist/tools/extensions.js +132 -60
  154. package/dist/tools/extensions.js.map +1 -1
  155. package/dist/tools/health.d.ts +32 -0
  156. package/dist/tools/health.js +111 -19
  157. package/dist/tools/health.js.map +1 -1
  158. package/dist/tools/mcp.d.ts +13 -0
  159. package/dist/tools/mcp.js +88 -0
  160. package/dist/tools/mcp.js.map +1 -1
  161. package/dist/tools/project-os.d.ts +11 -0
  162. package/dist/tools/project-os.js +267 -0
  163. package/dist/tools/project-os.js.map +1 -0
  164. package/dist/tools/skills.d.ts +5 -1
  165. package/dist/tools/skills.js +5 -3
  166. package/dist/tools/skills.js.map +1 -1
  167. package/dist/tools/updates.d.ts +27 -9
  168. package/dist/tools/updates.js +163 -51
  169. package/dist/tools/updates.js.map +1 -1
  170. package/dist/tools/wiki.d.ts +68 -0
  171. package/dist/tools/wiki.js +413 -0
  172. package/dist/tools/wiki.js.map +1 -0
  173. package/dist/workspace/escape.d.ts +61 -0
  174. package/dist/workspace/escape.js +149 -0
  175. package/dist/workspace/escape.js.map +1 -0
  176. package/dist/workspace/extract.d.ts +22 -0
  177. package/dist/workspace/extract.js +308 -0
  178. package/dist/workspace/extract.js.map +1 -0
  179. package/dist/workspace/fs.d.ts +12 -0
  180. package/dist/workspace/fs.js +92 -23
  181. package/dist/workspace/fs.js.map +1 -1
  182. package/dist/workspace/git.d.ts +18 -0
  183. package/dist/workspace/git.js +38 -25
  184. package/dist/workspace/git.js.map +1 -1
  185. package/dist/workspace/media.d.ts +4 -4
  186. package/dist/workspace/media.js +16 -45
  187. package/dist/workspace/media.js.map +1 -1
  188. package/dist/workspace/rollback.d.ts +34 -20
  189. package/dist/workspace/rollback.js +171 -265
  190. package/dist/workspace/rollback.js.map +1 -1
  191. package/dist/workspace/unzip.js +4 -1
  192. package/dist/workspace/unzip.js.map +1 -1
  193. package/dist/workspace/upload.d.ts +7 -4
  194. package/dist/workspace/upload.js +5 -2
  195. package/dist/workspace/upload.js.map +1 -1
  196. package/dist/workspace/worktrees.d.ts +2 -1
  197. package/dist/workspace/worktrees.js +14 -15
  198. package/dist/workspace/worktrees.js.map +1 -1
  199. package/package.json +4 -2
  200. package/sidecar/agent_dependency.json +4 -4
  201. package/sidecar/scripts/replay_sidecar.py +24 -0
  202. package/sidecar/talaria_sidecar/__init__.py +1 -1
  203. package/sidecar/talaria_sidecar/home.py +13 -2
  204. package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
  205. package/sidecar/talaria_sidecar/methods/aux.py +39 -5
  206. package/sidecar/talaria_sidecar/methods/chat.py +9 -5
  207. package/sidecar/talaria_sidecar/methods/checkpoints.py +89 -0
  208. package/sidecar/talaria_sidecar/methods/commands.py +81 -0
  209. package/sidecar/talaria_sidecar/methods/cron.py +29 -2
  210. package/sidecar/talaria_sidecar/methods/gateway.py +32 -15
  211. package/sidecar/talaria_sidecar/methods/kanban.py +28 -7
  212. package/sidecar/talaria_sidecar/methods/mcp.py +1 -1
  213. package/sidecar/talaria_sidecar/methods/process.py +58 -4
  214. package/sidecar/talaria_sidecar/methods/profiles.py +77 -7
  215. package/sidecar/talaria_sidecar/methods/providers.py +27 -8
  216. package/sidecar/talaria_sidecar/methods/state_db.py +39 -5
  217. package/sidecar/talaria_sidecar/runtime.py +17 -1
  218. package/static/dist/FILES.txt +127 -126
  219. package/static/dist/assets/AppShell-g3bkgnvt.js +1 -0
  220. package/static/dist/assets/ChatPage-oo7bdan6.js +25 -0
  221. package/static/dist/assets/Dialog-g355qxhp.js +1 -0
  222. package/static/dist/assets/ExtensionRoute-i2y9fvjm.js +1 -0
  223. package/static/dist/assets/HubRoute-lmkrbw47.js +2 -0
  224. package/static/dist/assets/{Markdown-mdwlvj3w.js → Markdown-j4kut4j2.js} +114 -114
  225. package/static/dist/assets/{RightPanel-ftvrjlcp.js → RightPanel-ohlsdzfl.js} +1 -1
  226. package/static/dist/assets/Select-kddq75qc.js +1 -0
  227. package/static/dist/assets/SessionListPanel-lu3oy0hw.js +1 -0
  228. package/static/dist/assets/{SettingsLayout-jonjefmj.js → SettingsLayout-o9bv4mgb.js} +1 -1
  229. package/static/dist/assets/{States-nmejrwrm.js → States-eq6fe29m.js} +1 -1
  230. package/static/dist/assets/Toaster-pc247u9f.js +1 -0
  231. package/static/dist/assets/{_app-gdidzd94.js → _app-f8n1i0m1.js} +1 -1
  232. package/static/dist/assets/{_app.ext._extensionId-opyh9y0v.js → _app.ext._extensionId-gfbzpoou.js} +1 -1
  233. package/static/dist/assets/_app.index-bzzv80hk.js +1 -0
  234. package/static/dist/assets/_app.insights-dzixbryi.js +1 -0
  235. package/static/dist/assets/_app.kanban-c6hep2us.js +1 -0
  236. package/static/dist/assets/_app.logs-i69qds98.js +1 -0
  237. package/static/dist/assets/_app.memory-kl4vf0gd.js +1 -0
  238. package/static/dist/assets/_app.profiles-b8zinmyp.js +1 -0
  239. package/static/dist/assets/{_app.session._sessionId-bfd4h3id.js → _app.session._sessionId-fn1bhde6.js} +1 -1
  240. package/static/dist/assets/_app.settings-cg8plwmf.js +1 -0
  241. package/static/dist/assets/_app.settings._section-bkwbi9zn.js +3 -0
  242. package/static/dist/assets/_app.skills-cs47sbvs.js +1 -0
  243. package/static/dist/assets/_app.tasks-dwvk64tc.js +1 -0
  244. package/static/dist/assets/_app.todos-c8xjm8p3.js +1 -0
  245. package/static/dist/assets/_app.workspaces-ehvd1gds.js +1 -0
  246. package/static/dist/assets/{abnfDiagram-VCTEODGH-b2x1lqot.js → abnfDiagram-VCTEODGH-c8kdixke.js} +1 -1
  247. package/static/dist/assets/architecture-7GRP2DOG-6crvnylg.js +1 -0
  248. package/static/dist/assets/{architectureDiagram-5GKGNRK7-h38o5l7b.js → architectureDiagram-5GKGNRK7-cku06x1p.js} +1 -1
  249. package/static/dist/assets/{back-bemaer09.js → back-h7aun7ew.js} +1 -1
  250. package/static/dist/assets/{blockDiagram-I7D4REHJ-bqquxv2u.js → blockDiagram-I7D4REHJ-jzthp1wa.js} +1 -1
  251. package/static/dist/assets/{c4Diagram-7LVT6UL2-f3fb8w6z.js → c4Diagram-7LVT6UL2-mwr67mt8.js} +1 -1
  252. package/static/dist/assets/{cancel-drd0g6d9.js → cancel-fhi0vcnu.js} +1 -1
  253. package/static/dist/assets/channel-b2zo73z1.js +1 -0
  254. package/static/dist/assets/{chunk-4HAMMTFA-bzfyyzp3.js → chunk-4HAMMTFA-gpq1y228.js} +1 -1
  255. package/static/dist/assets/{chunk-75Z2AOVW-ly99rfx5.js → chunk-75Z2AOVW-ktje58c4.js} +1 -1
  256. package/static/dist/assets/{chunk-DU6HZSFF-bg6ecfqf.js → chunk-DU6HZSFF-ofajdixh.js} +3 -3
  257. package/static/dist/assets/{chunk-F27PBJKO-e8k7mmqw.js → chunk-F27PBJKO-cmev85ti.js} +1 -1
  258. package/static/dist/assets/{chunk-GMAD6QVW-cmalnoaj.js → chunk-GMAD6QVW-htjgt22y.js} +1 -1
  259. package/static/dist/assets/{chunk-GVQU2GXP-gmif5o01.js → chunk-GVQU2GXP-qndvoxl7.js} +1 -1
  260. package/static/dist/assets/{chunk-IMKFNOWR-f1nwwh6u.js → chunk-IMKFNOWR-e5bkd5q6.js} +1 -1
  261. package/static/dist/assets/{chunk-L3NEJ4N5-oktfh5v7.js → chunk-L3NEJ4N5-mj1155r6.js} +1 -1
  262. package/static/dist/assets/{chunk-OSK3NFVY-b1sdx96z.js → chunk-OSK3NFVY-e7gkikcw.js} +1 -1
  263. package/static/dist/assets/{chunk-P2QGCYS3-fhu4nzxt.js → chunk-P2QGCYS3-f7t54xfm.js} +1 -1
  264. package/static/dist/assets/{chunk-POPQ4Y6H-em31rnef.js → chunk-POPQ4Y6H-i927v9oa.js} +1 -1
  265. package/static/dist/assets/{chunk-PWAF6VOD-bxhhyftw.js → chunk-PWAF6VOD-m84lmnyt.js} +1 -1
  266. package/static/dist/assets/{chunk-SHT3W25Y-fmrvwcav.js → chunk-SHT3W25Y-mi0tlcu9.js} +1 -1
  267. package/static/dist/assets/{chunk-SVP7TREG-lymukl2j.js → chunk-SVP7TREG-dnau702j.js} +1 -1
  268. package/static/dist/assets/{chunk-TICWLB2K-lmza6k23.js → chunk-TICWLB2K-edem1gkg.js} +1 -1
  269. package/static/dist/assets/classDiagram-ZZMXUADV-oe99st8x.js +1 -0
  270. package/static/dist/assets/classDiagram-v2-VYDZK3BY-oe99st8x.js +1 -0
  271. package/static/dist/assets/{copied-ima3pysb.js → copied-hcplv46p.js} +1 -1
  272. package/static/dist/assets/{copy-fw5b1pc6.js → copy-magtofyp.js} +1 -1
  273. package/static/dist/assets/{cynefin-OW5HDTMX-czlhbyhs.js → cynefin-OW5HDTMX-siqlulkl.js} +1 -1
  274. package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-gq7mvswb.js → cynefinDiagram-5FMLGOSQ-i648tah9.js} +1 -1
  275. package/static/dist/assets/{dagre-GXQ25YYZ-cqdbf7dr.js → dagre-GXQ25YYZ-mwn6uc17.js} +1 -1
  276. package/static/dist/assets/{diagram-S7CK7UJ4-o6arxa58.js → diagram-S7CK7UJ4-l8abxaes.js} +1 -1
  277. package/static/dist/assets/{diagram-UQ7AKVKN-nhnd68tz.js → diagram-UQ7AKVKN-mekq7zgq.js} +1 -1
  278. package/static/dist/assets/{diagram-VSXAHHWV-dk2sclzn.js → diagram-VSXAHHWV-fnky6lvc.js} +1 -1
  279. package/static/dist/assets/{diagram-VX7I27RA-n3k5nyx3.js → diagram-VX7I27RA-xe6r5qfw.js} +1 -1
  280. package/static/dist/assets/{diagram-Z3DM3KII-m6q9cwdh.js → diagram-Z3DM3KII-nq07dg7k.js} +1 -1
  281. package/static/dist/assets/{ebnfDiagram-PWID7BFC-lsa5641p.js → ebnfDiagram-PWID7BFC-c62ipm3g.js} +1 -1
  282. package/static/dist/assets/endpoints-klbxod4t.js +64 -0
  283. package/static/dist/assets/{erDiagram-RLTQ6QDP-fo2ckbe6.js → erDiagram-RLTQ6QDP-h39kmnzy.js} +1 -1
  284. package/static/dist/assets/eventmodeling-NTZA5JFV-obfede4u.js +1 -0
  285. package/static/dist/assets/flowDiagram-HODETNUW-kdpsp1je.js +1 -0
  286. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-nzqz6x9g.js → ganttDiagram-EL5Y4UJY-ngbsyp37.js} +1 -1
  287. package/static/dist/assets/{gitGraph-4MIJSDKK-gukvcxe6.js → gitGraph-4MIJSDKK-fr2i7caa.js} +1 -1
  288. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-lqhdp8f0.js → gitGraphDiagram-WWUBYQGX-eravdti4.js} +1 -1
  289. package/static/dist/assets/{highlighted-body-KPVGNVTW-igw6cp39.js → highlighted-body-KPVGNVTW-jwpop9p8.js} +1 -1
  290. package/static/dist/assets/host-i828xuo7.js +1 -0
  291. package/static/dist/assets/index-eh8zilnv.css +2 -0
  292. package/static/dist/assets/{index-cxq58xb1.js → index-r648f4o1.js} +5 -5
  293. package/static/dist/assets/{info-A6RAGUB7-gvu7a93l.js → info-A6RAGUB7-h11x0dxo.js} +1 -1
  294. package/static/dist/assets/{infoDiagram-27XIBGKW-m8t8g6w1.js → infoDiagram-27XIBGKW-dfh47dw9.js} +1 -1
  295. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-l07vkbgj.js → ishikawaDiagram-5VMMS53U-i6ypq351.js} +1 -1
  296. package/static/dist/assets/{journeyDiagram-3NMN7TZE-hybkgj8l.js → journeyDiagram-3NMN7TZE-lduf1z95.js} +1 -1
  297. package/static/dist/assets/{kanban-definition-UXKFOSKX-by13uvtn.js → kanban-definition-UXKFOSKX-e18bu4pg.js} +1 -1
  298. package/static/dist/assets/{lazyRouteComponent-af0lvgjx.js → lazyRouteComponent-e1y4t2i9.js} +1 -1
  299. package/static/dist/assets/{line-osxq6fq7.js → line-meizqsvn.js} +1 -1
  300. package/static/dist/assets/{link-jyxicdhg.js → link-oot2yn73.js} +1 -1
  301. package/static/dist/assets/login-gu28l9ou.js +1 -0
  302. package/static/dist/assets/mermaid-HWGCJPDP-mtwrj8fn.js +1 -0
  303. package/static/dist/assets/{mermaid-parser.core-i91oxmou.js → mermaid-parser.core-ntne45i9.js} +3 -3
  304. package/static/dist/assets/{mindmap-definition-YA3MSWOX-m82mx8ww.js → mindmap-definition-YA3MSWOX-27b26eok.js} +1 -1
  305. package/static/dist/assets/onboarding-lkphzu1s.js +1 -0
  306. package/static/dist/assets/{onboarding_model_label-dp7xffoh.js → onboarding_model_label-ftpjdwqk.js} +1 -1
  307. package/static/dist/assets/{packet-AYTQ26CC-nbtgge2o.js → packet-AYTQ26CC-l6jdka8p.js} +1 -1
  308. package/static/dist/assets/{pegDiagram-XKGWAZYB-f1zxzga1.js → pegDiagram-XKGWAZYB-g1er3bxj.js} +1 -1
  309. package/static/dist/assets/{pie-WAS4IAKB-of8vuhxs.js → pie-WAS4IAKB-i0ylcrt9.js} +1 -1
  310. package/static/dist/assets/{pieDiagram-E7YTZNPT-o7y8ftio.js → pieDiagram-E7YTZNPT-fbz1e55z.js} +1 -1
  311. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-iqiwia1t.js → quadrantDiagram-AXDQQJYC-hhue7d8o.js} +1 -1
  312. package/static/dist/assets/{radar-RG4KPBEZ-delnsy2v.js → radar-RG4KPBEZ-f9b70aa4.js} +1 -1
  313. package/static/dist/assets/{railroad-74A4TZTK-nspnyq9u.js → railroad-74A4TZTK-lgsuoeda.js} +1 -1
  314. package/static/dist/assets/railroad-abnf-HS5TGJTU-nf9d6pdx.js +1 -0
  315. package/static/dist/assets/railroad-ebnf-LZEXJU2U-f7ummkt5.js +1 -0
  316. package/static/dist/assets/railroad-peg-WCYAUIDC-oczq5tqs.js +1 -0
  317. package/static/dist/assets/{railroadDiagram-O6MQD6OU-z41tqx5z.js → railroadDiagram-O6MQD6OU-eqpy9fnz.js} +1 -1
  318. package/static/dist/assets/{refresh-b4uams1i.js → refresh-abtu93ke.js} +1 -1
  319. package/static/dist/assets/{refresh-cw-nt1dtsx4.js → refresh-cw-hlwb02qv.js} +1 -1
  320. package/static/dist/assets/{requirementDiagram-BXWQKSXE-bdvif194.js → requirementDiagram-BXWQKSXE-ogf6puhi.js} +1 -1
  321. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-h73e0rol.js → sankeyDiagram-P5KCCOFB-fqb8xcjf.js} +1 -1
  322. package/static/dist/assets/{saved-kg52csy7.js → saved-girnabkf.js} +1 -1
  323. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-iv95mb1z.js → sequenceDiagram-WJ2MYXX4-dlcz6qjz.js} +1 -1
  324. package/static/dist/assets/{settings_section_extensions_title-ej0iapxe.js → settings_section_extensions_title-h14cwk6f.js} +1 -1
  325. package/static/dist/assets/share._token-kl0r7ido.js +2 -0
  326. package/static/dist/assets/{sliders-horizontal-fj18it47.js → sliders-horizontal-n1zm34qq.js} +1 -1
  327. package/static/dist/assets/sse-iwku0sjo.js +1 -0
  328. package/static/dist/assets/{stateDiagram-D77RDMKH-k417ixmf.js → stateDiagram-D77RDMKH-fyoznpcs.js} +1 -1
  329. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-ixhx8r46.js +1 -0
  330. package/static/dist/assets/{swimlanes-42K2YHIH-esola70w.js → swimlanes-42K2YHIH-oefldhfy.js} +1 -1
  331. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-h9ersk2e.js +8 -0
  332. package/static/dist/assets/{timeline-definition-24CTP7MA-getrz2y8.js → timeline-definition-24CTP7MA-b1vl2zil.js} +1 -1
  333. package/static/dist/assets/todoStore-jjr5yr78.js +1 -0
  334. package/static/dist/assets/{treeView-Q6P3EWNA-hc7ap1q7.js → treeView-Q6P3EWNA-f7hcci5o.js} +1 -1
  335. package/static/dist/assets/{treemap-WGGIJYW6-b7k1syy5.js → treemap-WGGIJYW6-nl85071a.js} +1 -1
  336. package/static/dist/assets/{tts-lxdxnmjd.js → units-kgxzxfhi.js} +1 -1
  337. package/static/dist/assets/{untitled-ovpfthg4.js → untitled-cikfjntn.js} +1 -1
  338. package/static/dist/assets/{useForm-bcblv1rx.js → useForm-kqoqg0k9.js} +1 -1
  339. package/static/dist/assets/useLocale-o59tbiea.js +1 -0
  340. package/static/dist/assets/{useParams-lhg566ew.js → useParams-b4dq6n4q.js} +1 -1
  341. package/static/dist/assets/{useSearch-lbrh6ejn.js → useSearch-ipfmv3he.js} +1 -1
  342. package/static/dist/assets/{vennDiagram-4TSXK5OY-n6pcy5qb.js → vennDiagram-4TSXK5OY-h5hzbk0n.js} +1 -1
  343. package/static/dist/assets/{wardley-WFR3VGLG-lpa18vz1.js → wardley-WFR3VGLG-jftvpm3e.js} +1 -1
  344. package/static/dist/assets/{wardleyDiagram-VM6X3IG4-kpclwwg7.js → wardleyDiagram-VM6X3IG4-hedh85ge.js} +1 -1
  345. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-paa2n8y0.js → xychartDiagram-S5SC5T6Z-e49523ab.js} +1 -1
  346. package/static/dist/index.html +1 -1
  347. package/static/dist/sw.js +1 -1
  348. package/static/dist/assets/AppShell-fb2td9zi.js +0 -1
  349. package/static/dist/assets/ChatPage-cj96kiy9.js +0 -25
  350. package/static/dist/assets/Dialog-kxcg7ard.js +0 -1
  351. package/static/dist/assets/ExtensionRoute-jf7h0754.js +0 -1
  352. package/static/dist/assets/HubRoute-gmanpd5y.js +0 -2
  353. package/static/dist/assets/Select-bilaypej.js +0 -1
  354. package/static/dist/assets/SessionListPanel-og8n36qs.js +0 -1
  355. package/static/dist/assets/Toaster-nnl8zkt9.js +0 -1
  356. package/static/dist/assets/_app.index-hbarzbjx.js +0 -1
  357. package/static/dist/assets/_app.insights-luywlarz.js +0 -1
  358. package/static/dist/assets/_app.kanban-kincnbr8.js +0 -1
  359. package/static/dist/assets/_app.logs-oi2gr3gs.js +0 -1
  360. package/static/dist/assets/_app.memory-b6jfivt6.js +0 -1
  361. package/static/dist/assets/_app.profiles-lpvixmqp.js +0 -1
  362. package/static/dist/assets/_app.settings-vowx3iq0.js +0 -1
  363. package/static/dist/assets/_app.settings._section-cnntg1tu.js +0 -3
  364. package/static/dist/assets/_app.skills-fb57lpau.js +0 -1
  365. package/static/dist/assets/_app.tasks-eszpx1q7.js +0 -2
  366. package/static/dist/assets/_app.todos-nr05ac6w.js +0 -1
  367. package/static/dist/assets/_app.workspaces-d2luomu6.js +0 -1
  368. package/static/dist/assets/architecture-7GRP2DOG-hd0o7bde.js +0 -1
  369. package/static/dist/assets/channel-cpdgf867.js +0 -1
  370. package/static/dist/assets/classDiagram-ZZMXUADV-ko09sguk.js +0 -1
  371. package/static/dist/assets/classDiagram-v2-VYDZK3BY-ko09sguk.js +0 -1
  372. package/static/dist/assets/endpoints-d33siokx.js +0 -64
  373. package/static/dist/assets/eventmodeling-NTZA5JFV-emctlo64.js +0 -1
  374. package/static/dist/assets/flowDiagram-HODETNUW-bfi3moqa.js +0 -1
  375. package/static/dist/assets/index-hgtx08n5.css +0 -2
  376. package/static/dist/assets/login-ec21t39x.js +0 -1
  377. package/static/dist/assets/mermaid-HWGCJPDP-gfl8z3ep.js +0 -1
  378. package/static/dist/assets/onboarding-btnltvyz.js +0 -1
  379. package/static/dist/assets/railroad-abnf-HS5TGJTU-nst1qxyn.js +0 -1
  380. package/static/dist/assets/railroad-ebnf-LZEXJU2U-ci37x8qy.js +0 -1
  381. package/static/dist/assets/railroad-peg-WCYAUIDC-aimkk07z.js +0 -1
  382. package/static/dist/assets/share._token-f97e94r0.js +0 -2
  383. package/static/dist/assets/sse-fwpqtzbg.js +0 -1
  384. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hwn4apct.js +0 -1
  385. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-d1h97oyg.js +0 -8
  386. package/static/dist/assets/todoStore-eginf9h4.js +0 -1
  387. package/static/dist/assets/useLocale-fi5jb8ma.js +0 -1
@@ -244,6 +244,77 @@ def execute_plugin_command(command: str) -> str | None:
244
244
  return f"Plugin command error: {type(exc).__name__}"
245
245
 
246
246
 
247
+ def _profile_bundles(skill_bundles) -> dict[str, dict[str, Any]]:
248
+ """The scoped profile's bundles keyed ``/slug``, read from disk the way ``scan_bundles`` reads them.
249
+
250
+ ``scan_bundles``/``get_skill_bundles`` publish one profile's bundles in a process-wide cache keyed only by mtime,
251
+ which cron jobs in other profiles read concurrently, so the sidecar never fills it."""
252
+ out: dict[str, dict[str, Any]] = {}
253
+ for path in skill_bundles._iter_bundle_files():
254
+ info = skill_bundles._load_bundle_file(path)
255
+ if info:
256
+ out.setdefault(f"/{info['slug']}", info)
257
+ return out
258
+
259
+
260
+ def list_command_bundles() -> list[dict[str, Any]]:
261
+ """The profile's skill bundles as slash-command rows; [] when the bundle runtime is missing or fails."""
262
+ try:
263
+ import agent.skill_bundles as skill_bundles
264
+ except ImportError:
265
+ return []
266
+ try:
267
+ bundles = sorted(_profile_bundles(skill_bundles).values(), key=lambda b: b["slug"])
268
+ except Exception: # noqa: BLE001
269
+ log.warning("Failed to list skill bundles", exc_info=True)
270
+ return []
271
+ return [{"name": str(b["slug"]).strip().lower(), "description": str(b.get("description") or "").strip() or "Skill bundle",
272
+ "skill_count": len(b.get("skills") or []), "source": "bundle"} for b in bundles if str(b.get("slug") or "").strip()]
273
+
274
+
275
+ def _bundle_invocation(info: dict[str, Any], instruction: str) -> tuple[str, list[str], list[str]] | None:
276
+ """``build_bundle_invocation_message`` for a bundle read by ``_profile_bundles`` (that one looks it up in the shared cache),
277
+ skipping the skills disabled for Web chat (``skills.platform_disabled.webui``)."""
278
+ from agent.skill_commands import _disabled_skill_names, _load_skill_blocks, _load_skill_payload, _scaffold_header
279
+
280
+ name = info["name"]
281
+ loaded, missing, disabled, blocks = _load_skill_blocks(
282
+ [(skill or "").strip() for skill in info["skills"]], _load_skill_payload, lambda _skill: f'[Loaded as part of the "{name}" skill bundle.]',
283
+ None, disabled_names=_disabled_skill_names("webui"))
284
+ if not blocks:
285
+ return None
286
+ header = _scaffold_header(f'"{name}" skill bundle', loaded, lead_lines=[f"Bundle: {name}"], missing=missing, disabled=disabled,
287
+ extra_instruction=info.get("instruction") or "", user_instruction=instruction)
288
+ return "\n\n".join([header, *blocks]), loaded, missing
289
+
290
+
291
+ def resolve_bundle_command(command: str) -> dict[str, Any]:
292
+ """Expand ``/<bundle> [instruction]`` into the user message that loads the bundle's skills."""
293
+ name, instruction = parse_slash_command(command)
294
+ try:
295
+ import agent.skill_bundles as skill_bundles
296
+ from agent.skill_commands import resolve_slash_key
297
+ except ImportError as exc:
298
+ raise RpcError("Skill bundle runtime unavailable", condition="bundle_unavailable") from exc
299
+ try:
300
+ bundles = _profile_bundles(skill_bundles)
301
+ key = resolve_slash_key(name, bundles)
302
+ result = _bundle_invocation(bundles[key], instruction) if key else None
303
+ except ValueError as exc:
304
+ raise InvalidParams(str(exc)) from exc
305
+ except Exception as exc: # noqa: BLE001
306
+ log.warning("Failed to resolve skill bundle command", exc_info=True)
307
+ raise RpcError("Skill bundle command unavailable", condition="bundle_unavailable") from exc
308
+ if key is None:
309
+ raise RpcError("Bundle command not found", condition="bundle_not_found")
310
+ message, loaded, missing = result or ("", [], [])
311
+ message = str(message or "").strip()
312
+ if not message:
313
+ raise RpcError("Bundle command returned no invocation text", condition="bundle_unavailable")
314
+ return {"name": key.lstrip("/"), "source": "bundle", "message": message, "loaded_skills": [str(s) for s in loaded or []],
315
+ "missing_skills": [str(s) for s in missing or []]}
316
+
317
+
247
318
  def resolve_moa_config(preset: str | None) -> dict:
248
319
  try:
249
320
  from hermes_cli.moa_config import moa_usage, normalize_moa_config
@@ -352,6 +423,16 @@ def register(registry) -> None:
352
423
  raise RpcError(f"unknown command {parse_slash_command(command)[0]!r}", condition="command_not_found")
353
424
  return {"output": output, "source": source}
354
425
 
426
+ @registry.method("commands.bundles")
427
+ def bundles(ctx: CallContext, params: dict) -> dict:
428
+ with scoped_home(profile_home_param(params)):
429
+ return {"bundles": list_command_bundles()}
430
+
431
+ @registry.method("commands.bundle_resolve")
432
+ def bundle_resolve(ctx: CallContext, params: dict) -> dict:
433
+ with scoped_home(profile_home_param(params)):
434
+ return resolve_bundle_command(str(params.get("command") or ""))
435
+
355
436
  @registry.method("commands.moa_preset")
356
437
  def moa(ctx: CallContext, params: dict) -> dict:
357
438
  with scoped_home(profile_home_param(params)):
@@ -33,6 +33,35 @@ _RUNNING_LOCK = threading.Lock()
33
33
  _SNAPSHOT_LOCK = threading.Lock()
34
34
 
35
35
 
36
+ def _main_model_pin() -> tuple[str | None, str | None]:
37
+ """``(provider, model)`` the scoped profile's main agent runs on: the Agent's own ``cron.jobs._main_model_pin``.
38
+
39
+ Agent 0.21.3 predates that helper, so it is rebuilt from the readers 0.21.4 composes: the home's effective
40
+ ``config.yaml`` model and the provider the runtime resolves for it. ``(None, None)`` when no model is configured.
41
+ """
42
+ from cron import jobs
43
+
44
+ if hasattr(jobs, "_main_model_pin"):
45
+ return jobs._main_model_pin()
46
+ from hermes_cli.config_effective import load_user_config_effective
47
+ from hermes_constants import get_hermes_home
48
+
49
+ cfg_path = get_hermes_home() / "config.yaml"
50
+ cfg = load_user_config_effective(cfg_path) if cfg_path.exists() else {}
51
+ model_cfg = cfg.get("model") or {}
52
+ model = model_cfg.get("default") or model_cfg.get("model") if isinstance(model_cfg, dict) else model_cfg
53
+ model = model.strip() or None if isinstance(model, str) else None
54
+ if not model:
55
+ return None, None
56
+ provider = None
57
+ with contextlib.suppress(Exception):
58
+ from hermes_cli.runtime_provider import resolve_runtime_provider
59
+
60
+ resolved = resolve_runtime_provider(requested=None).get("provider")
61
+ provider = resolved.strip().lower() or None if isinstance(resolved, str) else None
62
+ return provider, model
63
+
64
+
36
65
  class CronUnavailable(RpcError):
37
66
  def __init__(self, exc: Exception):
38
67
  super().__init__(f"cron unavailable: {exc}", condition="cron_unavailable")
@@ -396,8 +425,6 @@ def register(registry) -> None:
396
425
  # to the store home; owner_profile only preserves management from the creating Web profile.
397
426
  if post.get("profile") and execution_home and not (model and provider) and not body.get("no_agent"):
398
427
  try:
399
- from cron.jobs import _main_model_pin
400
-
401
428
  with _SNAPSHOT_LOCK, scoped_home(Path(execution_home)):
402
429
  profile_provider, profile_model = _main_model_pin()
403
430
  if not profile_model:
@@ -1,4 +1,4 @@
1
- """``gateway.*``: Hermes gateway lifecycle through the ``hermes`` CLI (ported from api/gateway_restart.py)."""
1
+ """``gateway.*``: Hermes gateway lifecycle (start, stop, restart) through the ``hermes`` CLI."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -16,7 +16,10 @@ from ..home import profile_home_param
16
16
  from ..rpc import CallContext
17
17
 
18
18
  log = logging.getLogger("talaria_sidecar.gateway")
19
- _RESTART_LOCK = threading.Lock()
19
+ # One lifecycle action at a time across every action: overlapping `hermes gateway` subprocesses race each other.
20
+ _ACTION_LOCK = threading.Lock()
21
+ ACTIONS = ("start", "stop", "restart")
22
+ _DONE = {"start": "started", "stop": "stopped", "restart": "restarted"}
20
23
 
21
24
 
22
25
  def _hermes_command() -> str:
@@ -39,9 +42,11 @@ def _drain(stream, sink: list[str]) -> None:
39
42
  pass
40
43
 
41
44
 
42
- def restart(home: Path, cli_profile: str | None, ctx: CallContext, *, quick_timeout: float, background_wait: float) -> dict:
43
- if not _RESTART_LOCK.acquire(blocking=False):
44
- return {"status": "busy", "message": "Restart already in progress. Please wait a moment and try again."}
45
+ def control(action: str, home: Path, cli_profile: str | None, ctx: CallContext, *, quick_timeout: float, background_wait: float) -> dict:
46
+ if action not in ACTIONS:
47
+ raise InvalidParams("unsupported gateway action")
48
+ if not _ACTION_LOCK.acquire(blocking=False):
49
+ return {"status": "busy", "message": "Another gateway action is already in progress. Please wait a moment and try again."}
45
50
  try:
46
51
  env = dict(os.environ)
47
52
  env["HERMES_HOME"] = str(home)
@@ -49,9 +54,12 @@ def restart(home: Path, cli_profile: str | None, ctx: CallContext, *, quick_time
49
54
  cmd = [_hermes_command()]
50
55
  if cli_profile:
51
56
  cmd.extend(["--profile", cli_profile])
52
- cmd.extend(["gateway", "restart"])
53
- log.info("Restarting gateway via %s (HERMES_HOME=%s)", " ".join(cmd), home)
54
- proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, env=env)
57
+ cmd.extend(["gateway", action])
58
+ log.info("Running gateway %s via %s (HERMES_HOME=%s)", action, " ".join(cmd), home)
59
+ try:
60
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, env=env)
61
+ except FileNotFoundError:
62
+ return {"status": "failed", "message": "Hermes CLI not found"}
55
63
  # Drain both pipes from the start: a chatty restart must never block on a full pipe and be mistaken for a hang.
56
64
  out: list[str] = []
57
65
  err: list[str] = []
@@ -67,8 +75,8 @@ def restart(home: Path, cli_profile: str | None, ctx: CallContext, *, quick_time
67
75
  def outcome() -> dict:
68
76
  stdout, stderr = collected()
69
77
  if proc.returncode == 0:
70
- return {"status": "completed", "message": "Gateway service restarted successfully", "detail": stdout or stderr}
71
- return {"status": "failed", "message": f"Restart failed: {stderr or stdout}", "detail": stdout or stderr, "returncode": proc.returncode}
78
+ return {"status": "completed", "message": f"Gateway service {_DONE[action]} successfully", "detail": stdout or stderr}
79
+ return {"status": "failed", "message": f"{action.capitalize()} failed: {stderr or stdout}", "detail": stdout or stderr, "returncode": proc.returncode}
72
80
 
73
81
  ctx.emit("progress", {"phase": "started"})
74
82
  deadline = time.monotonic() + quick_timeout
@@ -90,17 +98,26 @@ def restart(home: Path, cli_profile: str | None, ctx: CallContext, *, quick_time
90
98
  proc.kill()
91
99
  proc.wait(timeout=5)
92
100
  collected()
93
- return {"status": "failed", "message": f"Gateway restart process timed out after {background_wait:.0f}s and was terminated"}
101
+ return {"status": "failed", "message": f"Gateway {action} process timed out after {background_wait:.0f}s and was terminated", "timed_out": True}
94
102
  return outcome()
95
103
  finally:
96
- _RESTART_LOCK.release()
104
+ _ACTION_LOCK.release()
97
105
 
98
106
 
99
107
  def register(registry) -> None:
100
- @registry.method("gateway.restart", requires_agent=False)
101
- def restart_(ctx: CallContext, params: dict) -> dict:
108
+ def run(ctx: CallContext, params: dict, action: str) -> dict:
102
109
  home = profile_home_param(params)
103
110
  cli_profile = params.get("cli_profile")
104
111
  if cli_profile is not None and not isinstance(cli_profile, str):
105
112
  raise InvalidParams("cli_profile must be a string")
106
- return restart(home, cli_profile or None, ctx, quick_timeout=float(params.get("quick_timeout_seconds") or 2.0), background_wait=float(params.get("background_wait_seconds") or 240.0))
113
+ # A restart drains the old gateway first, so it gets longer than start or stop.
114
+ default_wait = 240.0 if action == "restart" else 60.0
115
+ return control(action, home, cli_profile or None, ctx, quick_timeout=float(params.get("quick_timeout_seconds") or 2.0), background_wait=float(params.get("background_wait_seconds") or default_wait))
116
+
117
+ @registry.method("gateway.control", requires_agent=False)
118
+ def control_(ctx: CallContext, params: dict) -> dict:
119
+ return run(ctx, params, str(params.get("action") or ""))
120
+
121
+ @registry.method("gateway.restart", requires_agent=False)
122
+ def restart_(ctx: CallContext, params: dict) -> dict:
123
+ return run(ctx, params, "restart")
@@ -96,10 +96,28 @@ def _row_claim_live(row) -> bool:
96
96
  return False
97
97
  columns = row.keys()
98
98
  claim = {k: row[k] if k in columns else None for k in ("status", "claim_lock", "worker_pid", "worker_started_at")}
99
+ claimed = bool(claim["claim_lock"] and claim["worker_pid"])
99
100
  try:
100
- return bool(_kb()._claim_is_live(claim))
101
- except Exception: # noqa: BLE001 - an older Agent without the helper
102
- return bool(claim["claim_lock"] and claim["worker_pid"])
101
+ helper = getattr(_kb(), "_claim_is_live", None)
102
+ except RpcError:
103
+ return claimed
104
+ if helper is not None:
105
+ try:
106
+ return bool(helper(claim))
107
+ except Exception: # noqa: BLE001 - the Agent could not decide: unknown liveness counts as live
108
+ return claimed
109
+ if not claimed:
110
+ return False
111
+ try:
112
+ # Agent 0.21.3 has no _claim_is_live, but its dispatcher's own worker PID check. Like its crash sweep, probe
113
+ # only a claim this host issued: another host's PID means nothing here, so a remote claim stays live.
114
+ from hermes_cli.kanban_db_dispatch import _pid_alive
115
+
116
+ if not str(claim["claim_lock"]).startswith(_kb()._host_prefix()):
117
+ return True
118
+ return bool(_pid_alive(int(claim["worker_pid"])))
119
+ except Exception: # noqa: BLE001
120
+ return True
103
121
 
104
122
 
105
123
  def _claim_live(conn, task_id) -> bool:
@@ -655,11 +673,14 @@ def bulk_payload(params: dict) -> dict:
655
673
 
656
674
 
657
675
  def dispatch_payload(params: dict) -> dict:
658
- kb = _kb()
659
- if not hasattr(kb, "dispatch_once"):
660
- raise InvalidParams("dispatcher is unavailable")
676
+ _kb()
677
+ try:
678
+ # Agent 0.21.6 dropped the kanban_db re-export; every supported release defines it here.
679
+ from hermes_cli.kanban_db_dispatch import dispatch_once
680
+ except Exception as exc: # noqa: BLE001
681
+ raise InvalidParams("dispatcher is unavailable") from exc
661
682
  with _conn(board=_board(params)) as conn:
662
- result = kb.dispatch_once(conn, dry_run=_bool(params, "dry_run"), max_spawn=_int(params, "max", 8, minimum=1, maximum=100))
683
+ result = dispatch_once(conn, dry_run=_bool(params, "dry_run"), max_spawn=_int(params, "max", 8, minimum=1, maximum=100))
663
684
  if isinstance(result, dict):
664
685
  return result
665
686
  try:
@@ -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,6 +8,7 @@ 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
@@ -42,12 +43,65 @@ 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()
@@ -60,6 +114,7 @@ def drain(max_events: int) -> list[dict]:
60
114
  detached = [s for s in registry._running.values() if getattr(s, "detached", False)]
61
115
  for session in detached:
62
116
  registry._refresh_detached_session(session)
117
+ _publish_recovered_exits(registry)
63
118
  except Exception: # noqa: BLE001
64
119
  log.debug("Failed to reconcile recovered processes", exc_info=True)
65
120
  out = []
@@ -415,10 +470,9 @@ def recover(base_home: Path) -> int:
415
470
  """TAL-533: re-adopt every profile's checkpointed processes when a sidecar starts, so a process in a profile nobody
416
471
  uses yet is watched (and its exit reported) from the start. Entering a home's scope recovers it once; a failed
417
472
  recovery fails the call, so the server retries."""
418
- from hermes_cli.profiles import _PROFILE_ID_RE
473
+ from .profiles import named_profile_homes
419
474
 
420
- profiles_root = base_home / "profiles"
421
- named = sorted(p for p in profiles_root.iterdir() if p.is_dir() and _PROFILE_ID_RE.match(p.name)) if profiles_root.is_dir() else []
475
+ named = [home for _, home in named_profile_homes(base_home)]
422
476
  failed = []
423
477
  for home in [base_home, *named]:
424
478
  try:
@@ -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
 
@@ -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:
@@ -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: