@maudecode/talaria-web 1.14.0 → 1.15.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 (253) hide show
  1. package/_release.json +4 -4
  2. package/dist/api/automation-router.d.ts +102 -0
  3. package/dist/api/automation-router.js +2 -0
  4. package/dist/api/automation-router.js.map +1 -1
  5. package/dist/api/chat-router.d.ts +589 -52
  6. package/dist/api/chat-router.js +111 -121
  7. package/dist/api/chat-router.js.map +1 -1
  8. package/dist/api/router.d.ts +2 -1
  9. package/dist/api/router.js +3 -1
  10. package/dist/api/router.js.map +1 -1
  11. package/dist/api/sessions-router.d.ts +1913 -299
  12. package/dist/api/sessions-router.js +35 -24
  13. package/dist/api/sessions-router.js.map +1 -1
  14. package/dist/api/settings-router.d.ts +35 -8
  15. package/dist/api/settings-router.js +14 -10
  16. package/dist/api/settings-router.js.map +1 -1
  17. package/dist/api/sse-routes.js +1 -1
  18. package/dist/api/sse-routes.js.map +1 -1
  19. package/dist/app.d.ts +2715 -418
  20. package/dist/config/agent-config.js +20 -15
  21. package/dist/config/agent-config.js.map +1 -1
  22. package/dist/http/context.d.ts +4 -2
  23. package/dist/http/context.js.map +1 -1
  24. package/dist/onboarding.d.ts +1 -1
  25. package/dist/onboarding.js +2 -1
  26. package/dist/onboarding.js.map +1 -1
  27. package/dist/providers/catalog.d.ts +11 -0
  28. package/dist/providers/catalog.js +32 -1
  29. package/dist/providers/catalog.js.map +1 -1
  30. package/dist/runtime.js +54 -16
  31. package/dist/runtime.js.map +1 -1
  32. package/dist/sessions/background-tasks.d.ts +137 -0
  33. package/dist/sessions/background-tasks.js +349 -0
  34. package/dist/sessions/background-tasks.js.map +1 -0
  35. package/dist/sessions/background-updates.d.ts +56 -0
  36. package/dist/sessions/background-updates.js +152 -0
  37. package/dist/sessions/background-updates.js.map +1 -0
  38. package/dist/sessions/cli-sessions.d.ts +6 -0
  39. package/dist/sessions/cli-sessions.js +20 -7
  40. package/dist/sessions/cli-sessions.js.map +1 -1
  41. package/dist/sessions/completions.d.ts +17 -1
  42. package/dist/sessions/completions.js +83 -12
  43. package/dist/sessions/completions.js.map +1 -1
  44. package/dist/sessions/compress.d.ts +33 -0
  45. package/dist/sessions/compress.js +66 -0
  46. package/dist/sessions/compress.js.map +1 -0
  47. package/dist/sessions/list.d.ts +14 -0
  48. package/dist/sessions/list.js +89 -16
  49. package/dist/sessions/list.js.map +1 -1
  50. package/dist/sessions/merge.d.ts +12 -1
  51. package/dist/sessions/merge.js +64 -7
  52. package/dist/sessions/merge.js.map +1 -1
  53. package/dist/sessions/relay.d.ts +7 -1
  54. package/dist/sessions/relay.js +17 -4
  55. package/dist/sessions/relay.js.map +1 -1
  56. package/dist/sessions/service.d.ts +61 -3
  57. package/dist/sessions/service.js +243 -15
  58. package/dist/sessions/service.js.map +1 -1
  59. package/dist/sessions/session.d.ts +16 -0
  60. package/dist/sessions/session.js +23 -2
  61. package/dist/sessions/session.js.map +1 -1
  62. package/dist/sessions/source-kind.d.ts +22 -0
  63. package/dist/sessions/source-kind.js +54 -0
  64. package/dist/sessions/source-kind.js.map +1 -0
  65. package/dist/sessions/state-db.d.ts +10 -1
  66. package/dist/sessions/state-db.js +48 -17
  67. package/dist/sessions/state-db.js.map +1 -1
  68. package/dist/sessions/store.d.ts +2 -0
  69. package/dist/sessions/store.js +23 -4
  70. package/dist/sessions/store.js.map +1 -1
  71. package/dist/sessions/streams.d.ts +3 -0
  72. package/dist/sessions/streams.js +0 -5
  73. package/dist/sessions/streams.js.map +1 -1
  74. package/dist/sessions/titles.d.ts +4 -0
  75. package/dist/sessions/titles.js +16 -0
  76. package/dist/sessions/titles.js.map +1 -1
  77. package/dist/sessions/turn-context.d.ts +1 -0
  78. package/dist/sessions/turn-context.js +3 -0
  79. package/dist/sessions/turn-context.js.map +1 -1
  80. package/dist/sessions/turn.d.ts +66 -6
  81. package/dist/sessions/turn.js +304 -61
  82. package/dist/sessions/turn.js.map +1 -1
  83. package/dist/settings.d.ts +2 -0
  84. package/dist/settings.js +4 -0
  85. package/dist/settings.js.map +1 -1
  86. package/dist/tools/crons.d.ts +34 -3
  87. package/dist/tools/crons.js +271 -31
  88. package/dist/tools/crons.js.map +1 -1
  89. package/package.json +2 -2
  90. package/sidecar/talaria_sidecar/__init__.py +1 -1
  91. package/sidecar/talaria_sidecar/methods/chat.py +233 -6
  92. package/sidecar/talaria_sidecar/methods/cron.py +22 -14
  93. package/sidecar/talaria_sidecar/methods/process.py +318 -0
  94. package/static/dist/FILES.txt +120 -119
  95. package/static/dist/assets/AppShell-orat1s43.js +1 -0
  96. package/static/dist/assets/ChatPage-ojhka3hy.js +22 -0
  97. package/static/dist/assets/HubRoute-gxgw1t33.js +2 -0
  98. package/static/dist/assets/{Markdown-emii55o0.js → Markdown-dy8t4dy7.js} +7 -7
  99. package/static/dist/assets/{RightPanel-o73y98dn.js → RightPanel-c66ani1s.js} +1 -1
  100. package/static/dist/assets/Select-fswf66up.js +1 -0
  101. package/static/dist/assets/SessionListPanel-goh5mtt8.js +1 -0
  102. package/static/dist/assets/{SettingsLayout-lnn2wo2s.js → SettingsLayout-n5d4wq4m.js} +1 -1
  103. package/static/dist/assets/{States-e5hv6gdh.js → States-kxyu50at.js} +1 -1
  104. package/static/dist/assets/{Toaster-c4jq1i26.js → Toaster-6zmbcqgg.js} +1 -1
  105. package/static/dist/assets/_app-fowyruiu.js +1 -0
  106. package/static/dist/assets/_app.ext._extensionId-nvjyrwhy.js +1 -0
  107. package/static/dist/assets/_app.index-c9zv4y7w.js +1 -0
  108. package/static/dist/assets/_app.insights-jf12nofo.js +1 -0
  109. package/static/dist/assets/_app.kanban-lri5a4yu.js +1 -0
  110. package/static/dist/assets/_app.logs-md1p0zqo.js +1 -0
  111. package/static/dist/assets/_app.memory-bzvf2e28.js +1 -0
  112. package/static/dist/assets/_app.profiles-jpg7zmbn.js +1 -0
  113. package/static/dist/assets/{_app.session._sessionId-m6nx9g8t.js → _app.session._sessionId-gyoh5ylx.js} +1 -1
  114. package/static/dist/assets/_app.settings-mpuwlx4j.js +1 -0
  115. package/static/dist/assets/_app.settings._section-knz8rwoj.js +1 -0
  116. package/static/dist/assets/_app.skills-fwu1dkir.js +1 -0
  117. package/static/dist/assets/_app.tasks-h9gphtl3.js +2 -0
  118. package/static/dist/assets/_app.todos-hd1dnvrh.js +1 -0
  119. package/static/dist/assets/_app.workspaces-l8toyn5f.js +1 -0
  120. package/static/dist/assets/{abnfDiagram-VCTEODGH-kejgxpil.js → abnfDiagram-VCTEODGH-cjqletra.js} +1 -1
  121. package/static/dist/assets/architecture-7GRP2DOG-cavnusaw.js +1 -0
  122. package/static/dist/assets/{architectureDiagram-5GKGNRK7-hqnf84fc.js → architectureDiagram-5GKGNRK7-k5a7v36n.js} +1 -1
  123. package/static/dist/assets/back-e4m6nsnm.js +1 -0
  124. package/static/dist/assets/{blockDiagram-I7D4REHJ-l1466a7j.js → blockDiagram-I7D4REHJ-b195z021.js} +1 -1
  125. package/static/dist/assets/{c4Diagram-7LVT6UL2-gg3aq3sr.js → c4Diagram-7LVT6UL2-olu212k1.js} +1 -1
  126. package/static/dist/assets/channel-gozy3dp3.js +1 -0
  127. package/static/dist/assets/{chunk-4HAMMTFA-oalgz5l8.js → chunk-4HAMMTFA-b8o1nufs.js} +1 -1
  128. package/static/dist/assets/{chunk-75Z2AOVW-babmzhdg.js → chunk-75Z2AOVW-l1seddto.js} +1 -1
  129. package/static/dist/assets/{chunk-DU6HZSFF-lagu350c.js → chunk-DU6HZSFF-lo5zg15v.js} +3 -3
  130. package/static/dist/assets/{chunk-F27PBJKO-gjh2tqif.js → chunk-F27PBJKO-dsdzrp16.js} +1 -1
  131. package/static/dist/assets/{chunk-GMAD6QVW-ipdgja5c.js → chunk-GMAD6QVW-f3fxqq8b.js} +1 -1
  132. package/static/dist/assets/{chunk-GVQU2GXP-j8wkn1at.js → chunk-GVQU2GXP-h8egxtf6.js} +1 -1
  133. package/static/dist/assets/{chunk-IMKFNOWR-k00laqyk.js → chunk-IMKFNOWR-iolwd8s0.js} +1 -1
  134. package/static/dist/assets/{chunk-L3NEJ4N5-fju6xqj4.js → chunk-L3NEJ4N5-b88bbtwi.js} +1 -1
  135. package/static/dist/assets/{chunk-OSK3NFVY-bi6widqt.js → chunk-OSK3NFVY-e3e7nr6s.js} +1 -1
  136. package/static/dist/assets/{chunk-P2QGCYS3-czr4tebv.js → chunk-P2QGCYS3-mdc46rt1.js} +1 -1
  137. package/static/dist/assets/{chunk-POPQ4Y6H-ktdhn67w.js → chunk-POPQ4Y6H-kqlllrrm.js} +1 -1
  138. package/static/dist/assets/{chunk-PWAF6VOD-ii4dsu0d.js → chunk-PWAF6VOD-he585k2x.js} +1 -1
  139. package/static/dist/assets/{chunk-SHT3W25Y-fpwk3xxp.js → chunk-SHT3W25Y-gh94y9a4.js} +1 -1
  140. package/static/dist/assets/{chunk-SVP7TREG-k0jrvhei.js → chunk-SVP7TREG-euxp9y7e.js} +1 -1
  141. package/static/dist/assets/{chunk-TICWLB2K-nkqv9ot0.js → chunk-TICWLB2K-c8cxes1q.js} +1 -1
  142. package/static/dist/assets/classDiagram-ZZMXUADV-n0vs2f2q.js +1 -0
  143. package/static/dist/assets/classDiagram-v2-VYDZK3BY-n0vs2f2q.js +1 -0
  144. package/static/dist/assets/{copied-gpchexqu.js → copied-kq0u3ht7.js} +1 -1
  145. package/static/dist/assets/{cynefin-OW5HDTMX-lqxt8rs3.js → cynefin-OW5HDTMX-lxd44ux4.js} +1 -1
  146. package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-j9eqovf5.js → cynefinDiagram-5FMLGOSQ-njpiwat8.js} +1 -1
  147. package/static/dist/assets/{dagre-GXQ25YYZ-m9agobjm.js → dagre-GXQ25YYZ-nvgy6yju.js} +1 -1
  148. package/static/dist/assets/{diagram-S7CK7UJ4-itodc50c.js → diagram-S7CK7UJ4-g98nyv72.js} +1 -1
  149. package/static/dist/assets/{diagram-UQ7AKVKN-n1h2t4ah.js → diagram-UQ7AKVKN-ovgm6lvv.js} +1 -1
  150. package/static/dist/assets/{diagram-VSXAHHWV-lvtvm9bj.js → diagram-VSXAHHWV-pb2bn2e4.js} +1 -1
  151. package/static/dist/assets/{diagram-VX7I27RA-cgas58sy.js → diagram-VX7I27RA-6j4mvfqo.js} +1 -1
  152. package/static/dist/assets/{diagram-Z3DM3KII-d0aeps1q.js → diagram-Z3DM3KII-dqmb982r.js} +1 -1
  153. package/static/dist/assets/{ebnfDiagram-PWID7BFC-md345fyq.js → ebnfDiagram-PWID7BFC-ozbpzb6d.js} +1 -1
  154. package/static/dist/assets/{endpoints-e58saih3.js → endpoints-k8agnro8.js} +8 -8
  155. package/static/dist/assets/{erDiagram-RLTQ6QDP-i4rua63b.js → erDiagram-RLTQ6QDP-hl7m8vua.js} +1 -1
  156. package/static/dist/assets/eventmodeling-NTZA5JFV-j1y2ekec.js +1 -0
  157. package/static/dist/assets/flowDiagram-HODETNUW-g2cvcbm5.js +1 -0
  158. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-geqx7zgl.js → ganttDiagram-EL5Y4UJY-ompixqmh.js} +1 -1
  159. package/static/dist/assets/{gitGraph-4MIJSDKK-leoq0z8t.js → gitGraph-4MIJSDKK-b6vxwp9q.js} +1 -1
  160. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-llrlxfha.js → gitGraphDiagram-WWUBYQGX-k98f57on.js} +1 -1
  161. package/static/dist/assets/{highlighted-body-KPVGNVTW-kq2cnw6q.js → highlighted-body-KPVGNVTW-cd3ff6ge.js} +1 -1
  162. package/static/dist/assets/index-nfy0ezs6.css +2 -0
  163. package/static/dist/assets/{index-f9bzglkn.js → index-nqed3oum.js} +3 -3
  164. package/static/dist/assets/{info-A6RAGUB7-lkly4xj6.js → info-A6RAGUB7-hjcwjami.js} +1 -1
  165. package/static/dist/assets/{infoDiagram-27XIBGKW-oo7kdyte.js → infoDiagram-27XIBGKW-fm06rj36.js} +1 -1
  166. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-hq6jf374.js → ishikawaDiagram-5VMMS53U-bxak4mwu.js} +1 -1
  167. package/static/dist/assets/{journeyDiagram-3NMN7TZE-is03ntb0.js → journeyDiagram-3NMN7TZE-khmihclr.js} +1 -1
  168. package/static/dist/assets/{kanban-definition-UXKFOSKX-jmerizqb.js → kanban-definition-UXKFOSKX-h1r32wdx.js} +1 -1
  169. package/static/dist/assets/{lazyRouteComponent-b1h2ykmb.js → lazyRouteComponent-b2eg0fws.js} +1 -1
  170. package/static/dist/assets/{line-laaa8h99.js → line-friyjof3.js} +1 -1
  171. package/static/dist/assets/{link-d8f0788e.js → link-m90ahx3n.js} +1 -1
  172. package/static/dist/assets/login-bw7m2fzo.js +1 -0
  173. package/static/dist/assets/mermaid-HWGCJPDP-njwcl62n.js +1 -0
  174. package/static/dist/assets/{mermaid-parser.core-ix4junux.js → mermaid-parser.core-f1gvpwrt.js} +3 -3
  175. package/static/dist/assets/{mindmap-definition-YA3MSWOX-o0e0adl1.js → mindmap-definition-YA3MSWOX-mt2oxwue.js} +1 -1
  176. package/static/dist/assets/{onboarding-lx0d6b38.js → onboarding-ret37j1e.js} +1 -1
  177. package/static/dist/assets/{packet-AYTQ26CC-kl8pwsyg.js → packet-AYTQ26CC-jik4ixlf.js} +1 -1
  178. package/static/dist/assets/{pegDiagram-XKGWAZYB-bbqn9jo4.js → pegDiagram-XKGWAZYB-erxlv6cc.js} +1 -1
  179. package/static/dist/assets/{pie-WAS4IAKB-cpyo8bkr.js → pie-WAS4IAKB-mnkoldfl.js} +1 -1
  180. package/static/dist/assets/{pieDiagram-E7YTZNPT-pelug34f.js → pieDiagram-E7YTZNPT-i7lwrada.js} +1 -1
  181. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-jds4jmr5.js → quadrantDiagram-AXDQQJYC-fr8ejdoy.js} +1 -1
  182. package/static/dist/assets/{radar-RG4KPBEZ-e02oxnvd.js → radar-RG4KPBEZ-otarjte2.js} +1 -1
  183. package/static/dist/assets/{railroad-74A4TZTK-kz3delwo.js → railroad-74A4TZTK-lw362oto.js} +1 -1
  184. package/static/dist/assets/railroad-abnf-HS5TGJTU-m741owar.js +1 -0
  185. package/static/dist/assets/railroad-ebnf-LZEXJU2U-noa4fbia.js +1 -0
  186. package/static/dist/assets/railroad-peg-WCYAUIDC-o4bpw4nb.js +1 -0
  187. package/static/dist/assets/{railroadDiagram-O6MQD6OU-jgroj2ew.js → railroadDiagram-O6MQD6OU-esyn75pw.js} +1 -1
  188. package/static/dist/assets/{refresh-cw-k7yxjdn3.js → refresh-cw-k66keho6.js} +1 -1
  189. package/static/dist/assets/{requirementDiagram-BXWQKSXE-m4az5e0j.js → requirementDiagram-BXWQKSXE-cyhi5u29.js} +1 -1
  190. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-vlryqiv3.js → sankeyDiagram-P5KCCOFB-jljpbo0h.js} +1 -1
  191. package/static/dist/assets/{saved-brdfjjnw.js → saved-do49si3v.js} +1 -1
  192. package/static/dist/assets/{search-gt2ueyvg.js → search-j5ki6a9o.js} +1 -1
  193. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-fr8hrpql.js → sequenceDiagram-WJ2MYXX4-fz0dhlu7.js} +1 -1
  194. package/static/dist/assets/{settings_section_extensions_title-l0zodj0b.js → settings_section_extensions_title-b7lojn0o.js} +1 -1
  195. package/static/dist/assets/share._token-kg8w647b.js +2 -0
  196. package/static/dist/assets/{sliders-horizontal-gcgscv8r.js → sliders-horizontal-c4wya4dy.js} +1 -1
  197. package/static/dist/assets/{stateDiagram-D77RDMKH-bg548iyr.js → stateDiagram-D77RDMKH-kdnithdx.js} +1 -1
  198. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-k2wg2645.js +1 -0
  199. package/static/dist/assets/{swimlanes-42K2YHIH-hk5z3efh.js → swimlanes-42K2YHIH-esd6gmz3.js} +1 -1
  200. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-n5kvjtgr.js +8 -0
  201. package/static/dist/assets/tabBuild-bjmyvmn5.js +56 -0
  202. package/static/dist/assets/{timeline-definition-24CTP7MA-olvknr62.js → timeline-definition-24CTP7MA-g0qygccv.js} +1 -1
  203. package/static/dist/assets/todoStore-kjk5l0sy.js +1 -0
  204. package/static/dist/assets/{treeView-Q6P3EWNA-ovvjsxtf.js → treeView-Q6P3EWNA-ndchzkuu.js} +1 -1
  205. package/static/dist/assets/{treemap-WGGIJYW6-ggkipj59.js → treemap-WGGIJYW6-hwjxj0zz.js} +1 -1
  206. package/static/dist/assets/{untitled-c8ydw0bu.js → untitled-mbr6tuhu.js} +1 -1
  207. package/static/dist/assets/{useForm-kd759sjk.js → useForm-ocxu460f.js} +1 -1
  208. package/static/dist/assets/useLocale-fzy5jhg6.js +1 -0
  209. package/static/dist/assets/{useParams-katwvzil.js → useParams-lo9u33d1.js} +1 -1
  210. package/static/dist/assets/{useSearch-c70sgau1.js → useSearch-lq1mr090.js} +1 -1
  211. package/static/dist/assets/{vennDiagram-4TSXK5OY-hzld7zt7.js → vennDiagram-4TSXK5OY-gubovd1r.js} +1 -1
  212. package/static/dist/assets/{wardley-WFR3VGLG-hxkeoms8.js → wardley-WFR3VGLG-ir3om65j.js} +1 -1
  213. package/static/dist/assets/{wardleyDiagram-VM6X3IG4-ljh90mb1.js → wardleyDiagram-VM6X3IG4-ce0or1ie.js} +1 -1
  214. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-mnuaes03.js → xychartDiagram-S5SC5T6Z-dexylcjg.js} +1 -1
  215. package/static/dist/index.html +1 -1
  216. package/static/dist/sw.js +1 -1
  217. package/static/dist/assets/AppShell-haqi9qzu.js +0 -1
  218. package/static/dist/assets/ChatPage-hjyu2g64.js +0 -22
  219. package/static/dist/assets/HubRoute-gkd8o1vg.js +0 -2
  220. package/static/dist/assets/Select-jld3yeni.js +0 -1
  221. package/static/dist/assets/SessionListPanel-d3qx4ric.js +0 -1
  222. package/static/dist/assets/_app-bnip2pjg.js +0 -1
  223. package/static/dist/assets/_app.ext._extensionId-egptzrfy.js +0 -1
  224. package/static/dist/assets/_app.index-vdz13ul5.js +0 -1
  225. package/static/dist/assets/_app.insights-lgjshsba.js +0 -1
  226. package/static/dist/assets/_app.kanban-c18cjrpz.js +0 -1
  227. package/static/dist/assets/_app.logs-ezhojgeg.js +0 -1
  228. package/static/dist/assets/_app.memory-i00paisv.js +0 -1
  229. package/static/dist/assets/_app.profiles-kd02bwjx.js +0 -1
  230. package/static/dist/assets/_app.settings-giaudkuk.js +0 -1
  231. package/static/dist/assets/_app.settings._section-k4qtlexk.js +0 -1
  232. package/static/dist/assets/_app.skills-bby2uvl8.js +0 -1
  233. package/static/dist/assets/_app.tasks-1kz9bosm.js +0 -2
  234. package/static/dist/assets/_app.todos-izjdbkm2.js +0 -1
  235. package/static/dist/assets/_app.workspaces-lfh2kw7s.js +0 -1
  236. package/static/dist/assets/architecture-7GRP2DOG-b4u9z1w3.js +0 -1
  237. package/static/dist/assets/channel-o77ioktg.js +0 -1
  238. package/static/dist/assets/classDiagram-ZZMXUADV-f5rvod26.js +0 -1
  239. package/static/dist/assets/classDiagram-v2-VYDZK3BY-f5rvod26.js +0 -1
  240. package/static/dist/assets/eventmodeling-NTZA5JFV-bn3fjsaq.js +0 -1
  241. package/static/dist/assets/flowDiagram-HODETNUW-li70d120.js +0 -1
  242. package/static/dist/assets/index-gkh7kum4.css +0 -2
  243. package/static/dist/assets/login-llkohrcg.js +0 -1
  244. package/static/dist/assets/mermaid-HWGCJPDP-id0ffk2r.js +0 -1
  245. package/static/dist/assets/railroad-abnf-HS5TGJTU-l13ped2z.js +0 -1
  246. package/static/dist/assets/railroad-ebnf-LZEXJU2U-c7rg3g4g.js +0 -1
  247. package/static/dist/assets/railroad-peg-WCYAUIDC-od0b8niq.js +0 -1
  248. package/static/dist/assets/share._token-oyjv046r.js +0 -2
  249. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-j5v40qih.js +0 -1
  250. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-grzefo9c.js +0 -8
  251. package/static/dist/assets/tabBuild-idliyw9f.js +0 -56
  252. package/static/dist/assets/todoStore-j4c2xq0b.js +0 -1
  253. package/static/dist/assets/useLocale-hds4xmja.js +0 -1
@@ -257,13 +257,86 @@ def _clarify_timeout(params: dict) -> int:
257
257
  return 3600
258
258
 
259
259
 
260
+ def _steer_slot_lock(agent):
261
+ """The Agent's own pending-steer lock (a plain ``Lock``), so a slot read or rewrite is atomic against its drains."""
262
+ lock = agent.__dict__.get("_pending_steer_lock") if hasattr(agent, "__dict__") else None
263
+ return contextlib.nullcontext() if lock is None else lock
264
+
265
+
266
+ def _steer_slot(agent) -> str:
267
+ """The Agent's not-yet-applied steer text; call under ``_steer_slot_lock``."""
268
+ return str(getattr(agent, "_pending_steer", "") or "")
269
+
270
+
260
271
  def _agent_pending_steer_text(agent) -> str:
261
272
  """Predecessor ``_agent_pending_steer_text``: the Agent's not-yet-applied steer text."""
262
- lock = agent.__dict__.get("_pending_steer_lock") if hasattr(agent, "__dict__") else None
263
- if lock is None:
264
- return str(getattr(agent, "_pending_steer", "") or "")
265
- with lock:
266
- return str(agent.__dict__.get("_pending_steer") or "")
273
+ with _steer_slot_lock(agent):
274
+ return _steer_slot(agent)
275
+
276
+
277
+ def _slot_without(slot: str, pending: list[str], index: int) -> str | None:
278
+ """``slot`` without the steer ``pending[index]`` while the Agent still holds it, else None (it already took it).
279
+
280
+ The Agent keeps every not-yet-applied steer in one newline-joined slot, so the slot is the tail of ``pending``
281
+ (the server's steers, oldest first), possibly behind text another surface queued: the server's
282
+ ``pendingSteerSuffixStart`` rule. The longest tail that still holds the steer wins."""
283
+ for start in range(index + 1):
284
+ tail = "\n".join(pending[start:])
285
+ if slot == tail or slot.endswith("\n" + tail):
286
+ head = slot[: len(slot) - len(tail)].rstrip("\n")
287
+ return "\n".join(part for part in [head, *pending[start:index], *pending[index + 1:]] if part)
288
+ return None
289
+
290
+
291
+ def withdraw_steer(agent, pending: list[str], index: int) -> bool:
292
+ """TAL-424: take a not-yet-applied steer back out of the Agent's slot, keeping the others in order. False when the
293
+ Agent already took it; the slot is then left as it was, so no text is ever lost."""
294
+ with _steer_slot_lock(agent):
295
+ rest = _slot_without(_steer_slot(agent), pending, index)
296
+ if rest is None:
297
+ return False
298
+ agent._pending_steer = rest or None
299
+ return True
300
+
301
+
302
+ def steer_now(agent, pending: list[str], index: int) -> dict:
303
+ """TAL-424: deliver a pending steer now with the Agent's ``redirect``. During a model request that request restarts
304
+ with it (``delivery: redirect``); during tools it goes back on the slot and the tools yield (``delivery: steer``).
305
+ With no live request the steer stays pending: back in its place, or last when the slot changed meanwhile."""
306
+ text = pending[index]
307
+ with _steer_slot_lock(agent):
308
+ before = _steer_slot(agent)
309
+ rest = _slot_without(before, pending, index)
310
+ if rest is None:
311
+ return {"redirected": False, "withdrawn": False}
312
+ agent._pending_steer = rest or None
313
+ redirect = getattr(agent, "redirect", None)
314
+ try:
315
+ redirected = bool(redirect(text)) if callable(redirect) else False
316
+ except Exception as exc: # noqa: BLE001
317
+ log.debug("redirect failed: %s", exc)
318
+ redirected = False
319
+ if not redirected:
320
+ with _steer_slot_lock(agent):
321
+ if _steer_slot(agent) == rest:
322
+ agent._pending_steer = before or None
323
+ return {"redirected": False, "withdrawn": True, "requeued": "kept"}
324
+ agent.steer(text)
325
+ return {"redirected": False, "withdrawn": True, "requeued": "last"}
326
+ with _steer_slot_lock(agent):
327
+ slot = _steer_slot(agent)
328
+ delivery = "steer" if slot == text or slot.endswith("\n" + text) else "redirect"
329
+ return {"redirected": True, "withdrawn": True, "delivery": delivery}
330
+
331
+
332
+ def _steer_queue_params(params: dict) -> tuple[list[str], int]:
333
+ pending = params.get("pending")
334
+ index = params.get("index")
335
+ if not isinstance(pending, list) or not all(isinstance(t, str) and t.strip() for t in pending):
336
+ raise InvalidParams("pending must be a list of steer texts")
337
+ if not isinstance(index, int) or isinstance(index, bool) or not 0 <= index < len(pending):
338
+ raise InvalidParams("index must point into pending")
339
+ return [t.strip() for t in pending], index
267
340
 
268
341
 
269
342
  def _cancel_checkpoint(run: _Run) -> list | None:
@@ -725,6 +798,131 @@ def start(ctx: CallContext, params: dict) -> dict: # noqa: PLR0915 - one turn,
725
798
  _RUNS_BY_SESSION.pop(session_id, None)
726
799
 
727
800
 
801
+ def _checkpoint_required() -> bool:
802
+ """``compression.checkpoint_required`` needs the memory provider loaded to write the pre-compression checkpoint."""
803
+ try:
804
+ from hermes_cli.config import load_config
805
+
806
+ return ((load_config() or {}).get("compression") or {}).get("checkpoint_required") is True
807
+ except Exception: # noqa: BLE001
808
+ return False
809
+
810
+
811
+ #: Compressed results awaiting the server's write: ``commit_token -> (agent, profile home, expiry timer)``.
812
+ _PENDING_COMPRESSIONS: dict[str, tuple[Any, Any, threading.Timer]] = {}
813
+ _PENDING_COMPRESSIONS_LOCK = threading.Lock()
814
+ #: A server that never answers (restart, crash) gets its compression discarded after this long.
815
+ _PENDING_COMPRESSION_TTL = 600.0
816
+
817
+
818
+ def _release_compression(agent, committed: bool) -> None:
819
+ """Emit (committed) or discard the Agent's deferred context-engine notification, then close the throwaway agent."""
820
+ from agent.conversation_compression import finalize_context_engine_compression_notification
821
+
822
+ try:
823
+ finalize_context_engine_compression_notification(agent, committed=committed)
824
+ finally:
825
+ with contextlib.suppress(Exception):
826
+ agent._end_session_on_close = False
827
+ close = getattr(agent, "close", None)
828
+ if callable(close):
829
+ try:
830
+ close()
831
+ except Exception: # noqa: BLE001
832
+ log.debug("compression agent close failed", exc_info=True)
833
+
834
+
835
+ def finalize_compression(token: str, committed: bool) -> bool:
836
+ """``chat.compress_finalize``: the server reports whether it installed the result; unknown tokens are a no-op."""
837
+ with _PENDING_COMPRESSIONS_LOCK:
838
+ entry = _PENDING_COMPRESSIONS.pop(token, None)
839
+ if entry is None:
840
+ return False
841
+ agent, home, expiry = entry
842
+ expiry.cancel() # a no-op when the timer itself is finalizing
843
+ with scoped_home(home):
844
+ _release_compression(agent, committed=committed)
845
+ return True
846
+
847
+
848
+ def compress(ctx: CallContext, params: dict) -> dict:
849
+ """Manual ``/compress`` of ``conversation_history`` through the Agent's shared core (``compress_now``) on a throwaway
850
+ agent, like the gateway's ``_run_manual_compression``. The server owns the transcript: it re-checks the session and
851
+ installs ``messages`` itself, so nothing here touches history or the cached turn agent. The agent has no session
852
+ store, so the Agent neither rotates nor writes state.db; its one deferred effect, the context-engine notification,
853
+ waits for ``chat.compress_finalize`` with the returned ``commit_token`` (two-phase, like the gateway's commit)."""
854
+ home = profile_home_param(params)
855
+ session_id = str(params.get("session_id") or "").strip()
856
+ if not session_id:
857
+ raise InvalidParams("session_id is required")
858
+ history = params.get("conversation_history")
859
+ if not isinstance(history, list):
860
+ raise InvalidParams("conversation_history must be a list")
861
+ model = str(params.get("model") or "").strip()
862
+ provider = str(params.get("model_provider") or "").strip() or None
863
+ focus_topic = str(params.get("focus_topic") or "").strip()[:500] or None
864
+ toolsets = params.get("enabled_toolsets")
865
+ runtime = _resolve_runtime(provider, model)
866
+ if not runtime.get("api_key"):
867
+ raise RpcError("No provider configured -- cannot compress.", condition="credential_missing")
868
+ from agent.conversation_compression_manual import CompressRequest, compress_now
869
+
870
+ AIAgent = _agent_class()
871
+ kwargs: dict = dict(
872
+ model=model or str(runtime.get("model") or ""),
873
+ provider=provider or runtime.get("provider"),
874
+ base_url=runtime.get("base_url"),
875
+ api_key=runtime.get("api_key"),
876
+ platform="webui",
877
+ quiet_mode=True,
878
+ enabled_toolsets=toolsets if isinstance(toolsets, list) else None,
879
+ session_id=session_id,
880
+ )
881
+ for name, value in (
882
+ ("api_mode", runtime.get("api_mode")),
883
+ ("acp_command", runtime.get("acp_command")),
884
+ ("acp_args", runtime.get("acp_args")),
885
+ ("credential_pool", runtime.get("credential_pool")),
886
+ ("gateway_session_key", session_id),
887
+ ("skip_memory", not _checkpoint_required()),
888
+ ):
889
+ if _supported(AIAgent, name) and value is not None:
890
+ kwargs[name] = value
891
+ agent = AIAgent(**kwargs)
892
+ held = False
893
+ try:
894
+ result = compress_now(agent, history, CompressRequest(focus_topic=focus_topic), task_id=session_id)
895
+ message = None
896
+ if result.status != "compressed":
897
+ from agent.conversation_compression_manual import render_compress_result
898
+
899
+ message = "\n".join(render_compress_result(result)) or None
900
+ payload = {
901
+ "status": result.status,
902
+ "messages": json.loads(json.dumps([m for m in result.after_messages if isinstance(m, dict)], default=str)),
903
+ "before_tokens": int(result.before_tokens or 0),
904
+ "after_tokens": int(result.after_tokens or 0),
905
+ "summary": json.loads(json.dumps(result.summary, default=str)) if isinstance(result.summary, dict) else None,
906
+ "message": message,
907
+ "agent_session_id": str(getattr(agent, "session_id", None) or session_id),
908
+ "commit_token": None,
909
+ }
910
+ if result.status == "compressed":
911
+ token = uuid.uuid4().hex
912
+ # A server that never answers (restart, failed RPC) gets the compression discarded on its own.
913
+ expiry = threading.Timer(_PENDING_COMPRESSION_TTL, finalize_compression, args=(token, False))
914
+ expiry.daemon = True
915
+ with _PENDING_COMPRESSIONS_LOCK:
916
+ _PENDING_COMPRESSIONS[token] = (agent, home, expiry)
917
+ expiry.start()
918
+ held = True
919
+ payload["commit_token"] = token
920
+ return payload
921
+ finally:
922
+ if not held:
923
+ _release_compression(agent, committed=False)
924
+
925
+
728
926
  def _run_for(params: dict) -> _Run | None:
729
927
  stream_id = str(params.get("stream_id") or "").strip()
730
928
  session_id = str(params.get("session_id") or "").strip()
@@ -748,6 +946,18 @@ def register(registry) -> None:
748
946
  with _turn_identity(session_id, workspace):
749
947
  return start(ctx, params)
750
948
 
949
+ @registry.method("chat.compress")
950
+ def compress_(ctx: CallContext, params: dict) -> dict:
951
+ with scoped_home(profile_home_param(params)):
952
+ return compress(ctx, params)
953
+
954
+ @registry.method("chat.compress_finalize", requires_agent=False)
955
+ def compress_finalize_(ctx: CallContext, params: dict) -> dict:
956
+ token = str(params.get("commit_token") or "").strip()
957
+ if not token:
958
+ raise InvalidParams("commit_token is required")
959
+ return {"finalized": finalize_compression(token, params.get("committed") is True)}
960
+
751
961
  @registry.method("chat.interrupt", requires_agent=False)
752
962
  def interrupt_(ctx: CallContext, params: dict) -> dict:
753
963
  run = _run_for(params)
@@ -786,7 +996,24 @@ def register(registry) -> None:
786
996
  except Exception as exc: # noqa: BLE001
787
997
  log.debug("steer failed: %s", exc)
788
998
  return {"accepted": False, "fallback": "steer_error"}
789
- return {"accepted": accepted, "fallback": None if accepted else "not_running"}
999
+ # `can_redirect`: whether this Agent can deliver a pending steer now (TAL-424 "Send now").
1000
+ return {"accepted": accepted, "fallback": None if accepted else "not_running", "can_redirect": callable(getattr(run.agent, "redirect", None))}
1001
+
1002
+ @registry.method("chat.steer_withdraw", requires_agent=False)
1003
+ def steer_withdraw_(ctx: CallContext, params: dict) -> dict:
1004
+ pending, index = _steer_queue_params(params)
1005
+ run = _run_for(params)
1006
+ if run is None or run.agent is None:
1007
+ return {"withdrawn": False}
1008
+ return {"withdrawn": withdraw_steer(run.agent, pending, index)}
1009
+
1010
+ @registry.method("chat.steer_now", requires_agent=False)
1011
+ def steer_now_(ctx: CallContext, params: dict) -> dict:
1012
+ pending, index = _steer_queue_params(params)
1013
+ run = _run_for(params)
1014
+ if run is None or run.agent is None:
1015
+ return {"redirected": False, "withdrawn": False}
1016
+ return steer_now(run.agent, pending, index)
790
1017
 
791
1018
  @registry.method("chat.evict_agent", requires_agent=False)
792
1019
  def evict_(ctx: CallContext, params: dict) -> dict:
@@ -379,25 +379,33 @@ def register(registry) -> None:
379
379
  post["profile"] = str(body["profile"]).strip()
380
380
  if body.get("toast_notifications") is False:
381
381
  post["toast_notifications"] = False
382
- snapshot_home = params.get("execution_home")
383
- # The selected profile's provider/model snapshot is resolved before the job exists (predecessor
384
- # ``_selected_profile_snapshot_updates`` ran before ``create_job``), so a failure leaves nothing behind.
385
- if post.get("profile") and snapshot_home and not (body.get("model") and body.get("provider")) and not body.get("no_agent"):
382
+ if body.get("owner_profile"):
383
+ post["owner_profile"] = str(body["owner_profile"]).strip()
384
+ execution_home = params.get("execution_home")
385
+ if post.get("profile"):
386
+ if not execution_home:
387
+ raise InvalidParams("execution_home is required for a profile job")
388
+ home = Path(execution_home)
389
+ model, provider = (value.strip() or None if isinstance(value, str) else None for value in (body.get("model"), body.get("provider")))
390
+ # The scheduler ignores Talaria's profile field. Pin the execution profile's main model
391
+ # before creating it in that profile's own store. Scheduled workers bind credentials/config
392
+ # to the store home; owner_profile only preserves management from the creating Web profile.
393
+ if post.get("profile") and execution_home and not (model and provider) and not body.get("no_agent"):
386
394
  try:
387
- from cron.jobs import _compute_provider_model_snapshots
388
-
389
- with _SNAPSHOT_LOCK, scoped_home(Path(snapshot_home)):
390
- provider_snapshot, model_snapshot = _compute_provider_model_snapshots(provider=body.get("provider") or None, model=body.get("model") or None, base_url=None, no_agent=False)
391
- if body.get("provider") is None:
392
- post["provider_snapshot"] = provider_snapshot
393
- if body.get("model") is None:
394
- post["model_snapshot"] = model_snapshot
395
+ from cron.jobs import _main_model_pin
396
+
397
+ with _SNAPSHOT_LOCK, scoped_home(Path(execution_home)):
398
+ profile_provider, profile_model = _main_model_pin()
399
+ if not profile_model:
400
+ raise ValueError("Profile has no main model configured")
401
+ provider = provider or profile_provider
402
+ model = model or profile_model
395
403
  except Exception as exc: # noqa: BLE001
396
- raise RpcError(f"Cannot safely resolve cron snapshots for profile {post['profile']!r}", condition="cron_snapshot_failed") from exc
404
+ raise RpcError(f"Cannot safely resolve cron model for profile {post['profile']!r}", condition="cron_snapshot_failed") from exc
397
405
  with _store(home), scoped_home(home):
398
406
  try:
399
407
  job = jobs.create_job(prompt=body.get("prompt") or "", schedule=body["schedule"], name=body.get("name") or None, deliver=body.get("deliver") or "local",
400
- skills=body.get("skills") or [], model=body.get("model") or None, provider=body.get("provider") or None, **kwargs)
408
+ skills=body.get("skills") or [], model=model, provider=provider, **kwargs)
401
409
  except Exception as exc: # noqa: BLE001
402
410
  raise InvalidParams(str(exc)) from exc
403
411
  if post:
@@ -4,8 +4,13 @@ routing; the sidecar drains and formats registry events."""
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
+ import json
7
8
  import logging
8
9
  import queue
10
+ import sqlite3
11
+ import time
12
+ from contextlib import closing
13
+ from pathlib import Path
9
14
 
10
15
  from ..errors import InvalidParams
11
16
  from ..home import profile_home_param, scoped_home
@@ -89,6 +94,62 @@ def mark_consumed(process_id: str) -> bool:
89
94
  return False
90
95
 
91
96
 
97
+ def _delivery_api():
98
+ """The Agent's durable delivery ledger (``tools.async_delegation``), or None on an Agent without it."""
99
+ try:
100
+ from tools import async_delegation
101
+ except Exception: # noqa: BLE001
102
+ return None
103
+ names = ("claim_event_delivery", "complete_event_delivery", "release_event_delivery")
104
+ return async_delegation if all(callable(getattr(async_delegation, n, None)) for n in names) else None
105
+
106
+
107
+ def _ledger_event(evt: dict) -> dict:
108
+ return {k: v for k, v in evt.items() if k not in ("process_id", "consumed")}
109
+
110
+
111
+ def claim_delivery(evt: dict, consumer: str) -> str | None:
112
+ """Claim a durable delegation completion before delivering it (TAL-459).
113
+
114
+ ``None``: another consumer holds it, or the ledger already delivered or dropped it, so do not deliver.
115
+ ``""``: nothing durable to acknowledge (interim notice, legacy event, or an Agent without the ledger)."""
116
+ api = _delivery_api()
117
+ if api is None:
118
+ return ""
119
+ claim_id = api.claim_event_delivery(_ledger_event(evt), consumer)
120
+ return None if claim_id is None else str(claim_id)
121
+
122
+
123
+ def complete_delivery(evt: dict, claim_id: str) -> bool:
124
+ api = _delivery_api()
125
+ if api is None or not claim_id:
126
+ return False
127
+ api.complete_event_delivery(_ledger_event(evt), claim_id)
128
+ return True
129
+
130
+
131
+ def release_delivery(evt: dict, claim_id: str) -> bool:
132
+ api = _delivery_api()
133
+ if api is None or not claim_id:
134
+ return False
135
+ api.release_event_delivery(_ledger_event(evt), claim_id)
136
+ return True
137
+
138
+
139
+ def defer_delivery(evt: dict, claim_id: str) -> bool:
140
+ """Hand back a claim the target never admitted (its session was busy) without spending a delivery attempt;
141
+ the Agent drops a row for good after its attempt budget, so a busy session must not burn it."""
142
+ api = _delivery_api()
143
+ if api is None or not claim_id:
144
+ return False
145
+ defer = getattr(api, "defer_completion_delivery", None)
146
+ if not callable(defer) or evt.get("type") != "async_delegation" or not evt.get("delegation_id"):
147
+ api.release_event_delivery(_ledger_event(evt), claim_id)
148
+ return True
149
+ defer(str(evt["delegation_id"]), claim_id)
150
+ return True
151
+
152
+
92
153
  def format_notification(evt: dict) -> str:
93
154
  if evt.get("type") == "async_delegation":
94
155
  try:
@@ -122,6 +183,210 @@ def list_sessions() -> list[dict]:
122
183
  return out
123
184
 
124
185
 
186
+ _LEDGER_COLUMNS = "delegation_id, origin_ui_session_id, state, dispatched_at, completed_at, updated_at, task_json, result_json, event_json IS NOT NULL"
187
+ _LEDGER_LIMIT = 200
188
+
189
+
190
+ def _json(text) -> dict:
191
+ try:
192
+ value = json.loads(text) if text else {}
193
+ except (TypeError, ValueError):
194
+ return {}
195
+ return value if isinstance(value, dict) else {}
196
+
197
+
198
+ def _unit_goals(task: dict) -> list[str]:
199
+ goals = task.get("goals") if isinstance(task.get("goals"), list) else None
200
+ indexes = task.get("task_indexes") if isinstance(task.get("task_indexes"), list) else None
201
+ if goals and indexes:
202
+ return [str(goals[i]) for i in indexes if isinstance(i, int) and 0 <= i < len(goals)]
203
+ if goals:
204
+ return [str(g) for g in goals]
205
+ return [str(task.get("goal") or "")]
206
+
207
+
208
+ def _child_statuses(result: dict) -> list[str]:
209
+ """Per-subagent outcome of a finished unit: a batch carries ``results``, a single task is its own result."""
210
+ if isinstance(result.get("results"), list):
211
+ return [str(r.get("status") or "") for r in result["results"] if isinstance(r, dict)]
212
+ return [str(result.get("status") or "")] if result else []
213
+
214
+
215
+ def _ledger_rows(home: Path, session_ids: list[str]) -> list[dict]:
216
+ db_path = home / "state.db"
217
+ if not db_path.exists() or not session_ids:
218
+ return []
219
+ marks = ",".join("?" * len(session_ids))
220
+ with closing(sqlite3.connect(f"{db_path.resolve().as_uri()}?mode=ro", uri=True, timeout=5)) as conn:
221
+ try:
222
+ rows = conn.execute(f"SELECT {_LEDGER_COLUMNS} FROM async_delegations WHERE origin_ui_session_id IN ({marks}) ORDER BY dispatched_at DESC LIMIT ?", [*session_ids, _LEDGER_LIMIT]).fetchall()
223
+ except sqlite3.OperationalError: # an Agent that never delegated has no ledger table yet
224
+ return []
225
+ out = []
226
+ for delegation_id, origin, state, dispatched_at, completed_at, updated_at, task_json, result_json, has_event in rows:
227
+ task, result = _json(task_json), _json(result_json)
228
+ goals = _unit_goals(task)
229
+ out.append({
230
+ "delegation_id": delegation_id, "origin_ui_session_id": origin, "state": state, "dispatched_at": dispatched_at,
231
+ "completed_at": completed_at, "updated_at": updated_at, "goals": goals,
232
+ "child_statuses": _child_statuses(result), "has_result": bool(has_event),
233
+ })
234
+ return out
235
+
236
+
237
+ def _live_delegations() -> dict[str, dict]:
238
+ try:
239
+ from tools.async_delegation import list_async_delegations
240
+ items = list_async_delegations()
241
+ except Exception: # noqa: BLE001 - an Agent without the live registry reports from the ledger alone
242
+ return {}
243
+ return {str(i.get("delegation_id")): {"status": str(i.get("status") or "")} for i in items if isinstance(i, dict) and i.get("delegation_id")}
244
+
245
+
246
+ def _owned_processes(session_ids: set[str]) -> list[dict]:
247
+ """Notified or watched processes a WebUI session started (``session_key`` is the WebUI session id)."""
248
+ registry = _registry()
249
+ if registry is None:
250
+ return []
251
+ try:
252
+ with registry._lock:
253
+ sessions = [*registry._running.values(), *registry._finished.values()]
254
+ except Exception: # noqa: BLE001
255
+ return []
256
+ out = []
257
+ for proc in sessions:
258
+ if str(getattr(proc, "session_key", "") or "") not in session_ids:
259
+ continue
260
+ watched = bool(getattr(proc, "watch_patterns", None))
261
+ if not (getattr(proc, "notify_on_complete", False) or watched):
262
+ continue
263
+ exited = bool(getattr(proc, "exited", False))
264
+ out.append({
265
+ "process_id": str(proc.id), "session_key": str(proc.session_key), "command": str(getattr(proc, "command", "") or "")[:200],
266
+ "started_at": float(getattr(proc, "started_at", 0) or 0) or None, "exited": exited,
267
+ "exited_at": float(getattr(proc, "exited_at", 0) or 0) or None, "exit_code": getattr(proc, "exit_code", None),
268
+ "completion_reason": str(getattr(proc, "completion_reason", "") or ""), "watched": watched,
269
+ })
270
+ return out
271
+
272
+
273
+ _CHILD_WINDOW_S = 5
274
+
275
+
276
+ def _live_children() -> list[dict]:
277
+ """Running subagents from the Agent's in-process registry: the only place a child's session id sits next to its
278
+ delegation id. An Agent without the registry reports none."""
279
+ try:
280
+ from tools.delegate_tool_registry import _active_subagents, _active_subagents_lock
281
+ with _active_subagents_lock:
282
+ records = list(_active_subagents.values())
283
+ except Exception: # noqa: BLE001
284
+ return []
285
+ out = []
286
+ for r in records:
287
+ sid = getattr(r.get("agent"), "session_id", None)
288
+ if isinstance(sid, str) and sid:
289
+ out.append({"delegation_id": str(r.get("delegation_id") or ""), "goal": str(r.get("goal") or ""), "owner": str(r.get("owner_agent_session_id") or ""), "session_id": sid})
290
+ return out
291
+
292
+
293
+ def _ledger_children(home: Path, owners: list[str]) -> list[dict]:
294
+ """Finished subagents' own sessions in state.db: ``source = subagent`` rows tagged ``_delegate_from`` with their
295
+ parent's session, each with its start time and first user message (the child's goal, sent verbatim)."""
296
+ db_path = home / "state.db"
297
+ if not db_path.exists() or not owners:
298
+ return []
299
+ marks = ",".join("?" * len(owners))
300
+ with closing(sqlite3.connect(f"{db_path.resolve().as_uri()}?mode=ro", uri=True, timeout=5)) as conn:
301
+ try:
302
+ # Only these chats' children: a long-lived profile keeps every subagent session it ever ran.
303
+ rows = conn.execute(
304
+ "SELECT s.id, s.model_config, s.started_at, (SELECT m.content FROM messages m WHERE m.session_id = s.id AND m.role = 'user' ORDER BY m.id LIMIT 1)"
305
+ f" FROM sessions s WHERE s.source = 'subagent' AND json_extract(s.model_config, '$._delegate_from') IN ({marks})", owners).fetchall()
306
+ except sqlite3.OperationalError:
307
+ return []
308
+ out = []
309
+ for sid, model_config, started_at, first in rows:
310
+ owner = _json(model_config).get("_delegate_from")
311
+ goal = _goal_text(first)
312
+ if owner in owners and goal:
313
+ out.append({"owner": owner, "session_id": sid, "started_at": started_at, "goal": goal})
314
+ return out
315
+
316
+
317
+ _CONTENT_JSON_PREFIX = "\x00json:"
318
+ _IMAGE_HINTS = "\n\n[Image attached"
319
+
320
+
321
+ def _goal_text(content) -> str:
322
+ """The goal a child was sent, from its stored first message: a task with images is stored as multimodal parts
323
+ (native image input; the goal is the text part) or as the goal followed by image hints (text input)."""
324
+ if not isinstance(content, str):
325
+ return ""
326
+ if content.startswith(_CONTENT_JSON_PREFIX):
327
+ try:
328
+ parts = json.loads(content[len(_CONTENT_JSON_PREFIX):])
329
+ except ValueError:
330
+ return ""
331
+ texts = [p.get("text") for p in parts if isinstance(p, dict) and p.get("type") == "text"] if isinstance(parts, list) else []
332
+ return str(texts[0]) if texts and texts[0] else ""
333
+ return content.split(_IMAGE_HINTS, 1)[0]
334
+
335
+
336
+ def unit_children(unit: dict, owner: str, live: list[dict], finished: list[dict], now: float) -> list[dict]:
337
+ """The subagent sessions one delegation unit ran, as ``{goal, session_id}`` per task, in task order. A live registry
338
+ record is exact (same owner, the unit's call id, the task's goal) and is used once. Without one, finished sessions
339
+ of that owner whose goal matches, started in the unit's window, count only when there are exactly as many as tasks
340
+ with that goal; anything else stays unlinked rather than guessed."""
341
+ unit_id, goals = unit["delegation_id"], [g for g in unit["goals"] if g]
342
+ pool = [r for r in live if r["owner"] == owner and r["delegation_id"] and (unit_id == r["delegation_id"] or unit_id.startswith(f"{r['delegation_id']}-"))]
343
+ linked: list[str | None] = []
344
+ used: set[str] = set()
345
+ for goal in goals:
346
+ record = next((r for r in pool if r["goal"] == goal and r["session_id"] not in used), None)
347
+ if record:
348
+ used.add(record["session_id"])
349
+ linked.append(record["session_id"] if record else None)
350
+ start = (unit.get("dispatched_at") or 0) - _CHILD_WINDOW_S
351
+ end = (unit.get("completed_at") or now) + _CHILD_WINDOW_S
352
+ for goal in {g for g, sid in zip(goals, linked) if sid is None}:
353
+ slots = [i for i, (g, sid) in enumerate(zip(goals, linked)) if g == goal and sid is None]
354
+ matches = sorted((c for c in finished if c["owner"] == owner and c["goal"] == goal and c["session_id"] not in used and start <= (c["started_at"] or 0) <= end), key=lambda c: c["started_at"] or 0)
355
+ if len(matches) == len(slots):
356
+ for i, c in zip(slots, matches):
357
+ linked[i] = c["session_id"]
358
+ return [{"goal": g, "session_id": sid} for g, sid in zip(goals, linked) if sid]
359
+
360
+
361
+ def background_list(home: Path, session_ids: list[str]) -> dict:
362
+ """TAL-372: what the Agent knows about the background work of these WebUI sessions: delegations from the durable
363
+ ledger (with the live registry's status while they run) and notified processes from the process registry.
364
+ TAL-494: each delegation also names the subagent sessions it ran."""
365
+ live = _live_delegations()
366
+ delegations = _ledger_rows(home, session_ids)
367
+ live_children = _live_children()
368
+ finished = _ledger_children(home, session_ids) if delegations else []
369
+ now = time.time()
370
+ for row in delegations:
371
+ row["live_status"] = live.get(row["delegation_id"], {}).get("status")
372
+ row["children"] = unit_children(row, row["origin_ui_session_id"], live_children, finished, now)
373
+ return {"delegations": delegations, "processes": _owned_processes(set(session_ids))}
374
+
375
+
376
+ def delegation_result(home: Path, session_id: str, delegation_id: str) -> str:
377
+ """The full result of a finished delegation unit, as the Agent words it for its own notification."""
378
+ db_path = home / "state.db"
379
+ if not db_path.exists():
380
+ return ""
381
+ with closing(sqlite3.connect(f"{db_path.resolve().as_uri()}?mode=ro", uri=True, timeout=5)) as conn:
382
+ try:
383
+ row = conn.execute("SELECT event_json FROM async_delegations WHERE delegation_id = ? AND origin_ui_session_id = ?", (delegation_id, session_id)).fetchone()
384
+ except sqlite3.OperationalError:
385
+ return ""
386
+ event = _json(row[0]) if row else {}
387
+ return format_notification(event) if event else ""
388
+
389
+
125
390
  def register(registry_) -> None:
126
391
  @registry_.method("process.drain")
127
392
  def drain_(ctx: CallContext, params: dict) -> dict:
@@ -142,6 +407,39 @@ def register(registry_) -> None:
142
407
  raise InvalidParams("process_id is required")
143
408
  return {"ok": mark_consumed(process_id)}
144
409
 
410
+ def _delivery_params(params: dict) -> dict:
411
+ evt = params.get("event")
412
+ if not isinstance(evt, dict):
413
+ raise InvalidParams("event must be an object")
414
+ return evt
415
+
416
+ @registry_.method("process.claim_delivery")
417
+ def claim_(ctx: CallContext, params: dict) -> dict:
418
+ evt = _delivery_params(params)
419
+ consumer = str(params.get("consumer") or "").strip()
420
+ if not consumer:
421
+ raise InvalidParams("consumer is required")
422
+ with scoped_home(profile_home_param(params)):
423
+ return {"claim_id": claim_delivery(evt, consumer)}
424
+
425
+ @registry_.method("process.complete_delivery")
426
+ def complete_(ctx: CallContext, params: dict) -> dict:
427
+ evt = _delivery_params(params)
428
+ with scoped_home(profile_home_param(params)):
429
+ return {"ok": complete_delivery(evt, str(params.get("claim_id") or ""))}
430
+
431
+ @registry_.method("process.release_delivery")
432
+ def release_(ctx: CallContext, params: dict) -> dict:
433
+ evt = _delivery_params(params)
434
+ with scoped_home(profile_home_param(params)):
435
+ return {"ok": release_delivery(evt, str(params.get("claim_id") or ""))}
436
+
437
+ @registry_.method("process.defer_delivery")
438
+ def defer_(ctx: CallContext, params: dict) -> dict:
439
+ evt = _delivery_params(params)
440
+ with scoped_home(profile_home_param(params)):
441
+ return {"ok": defer_delivery(evt, str(params.get("claim_id") or ""))}
442
+
145
443
  @registry_.method("process.format_notification")
146
444
  def format_(ctx: CallContext, params: dict) -> dict:
147
445
  evt = params.get("event")
@@ -149,6 +447,26 @@ def register(registry_) -> None:
149
447
  raise InvalidParams("event must be an object")
150
448
  return {"text": format_notification(evt)}
151
449
 
450
+ def _session_ids(params: dict) -> list[str]:
451
+ ids = params.get("session_ids")
452
+ if not isinstance(ids, list) or not all(isinstance(i, str) and i for i in ids):
453
+ raise InvalidParams("session_ids must be a list of session ids")
454
+ return ids
455
+
456
+ @registry_.method("process.background_list")
457
+ def background_list_(ctx: CallContext, params: dict) -> dict:
458
+ ids = _session_ids(params)
459
+ with scoped_home(profile_home_param(params)) as home:
460
+ return background_list(home, ids)
461
+
462
+ @registry_.method("process.delegation_result")
463
+ def delegation_result_(ctx: CallContext, params: dict) -> dict:
464
+ sid, did = str(params.get("session_id") or ""), str(params.get("delegation_id") or "")
465
+ if not sid or not did:
466
+ raise InvalidParams("session_id and delegation_id are required")
467
+ with scoped_home(profile_home_param(params)) as home:
468
+ return {"text": delegation_result(home, sid, did)}
469
+
152
470
  @registry_.method("process.list")
153
471
  def list_(ctx: CallContext, params: dict) -> dict:
154
472
  with scoped_home(profile_home_param(params)):