@maudecode/talaria-web 0.0.0 → 1.12.1

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 (275) hide show
  1. package/_release.json +20 -0
  2. package/dist/api/chat-router.d.ts +120 -3
  3. package/dist/api/chat-router.js +11 -8
  4. package/dist/api/chat-router.js.map +1 -1
  5. package/dist/api/router.d.ts +10 -0
  6. package/dist/api/router.js +2 -1
  7. package/dist/api/router.js.map +1 -1
  8. package/dist/api/sessions-router.d.ts +691 -40
  9. package/dist/api/sessions-router.js +2 -2
  10. package/dist/api/sessions-router.js.map +1 -1
  11. package/dist/api/settings-router.d.ts +8 -0
  12. package/dist/api/settings-router.js +13 -5
  13. package/dist/api/settings-router.js.map +1 -1
  14. package/dist/api/sse-routes.js +53 -9
  15. package/dist/api/sse-routes.js.map +1 -1
  16. package/dist/api/tools-router.d.ts +420 -0
  17. package/dist/api/tools-router.js +98 -5
  18. package/dist/api/tools-router.js.map +1 -1
  19. package/dist/app.d.ts +1247 -43
  20. package/dist/auth/oidc.js +2 -0
  21. package/dist/auth/oidc.js.map +1 -1
  22. package/dist/auth/store.d.ts +17 -0
  23. package/dist/auth/store.js +77 -19
  24. package/dist/auth/store.js.map +1 -1
  25. package/dist/bin/talaria-web.js +4 -1
  26. package/dist/bin/talaria-web.js.map +1 -1
  27. package/dist/cli/supervise.js +2 -1
  28. package/dist/cli/supervise.js.map +1 -1
  29. package/dist/fs/atomic.d.ts +8 -1
  30. package/dist/fs/atomic.js +52 -3
  31. package/dist/fs/atomic.js.map +1 -1
  32. package/dist/http/context.d.ts +2 -0
  33. package/dist/http/context.js.map +1 -1
  34. package/dist/redact.d.ts +15 -0
  35. package/dist/redact.js +1393 -9
  36. package/dist/redact.js.map +1 -1
  37. package/dist/release.js +3 -1
  38. package/dist/release.js.map +1 -1
  39. package/dist/runtime.js +28 -11
  40. package/dist/runtime.js.map +1 -1
  41. package/dist/server.d.ts +1 -1
  42. package/dist/server.js +11 -5
  43. package/dist/server.js.map +1 -1
  44. package/dist/sessions/anchor.d.ts +59 -5
  45. package/dist/sessions/anchor.js +373 -32
  46. package/dist/sessions/anchor.js.map +1 -1
  47. package/dist/sessions/file-changes.d.ts +15 -0
  48. package/dist/sessions/file-changes.js +58 -0
  49. package/dist/sessions/file-changes.js.map +1 -0
  50. package/dist/sessions/journal.d.ts +6 -1
  51. package/dist/sessions/journal.js +9 -15
  52. package/dist/sessions/journal.js.map +1 -1
  53. package/dist/sessions/list.d.ts +21 -1
  54. package/dist/sessions/list.js +81 -17
  55. package/dist/sessions/list.js.map +1 -1
  56. package/dist/sessions/merge.d.ts +72 -2
  57. package/dist/sessions/merge.js +326 -13
  58. package/dist/sessions/merge.js.map +1 -1
  59. package/dist/sessions/pending.d.ts +11 -2
  60. package/dist/sessions/pending.js +52 -3
  61. package/dist/sessions/pending.js.map +1 -1
  62. package/dist/sessions/service.d.ts +18 -3
  63. package/dist/sessions/service.js +115 -62
  64. package/dist/sessions/service.js.map +1 -1
  65. package/dist/sessions/session.js +1 -1
  66. package/dist/sessions/session.js.map +1 -1
  67. package/dist/sessions/state-db.d.ts +5 -0
  68. package/dist/sessions/state-db.js +34 -0
  69. package/dist/sessions/state-db.js.map +1 -1
  70. package/dist/sessions/streams.d.ts +4 -1
  71. package/dist/sessions/streams.js +3 -0
  72. package/dist/sessions/streams.js.map +1 -1
  73. package/dist/sessions/tool-display.d.ts +21 -0
  74. package/dist/sessions/tool-display.js +113 -0
  75. package/dist/sessions/tool-display.js.map +1 -0
  76. package/dist/sessions/turn.d.ts +39 -7
  77. package/dist/sessions/turn.js +288 -98
  78. package/dist/sessions/turn.js.map +1 -1
  79. package/dist/sessions/window.d.ts +1 -0
  80. package/dist/sessions/window.js +1 -1
  81. package/dist/sessions/window.js.map +1 -1
  82. package/dist/settings.js +5 -0
  83. package/dist/settings.js.map +1 -1
  84. package/dist/sidecar/client.d.ts +1 -1
  85. package/dist/sidecar/client.js +15 -4
  86. package/dist/sidecar/client.js.map +1 -1
  87. package/dist/sidecar/fake.js +3 -2
  88. package/dist/sidecar/fake.js.map +1 -1
  89. package/dist/spa.d.ts +2 -0
  90. package/dist/spa.js +9 -0
  91. package/dist/spa.js.map +1 -1
  92. package/dist/test/harness.d.ts +2 -0
  93. package/dist/test/harness.js +1 -1
  94. package/dist/test/harness.js.map +1 -1
  95. package/dist/tools/crons.js +55 -43
  96. package/dist/tools/crons.js.map +1 -1
  97. package/dist/tools/terminal.d.ts +6 -0
  98. package/dist/tools/terminal.js +24 -3
  99. package/dist/tools/terminal.js.map +1 -1
  100. package/dist/tools/update-notifications.d.ts +120 -0
  101. package/dist/tools/update-notifications.js +387 -0
  102. package/dist/tools/update-notifications.js.map +1 -0
  103. package/dist/tools/updates.d.ts +97 -13
  104. package/dist/tools/updates.js +638 -124
  105. package/dist/tools/updates.js.map +1 -1
  106. package/package.json +2 -2
  107. package/sidecar/agent_dependency.json +4 -3
  108. package/sidecar/scripts/replay_sidecar.py +17 -0
  109. package/sidecar/talaria_sidecar/__init__.py +1 -1
  110. package/sidecar/talaria_sidecar/home.py +51 -19
  111. package/sidecar/talaria_sidecar/methods/chat.py +113 -4
  112. package/sidecar/talaria_sidecar/methods/config.py +5 -3
  113. package/sidecar/talaria_sidecar/runtime.py +3 -9
  114. package/static/dist/FILES.txt +115 -116
  115. package/static/dist/assets/AppShell-m4w420jl.js +1 -0
  116. package/static/dist/assets/ChatPage-cuqcnisv.js +20 -0
  117. package/static/dist/assets/HubRoute-hqgqtm35.js +2 -0
  118. package/static/dist/assets/{Markdown-bt9bqd6a.js → Markdown-j2hlu30v.js} +7 -7
  119. package/static/dist/assets/RightPanel-oid5q557.js +1 -0
  120. package/static/dist/assets/Select-ot2zr0il.js +1 -0
  121. package/static/dist/assets/SessionListPanel-o3sgjnrq.js +1 -0
  122. package/static/dist/assets/SettingsLayout-b0idn649.js +1 -0
  123. package/static/dist/assets/States-htz1oee5.js +1 -0
  124. package/static/dist/assets/Toaster-h49pk1hy.js +1 -0
  125. package/static/dist/assets/_app-h7eaezs7.js +1 -0
  126. package/static/dist/assets/_app.ext._extensionId-h8oaqzsj.js +1 -0
  127. package/static/dist/assets/_app.index-xinao3xh.js +1 -0
  128. package/static/dist/assets/_app.insights-cxplvhkm.js +1 -0
  129. package/static/dist/assets/_app.kanban-hhli8h0h.js +1 -0
  130. package/static/dist/assets/_app.logs-f6srwjfl.js +1 -0
  131. package/static/dist/assets/_app.memory-lntcea85.js +1 -0
  132. package/static/dist/assets/_app.profiles-lftyp6of.js +1 -0
  133. package/static/dist/assets/{_app.session._sessionId-kevi4qd2.js → _app.session._sessionId-hhpcqih4.js} +1 -1
  134. package/static/dist/assets/_app.settings-rs93zpvw.js +1 -0
  135. package/static/dist/assets/_app.settings._section-nafqsbwm.js +1 -0
  136. package/static/dist/assets/_app.skills-jzoi1ivd.js +1 -0
  137. package/static/dist/assets/_app.tasks-ijphsw56.js +2 -0
  138. package/static/dist/assets/_app.todos-emyh8inv.js +1 -0
  139. package/static/dist/assets/_app.workspaces-fwr1cum8.js +1 -0
  140. package/static/dist/assets/{abnfDiagram-VCTEODGH-hvrr25lb.js → abnfDiagram-VCTEODGH-egykbz8h.js} +1 -1
  141. package/static/dist/assets/architecture-7GRP2DOG-l6y92mbn.js +1 -0
  142. package/static/dist/assets/{architectureDiagram-5GKGNRK7-b3r0z8sq.js → architectureDiagram-5GKGNRK7-b2e0wk3y.js} +1 -1
  143. package/static/dist/assets/{blockDiagram-I7D4REHJ-hd3gw0cj.js → blockDiagram-I7D4REHJ-i3cq67dn.js} +1 -1
  144. package/static/dist/assets/{c4Diagram-7LVT6UL2-jlmbjiji.js → c4Diagram-7LVT6UL2-lg1p1s7a.js} +1 -1
  145. package/static/dist/assets/channel-gq8cwu71.js +1 -0
  146. package/static/dist/assets/{chunk-4HAMMTFA-oydbpsin.js → chunk-4HAMMTFA-i0s1s7zi.js} +1 -1
  147. package/static/dist/assets/{chunk-75Z2AOVW-j633otj1.js → chunk-75Z2AOVW-kwinpapo.js} +1 -1
  148. package/static/dist/assets/{chunk-DU6HZSFF-ods2l027.js → chunk-DU6HZSFF-hb8qrbc3.js} +3 -3
  149. package/static/dist/assets/{chunk-F27PBJKO-ln4n2cwt.js → chunk-F27PBJKO-dy2tqtcs.js} +1 -1
  150. package/static/dist/assets/{chunk-GMAD6QVW-fpz133eh.js → chunk-GMAD6QVW-fqox5yvt.js} +1 -1
  151. package/static/dist/assets/{chunk-GVQU2GXP-abs9ixiy.js → chunk-GVQU2GXP-bo3x6ze4.js} +1 -1
  152. package/static/dist/assets/{chunk-IMKFNOWR-j9vukdy1.js → chunk-IMKFNOWR-nxs6rrcq.js} +1 -1
  153. package/static/dist/assets/{chunk-L3NEJ4N5-cfyw87r3.js → chunk-L3NEJ4N5-f9yeq1fi.js} +1 -1
  154. package/static/dist/assets/{chunk-OSK3NFVY-baja78wr.js → chunk-OSK3NFVY-c7df44qv.js} +1 -1
  155. package/static/dist/assets/{chunk-P2QGCYS3-g66xby8h.js → chunk-P2QGCYS3-eyjzf5o5.js} +1 -1
  156. package/static/dist/assets/{chunk-POPQ4Y6H-osqu864t.js → chunk-POPQ4Y6H-iy3hp9rc.js} +1 -1
  157. package/static/dist/assets/{chunk-PWAF6VOD-egejasv1.js → chunk-PWAF6VOD-co2yoefz.js} +1 -1
  158. package/static/dist/assets/{chunk-SHT3W25Y-l014h8zz.js → chunk-SHT3W25Y-obzqmn9k.js} +1 -1
  159. package/static/dist/assets/{chunk-SVP7TREG-n3z10bp3.js → chunk-SVP7TREG-hqwdo6vt.js} +1 -1
  160. package/static/dist/assets/{chunk-TICWLB2K-bzozkjc0.js → chunk-TICWLB2K-l8t1mdfd.js} +1 -1
  161. package/static/dist/assets/classDiagram-ZZMXUADV-fg9sc5r8.js +1 -0
  162. package/static/dist/assets/classDiagram-v2-VYDZK3BY-fg9sc5r8.js +1 -0
  163. package/static/dist/assets/{copied-cjsttec1.js → copied-hurn8vn4.js} +1 -1
  164. package/static/dist/assets/{cynefin-OW5HDTMX-elpt7h1j.js → cynefin-OW5HDTMX-ngx2ihay.js} +1 -1
  165. package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-5mzl5pi2.js → cynefinDiagram-5FMLGOSQ-fiyooys3.js} +1 -1
  166. package/static/dist/assets/{dagre-GXQ25YYZ-ihwlhmfl.js → dagre-GXQ25YYZ-fctryel6.js} +1 -1
  167. package/static/dist/assets/{diagram-S7CK7UJ4-4z90agaz.js → diagram-S7CK7UJ4-m27jopok.js} +1 -1
  168. package/static/dist/assets/{diagram-UQ7AKVKN-eifw0lg7.js → diagram-UQ7AKVKN-ojgkbmp4.js} +1 -1
  169. package/static/dist/assets/{diagram-VSXAHHWV-ksh3sjqv.js → diagram-VSXAHHWV-gfafpgyj.js} +1 -1
  170. package/static/dist/assets/{diagram-VX7I27RA-ett72f71.js → diagram-VX7I27RA-daobicg4.js} +1 -1
  171. package/static/dist/assets/{diagram-Z3DM3KII-l8scp8nu.js → diagram-Z3DM3KII-mi475zvk.js} +1 -1
  172. package/static/dist/assets/{ebnfDiagram-PWID7BFC-f0pxcmit.js → ebnfDiagram-PWID7BFC-daf4zvsy.js} +1 -1
  173. package/static/dist/assets/endpoints-cyjhnrbn.js +64 -0
  174. package/static/dist/assets/{erDiagram-RLTQ6QDP-f47m5xh0.js → erDiagram-RLTQ6QDP-gxfhxvfp.js} +1 -1
  175. package/static/dist/assets/eventmodeling-NTZA5JFV-manga0bq.js +1 -0
  176. package/static/dist/assets/flowDiagram-HODETNUW-bpt13uny.js +1 -0
  177. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-bmktjdi0.js → ganttDiagram-EL5Y4UJY-k3208e0p.js} +1 -1
  178. package/static/dist/assets/{gitGraph-4MIJSDKK-j2ueg258.js → gitGraph-4MIJSDKK-gjuhj4zk.js} +1 -1
  179. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-ms6586v9.js → gitGraphDiagram-WWUBYQGX-jglxvux1.js} +1 -1
  180. package/static/dist/assets/{highlighted-body-KPVGNVTW-ev52d2si.js → highlighted-body-KPVGNVTW-gh24xc9c.js} +1 -1
  181. package/static/dist/assets/index-gsg78cb4.css +2 -0
  182. package/static/dist/assets/{index-hywfjw88.js → index-jb47los5.js} +4 -4
  183. package/static/dist/assets/{info-A6RAGUB7-j717vyt7.js → info-A6RAGUB7-huf0xaoq.js} +1 -1
  184. package/static/dist/assets/{infoDiagram-27XIBGKW-dsbpib36.js → infoDiagram-27XIBGKW-jk3n96ix.js} +1 -1
  185. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-n0sz3i2d.js → ishikawaDiagram-5VMMS53U-o95uo5dx.js} +1 -1
  186. package/static/dist/assets/{journeyDiagram-3NMN7TZE-mcxz1mme.js → journeyDiagram-3NMN7TZE-h5ye2l70.js} +1 -1
  187. package/static/dist/assets/{kanban-definition-UXKFOSKX-n6hq2lyc.js → kanban-definition-UXKFOSKX-lgrpk5yi.js} +1 -1
  188. package/static/dist/assets/{lazyRouteComponent-faquioun.js → lazyRouteComponent-owywngyt.js} +1 -1
  189. package/static/dist/assets/{line-h5xq20s4.js → line-cihmc0pn.js} +1 -1
  190. package/static/dist/assets/login-g063apx6.js +1 -0
  191. package/static/dist/assets/mermaid-HWGCJPDP-okd1mnvj.js +1 -0
  192. package/static/dist/assets/{mermaid-parser.core-n9cuu5ue.js → mermaid-parser.core-d1609aga.js} +3 -3
  193. package/static/dist/assets/{mindmap-definition-YA3MSWOX-gta3u7z4.js → mindmap-definition-YA3MSWOX-lbvtir31.js} +1 -1
  194. package/static/dist/assets/onboarding-ngw0x0pr.js +1 -0
  195. package/static/dist/assets/{packet-AYTQ26CC-bpzuq5n6.js → packet-AYTQ26CC-o4cp5z3k.js} +1 -1
  196. package/static/dist/assets/{pegDiagram-XKGWAZYB-juye13e5.js → pegDiagram-XKGWAZYB-e8xqu1qj.js} +1 -1
  197. package/static/dist/assets/{pie-WAS4IAKB-odhb8buq.js → pie-WAS4IAKB-hm9icuov.js} +1 -1
  198. package/static/dist/assets/{pieDiagram-E7YTZNPT-mgb14ksh.js → pieDiagram-E7YTZNPT-hj96x7hn.js} +1 -1
  199. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-cg36naog.js → quadrantDiagram-AXDQQJYC-80utbxhm.js} +1 -1
  200. package/static/dist/assets/{radar-RG4KPBEZ-lgzfpnef.js → radar-RG4KPBEZ-k39b6100.js} +1 -1
  201. package/static/dist/assets/{railroad-74A4TZTK-gmglc3ua.js → railroad-74A4TZTK-ewyqz67j.js} +1 -1
  202. package/static/dist/assets/railroad-abnf-HS5TGJTU-dx2q0evz.js +1 -0
  203. package/static/dist/assets/railroad-ebnf-LZEXJU2U-l8xt49t5.js +1 -0
  204. package/static/dist/assets/railroad-peg-WCYAUIDC-kuxha9yz.js +1 -0
  205. package/static/dist/assets/{railroadDiagram-O6MQD6OU-cmxwjt73.js → railroadDiagram-O6MQD6OU-iwv9dby8.js} +1 -1
  206. package/static/dist/assets/{refresh-cw-mf192q3f.js → refresh-cw-hjzomwlo.js} +1 -1
  207. package/static/dist/assets/{requirementDiagram-BXWQKSXE-ir0s2mek.js → requirementDiagram-BXWQKSXE-cdgh5jha.js} +1 -1
  208. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-jo7jcdq0.js → sankeyDiagram-P5KCCOFB-dbu6p97m.js} +1 -1
  209. package/static/dist/assets/{saved-g777hp1t.js → saved-feqhycco.js} +1 -1
  210. package/static/dist/assets/{search-i2kfogyx.js → search-n3jdc20p.js} +1 -1
  211. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-n0pf6xk5.js → sequenceDiagram-WJ2MYXX4-b0wchifg.js} +1 -1
  212. package/static/dist/assets/{settings_section_extensions_title-hgvh507l.js → settings_section_extensions_title-mtyviexd.js} +1 -1
  213. package/static/dist/assets/share._token-cwov8uqh.js +2 -0
  214. package/static/dist/assets/sliders-horizontal-i1lseyg0.js +1 -0
  215. package/static/dist/assets/{stateDiagram-D77RDMKH-olbrwyv9.js → stateDiagram-D77RDMKH-mb4b38xh.js} +1 -1
  216. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-cn1fnqrg.js +1 -0
  217. package/static/dist/assets/{swimlanes-42K2YHIH-od14zrw6.js → swimlanes-42K2YHIH-ofrpfwj0.js} +1 -1
  218. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-d652ummv.js +8 -0
  219. package/static/dist/assets/{timeline-definition-24CTP7MA-l0qokmnb.js → timeline-definition-24CTP7MA-hlw0jg6o.js} +1 -1
  220. package/static/dist/assets/todoStore-mfy9lpio.js +1 -0
  221. package/static/dist/assets/{treeView-Q6P3EWNA-hma3ip8m.js → treeView-Q6P3EWNA-d93wr19c.js} +1 -1
  222. package/static/dist/assets/{treemap-WGGIJYW6-bghjng5m.js → treemap-WGGIJYW6-g7ezdedz.js} +1 -1
  223. package/static/dist/assets/{untitled-nfaw0q7m.js → untitled-mqxhd1eh.js} +1 -1
  224. package/static/dist/assets/{useForm-k7fj2zsr.js → useForm-hh33yrwa.js} +1 -1
  225. package/static/dist/assets/useLocale-cr30vl7r.js +1 -0
  226. package/static/dist/assets/{vennDiagram-4TSXK5OY-bo97zf3z.js → vennDiagram-4TSXK5OY-m5vgeba5.js} +1 -1
  227. package/static/dist/assets/{wardley-WFR3VGLG-pbjl6jav.js → wardley-WFR3VGLG-gpao9bxv.js} +1 -1
  228. package/static/dist/assets/{wardleyDiagram-VM6X3IG4-bmy9i09l.js → wardleyDiagram-VM6X3IG4-g2yzwshm.js} +1 -1
  229. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-eercu0mc.js → xychartDiagram-S5SC5T6Z-itakhpic.js} +1 -1
  230. package/static/dist/index.html +1 -1
  231. package/static/dist/sw.js +114 -152
  232. package/static/dist/assets/AppShell-gs0grlbs.js +0 -1
  233. package/static/dist/assets/ChatPage-gi1xlz52.js +0 -18
  234. package/static/dist/assets/HubRoute-qs9vzuh0.js +0 -2
  235. package/static/dist/assets/RightPanel-dn19xu42.js +0 -1
  236. package/static/dist/assets/Select-y7wtf85g.js +0 -1
  237. package/static/dist/assets/SessionListPanel-igwdj9xr.js +0 -1
  238. package/static/dist/assets/SettingsLayout-n6mu0ghg.js +0 -1
  239. package/static/dist/assets/States-oil2issf.js +0 -1
  240. package/static/dist/assets/Toaster-jtn59pjm.js +0 -1
  241. package/static/dist/assets/_app-k4jrkgfa.js +0 -1
  242. package/static/dist/assets/_app.ext._extensionId-mmf1mepm.js +0 -1
  243. package/static/dist/assets/_app.index-mb41nf00.js +0 -1
  244. package/static/dist/assets/_app.insights-mz5cep8m.js +0 -1
  245. package/static/dist/assets/_app.kanban-ocgw506i.js +0 -1
  246. package/static/dist/assets/_app.logs-lstmoju6.js +0 -1
  247. package/static/dist/assets/_app.memory-dishj9ra.js +0 -1
  248. package/static/dist/assets/_app.profiles-dynpvsio.js +0 -1
  249. package/static/dist/assets/_app.settings-c2czm9v4.js +0 -1
  250. package/static/dist/assets/_app.settings._section-exwflld7.js +0 -1
  251. package/static/dist/assets/_app.skills-j5jh2nug.js +0 -1
  252. package/static/dist/assets/_app.tasks-b99d98fo.js +0 -2
  253. package/static/dist/assets/_app.todos-ma8urvn3.js +0 -1
  254. package/static/dist/assets/_app.workspaces-l86cxeod.js +0 -1
  255. package/static/dist/assets/architecture-7GRP2DOG-mqft9w2p.js +0 -1
  256. package/static/dist/assets/channel-ix8cxdld.js +0 -1
  257. package/static/dist/assets/classDiagram-ZZMXUADV-c210y3il.js +0 -1
  258. package/static/dist/assets/classDiagram-v2-VYDZK3BY-c210y3il.js +0 -1
  259. package/static/dist/assets/endpoints-ipnv1hd4.js +0 -64
  260. package/static/dist/assets/eventmodeling-NTZA5JFV-d9b5a0ia.js +0 -1
  261. package/static/dist/assets/flowDiagram-HODETNUW-kfxxsyrk.js +0 -1
  262. package/static/dist/assets/index-bz7g7rhx.css +0 -2
  263. package/static/dist/assets/login-mvjys1ol.js +0 -1
  264. package/static/dist/assets/mermaid-HWGCJPDP-gmnkgf1n.js +0 -1
  265. package/static/dist/assets/onboarding-l6quy6gz.js +0 -1
  266. package/static/dist/assets/railroad-abnf-HS5TGJTU-5mjl5wpv.js +0 -1
  267. package/static/dist/assets/railroad-ebnf-LZEXJU2U-bfram8v8.js +0 -1
  268. package/static/dist/assets/railroad-peg-WCYAUIDC-nwfczn88.js +0 -1
  269. package/static/dist/assets/share._token-il3y6js2.js +0 -2
  270. package/static/dist/assets/sliders-horizontal-kd99ffvl.js +0 -1
  271. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-dnzlmdpu.js +0 -1
  272. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-ck0eqqdp.js +0 -8
  273. package/static/dist/assets/todoStore-la6uyqzj.js +0 -1
  274. package/static/dist/assets/trash-bxz1k9cw.js +0 -1
  275. package/static/dist/assets/useLocale-evhacmv2.js +0 -1
@@ -12,17 +12,19 @@ import { isWithin, resolvePathLikePython } from '../workspace/paths.js';
12
12
  import { SidecarError } from '../sidecar/client.js';
13
13
  import { HttpFailure, markSessionTitleGenerated } from './service.js';
14
14
  import { StreamRegistry, SessionChannels } from './streams.js';
15
- import { PendingPrompts } from './pending.js';
15
+ import { PendingPrompts, clarifyReply } from './pending.js';
16
16
  import { RunJournal } from './journal.js';
17
17
  import { Session, titleFrom } from './session.js';
18
- import { buildActiveTurnToken, redactSessionData, redactString } from '../redact.js';
18
+ import { buildActiveTurnToken, completedToolIndex, publicToolFrame, redactSessionData, redactString, withToolId } from '../redact.js';
19
19
  import { dict } from '../config/agent-config.js';
20
20
  import { ReasoningTitleTracker, reasoningEventPayload } from './reasoning-titles.js';
21
21
  import { messageWindowForDisplay, messagesForLimitedPayload, toolCallsForMessageWindow } from './window.js';
22
22
  import { attachTodoState } from './todo.js';
23
+ import { withSessionWireFlags } from './list.js';
24
+ import { hydrateAnchorActivityScenes, turnTerminalState, withTurnIds } from './anchor.js';
23
25
  import { persistentStateChanges, persistentStateSnapshot } from './state-saved.js';
24
26
  import { maxIterationsFromConfig, maxTokensFromConfig, processWakeupMaxIterations, reasoningConfigFromConfig, webuiEphemeralSystemPrompt, workspaceSystemMessage } from './turn-context.js';
25
- import { assistantReplyAddedAfterCurrentTurn, buildPartialMessage, extractToolCallsFromMessages, injectMaxIterationSummaryFallback, isContextCompressionMarker, isDict, mergeDisplayMessagesAfterAgentResult, messageIdentity, messageText, sanitizeMessagesForApi, sessionLacksFinalAssistantAnswer, splitThinkingFromContent, stripXmlToolCalls, workspaceContextPrefix } from './merge.js';
27
+ import { agentSteerText, assistantReplyAddedAfterCurrentTurn, buildPartialMessage, checkpointTurnStart, extractToolCallsFromMessages, injectMaxIterationSummaryFallback, isContextCompressionMarker, isDict, mergeDisplayMessagesAfterAgentResult, messageIdentity, messageText, pendingUserRow, sanitizeMessagesForApi, sessionLacksFinalAssistantAnswer, splitThinkingFromContent, stoppedTurnContext, stripXmlToolCalls, toolOutcome, withToolCallOutcomes, workspaceContextPrefix } from './merge.js';
26
28
  import { fallbackTitleFromExchange, firstExchangeSnippets, isGenericFallbackTitle, latestExchangeSnippets, looksInvalidGeneratedTitle, sanitizeGeneratedTitle, titleLanguageMismatch, titlePrompts } from './titles.js';
27
29
  import { str } from '../util.js';
28
30
  export const CHAT_LOCK_WAIT_SECONDS = 2;
@@ -34,7 +36,7 @@ export function classifyProviderError(errStr, opts = {}) {
34
36
  return { label: 'Authentication failed', type: 'auth_mismatch', hint: 'The selected model may not be supported by your configured provider or your API key is invalid. Run `hermes model` in your terminal to update credentials, then restart the WebUI.' };
35
37
  const cancelled = ['cancelled by user', 'canceled by user', 'user cancelled', 'user canceled', 'task cancelled', 'task canceled', 'cancellederror'].some((k) => lower.includes(k));
36
38
  if (cancelled)
37
- return { label: 'Task cancelled', type: 'cancelled', hint: cancelledTurnHint() };
39
+ return { label: 'Task cancelled', type: 'cancelled', hint: '' };
38
40
  if (['interrupted by user', 'response interrupted', 'operation interrupted', 'operation was interrupted', 'operation aborted', 'request was aborted', 'aborterror'].some((k) => lower.includes(k))) {
39
41
  return { label: 'Response interrupted', type: 'interrupted', hint: 'The run stopped before a provider response completed. If you did not cancel it, try again.' };
40
42
  }
@@ -60,12 +62,11 @@ export function classifyProviderError(errStr, opts = {}) {
60
62
  return { label: 'No response from provider', type: 'no_response', hint: 'The provider returned no content and no error. This often means a usage/rate limit was hit silently. Check provider status, switch providers via `hermes model`, or try again in a moment.' };
61
63
  return { label: 'Error', type: 'error', hint: '' };
62
64
  }
63
- export function cancelledTurnHint(agentName = 'Hermes') {
64
- return `${agentName} stopped this turn at your request. Send a new message to continue.`;
65
- }
65
+ /** Error classifications that are turn outcomes in their own right; every other one ends the turn as `error`. */
66
+ const CLASSIFIED_OUTCOMES = new Set(['no_response', 'compression_exhausted', 'interrupted', 'cancelled']);
66
67
  export function providerErrorPayload(message, errType, hint = '', redact = true) {
67
68
  const safe = redact ? redactString(message).trim() : message;
68
- const payload = { message: safe || message, type: errType };
69
+ const payload = { message: safe || message, type: errType, terminal_state: CLASSIFIED_OUTCOMES.has(errType) ? errType : 'error' };
69
70
  if (hint)
70
71
  payload.hint = hint;
71
72
  if (safe)
@@ -146,8 +147,17 @@ export class TurnRunner {
146
147
  yoloChains = new Map();
147
148
  abortControllers = new Map();
148
149
  steers = new Map();
150
+ consumedSteers = new Map();
151
+ lastCompletedTool = new Map();
149
152
  /** Streams whose run completed (`done` emitted) and only await title work; a late cancel is a no-op for these. */
150
153
  settledStreams = new Set();
154
+ /**
155
+ * TAL-364: what a Stop needs to write the turn's model context: its starting context, the prompt the Agent got, and
156
+ * whether a settlement already used a canonical checkpoint (which marks the stop boundary; later work never replaces it).
157
+ */
158
+ /** TAL-364: a Stop's in-flight `chat.interrupt` reply (null when it failed), so a worker that settles first can use its checkpoint. */
159
+ interrupts = new Map();
160
+ stopContexts = new Map();
151
161
  constructor(deps) {
152
162
  this.deps = deps;
153
163
  }
@@ -156,6 +166,8 @@ export class TurnRunner {
156
166
  /** Python `_start_chat_stream_for_session`: persist pending state, register the channel, launch the worker. */
157
167
  start(session, opts) {
158
168
  const s = session;
169
+ if (this.deps.updateInProgress?.())
170
+ return { error: 'Web is updating. Retry after it restarts.', _status: 503 };
159
171
  if (this.deps.profileDeleting?.(s.profile ?? null))
160
172
  return { error: `Profile '${str(s.profile)}' is being deleted.`, _status: 409 };
161
173
  const locked = s.active_stream_id;
@@ -190,7 +202,7 @@ export class TurnRunner {
190
202
  s.title = provisional;
191
203
  }
192
204
  if (this.deps.saveMode() === 'eager')
193
- this.checkpointUserMessage(s, opts.msg, attachments, s.pending_started_at, source);
205
+ this.checkpointUserMessage(s, opts.msg, attachments, s.pending_started_at, source, streamId);
194
206
  this.deps.store.save(s);
195
207
  this.registry.writebackOwners.set(s.session_id, streamId);
196
208
  if (wasHiddenEmpty)
@@ -206,7 +218,7 @@ export class TurnRunner {
206
218
  void this.run(s.session_id, streamId, channel, opts).catch((error) => {
207
219
  this.deps.log(`[webui] ERROR turn ${streamId} crashed\n${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
208
220
  });
209
- const response = { stream_id: streamId, session_id: s.session_id, pending_started_at: s.pending_started_at, turn_id: null, title: s.title };
221
+ const response = { stream_id: streamId, session_id: s.session_id, pending_started_at: s.pending_started_at, turn_id: streamId, title: s.title };
210
222
  if (opts.normalizedModel && opts.model)
211
223
  response.effective_model = opts.model;
212
224
  if (opts.modelProvider)
@@ -214,21 +226,11 @@ export class TurnRunner {
214
226
  return response;
215
227
  }
216
228
  /** Python `_checkpoint_user_message_for_eager_session_save`. */
217
- checkpointUserMessage(s, msg, attachments, startedAt, source) {
229
+ checkpointUserMessage(s, msg, attachments, startedAt, source, turnId) {
218
230
  const latest = s.messages[s.messages.length - 1];
219
231
  if (latest?.role === 'user' && messageText(latest.content).split(/\s+/).join(' ') === msg.split(/\s+/).join(' '))
220
232
  return;
221
- const user = { role: 'user', content: msg };
222
- const token = buildActiveTurnToken(s.active_stream_id, startedAt);
223
- if (token)
224
- user._active_turn_token = token;
225
- if (source !== 'webui')
226
- user._source = source;
227
- if (typeof startedAt === 'number' && startedAt > 0)
228
- user.timestamp = startedAt;
229
- if (attachments.length)
230
- user.attachments = [...attachments];
231
- s.messages.push(user);
233
+ s.messages.push(pendingUserRow(msg, attachments, startedAt, source, turnId));
232
234
  }
233
235
  // ── worker ───────────────────────────────────────────────────────────────
234
236
  async run(sessionId, streamId, channel, opts) {
@@ -241,7 +243,7 @@ export class TurnRunner {
241
243
  catch (error) {
242
244
  deps.log(`[webui] WARNING: run journal degraded for stream ${streamId}: ${error.message}`);
243
245
  }
244
- const put = (event, data) => {
246
+ const put = (event, data, meta = {}) => {
245
247
  for (const steerEvent of this.takeSteerEventsBefore(streamId, event))
246
248
  put(steerEvent[0], steerEvent[1]);
247
249
  if (this.registry.cancelled.has(streamId) && !['cancel', 'apperror', 'steer_consumed', 'pending_steer_leftover'].includes(event))
@@ -257,16 +259,20 @@ export class TurnRunner {
257
259
  let eventId = null;
258
260
  if (writer) {
259
261
  try {
260
- eventId = writer.appendSseEvent(event, data).event_id;
262
+ eventId = writer.appendSseEvent(event, data, meta).event_id;
261
263
  }
262
264
  catch (error) {
265
+ this.registry.degradedJournals.add(streamId);
263
266
  deps.log(`[webui] WARNING: run journal append failed for ${streamId}/${event}: ${error.message}`);
264
267
  }
265
268
  }
266
- channel.put([event, data, eventId]);
269
+ channel.put([event, data, eventId, meta.redacted]);
267
270
  if (event === 'done' || event === 'cancel' || event === 'apperror' || event === 'error') {
271
+ // Relay's phase follows the turn outcome, so a background Live Activity ends as the app would end it.
272
+ const state = str(data.terminal_state);
273
+ const phase = state === 'completed' || state === 'tool_limit_reached' ? 'completed' : event === 'cancel' || state === 'cancelled' ? 'cancelled' : 'failed';
268
274
  try {
269
- deps.onTerminal?.(streamId, event === 'done' ? 'completed' : event === 'cancel' ? 'cancelled' : 'failed');
275
+ deps.onTerminal?.(streamId, phase);
270
276
  }
271
277
  catch { /* best effort */ }
272
278
  }
@@ -278,7 +284,7 @@ export class TurnRunner {
278
284
  s = deps.store.get(sessionId);
279
285
  }
280
286
  catch {
281
- put('apperror', { type: 'error', message: 'Session not found', session_id: sessionId });
287
+ put('apperror', { type: 'error', terminal_state: 'error', message: 'Session not found', session_id: sessionId });
282
288
  this.teardown(sessionId, streamId);
283
289
  return;
284
290
  }
@@ -291,19 +297,32 @@ export class TurnRunner {
291
297
  const previousContext = structuredClone(localContext.some((m) => isContextCompressionMarker(m)) ? localContext : deps.service().mergedTranscript(s, localContext));
292
298
  // Python `_sanitize_messages_for_api`: the model never sees display-only rows or a replayed cancelled prompt.
293
299
  const apiHistory = sanitizeMessagesForApi(previousContext);
300
+ const workspaceCtx = workspaceContextPrefix(opts.workspace);
301
+ // Before the first await: a Stop can land at any point after admission.
302
+ // An eager save already put this turn's prompt in the transcript; the Stop fallback appends it once itself.
303
+ this.stopContexts.set(streamId, { previousContext: previousContext.filter((m) => m._turn_id !== streamId), historyLength: apiHistory.length, prompt: workspaceCtx + msgText, msgText, checkpointed: false });
294
304
  const activeTurnToken = buildActiveTurnToken(streamId, s.pending_started_at);
295
305
  const sidecar = deps.sidecar();
296
306
  const partialText = this.registry.partialText.get(streamId) ?? [];
297
307
  const reasoningText = this.registry.reasoningText.get(streamId) ?? [];
298
308
  const liveToolCalls = this.registry.liveToolCalls.get(streamId) ?? [];
309
+ // Each call's public `id`: the Agent's call id, else one minted here that its completion inherits through the pairing.
310
+ const toolIds = new WeakMap();
311
+ let mintedToolIds = 0;
312
+ const mintToolId = () => `tool-${streamId}-${String(++mintedToolIds)}`;
313
+ // When the server received each call's start frame: its completion's `duration` is measured from it.
314
+ const toolStartedAt = new WeakMap();
299
315
  let tokenSent = false;
300
316
  let firstTokenAt = null;
301
317
  const titles = new ReasoningTitleTracker();
302
318
  let capturedTerminalError = null;
303
319
  const controller = new AbortController();
304
320
  this.abortControllers.set(streamId, controller);
305
- const workspaceCtx = workspaceContextPrefix(opts.workspace);
306
321
  const userMessage = await this.buildUserMessage(workspaceCtx, msgText, opts.attachments ?? [], opts.workspace, sessionId, s, opts, controller.signal);
322
+ // The prompt the Agent actually gets, native image parts included.
323
+ const stop = this.stopContexts.get(streamId);
324
+ if (stop)
325
+ stop.prompt = userMessage;
307
326
  if (activeRun)
308
327
  activeRun.phase = 'running';
309
328
  const settledAt = { value: false };
@@ -323,8 +342,8 @@ export class TurnRunner {
323
342
  // Last yield before `chat.start`: a cancel that landed during the attachment, config or YOLO awaits must not
324
343
  // start an Agent turn (the client also refuses an already-aborted signal outright).
325
344
  if (this.registry.cancelled.has(streamId)) {
326
- this.finalizeCancelled(s, streamId, 'Task cancelled before start.', opts.ephemeral);
327
- put('cancel', this.cancelPayload('Cancelled before start'));
345
+ this.finalizeCancelled(s, streamId, opts.ephemeral);
346
+ put('cancel', this.cancelFrame(sessionId));
328
347
  return;
329
348
  }
330
349
  const frozenWorkspace = str(s.created_workspace) || str(s.workspace);
@@ -370,24 +389,34 @@ export class TurnRunner {
370
389
  case 'steer_pending':
371
390
  for (const record of this.takeConsumedSteers(streamId, str(data.text), { keepLeftovers: true }))
372
391
  put('steer_consumed', record);
392
+ this.saveConsumedSteers(sessionId, streamId);
373
393
  return;
374
- case 'tool':
375
- liveToolCalls.push({ name: data.name, args: data.args ?? {}, tid: str(data.tid), done: false });
376
- put('tool', data);
394
+ case 'tool': {
395
+ const call = { name: data.name, args: data.args ?? {}, tid: str(data.tid), done: false };
396
+ liveToolCalls.push(call);
397
+ toolIds.set(call, call.tid || mintToolId());
398
+ toolStartedAt.set(call, deps.now());
399
+ const redacted = deps.redactEnabled();
400
+ put('tool', publicToolFrame(withToolId(data, toolIds.get(call)), redacted), { redacted });
377
401
  return;
378
- case 'tool_complete':
379
- for (let i = liveToolCalls.length - 1; i >= 0; i -= 1) {
380
- const tc = liveToolCalls[i];
381
- if (tc.done)
382
- continue;
383
- if ((str(data.tid) && tc.tid === str(data.tid)) || (!tc.tid && tc.name === data.name)) {
384
- tc.done = true;
385
- tc.snippet = data.preview;
386
- break;
387
- }
402
+ }
403
+ case 'tool_complete': {
404
+ // TAL-313: the server decides failure from the sidecar's raw result, which never leaves the server.
405
+ const { raw_result: rawResult, ...complete } = data;
406
+ complete.is_error = toolOutcome(rawResult).is_error;
407
+ const tc = liveToolCalls[completedToolIndex(liveToolCalls, str(data.tid), data.name)];
408
+ if (tc) {
409
+ const startedAt = toolStartedAt.get(tc);
410
+ if (startedAt !== undefined)
411
+ complete.duration = Math.round(Math.max(0, deps.now() - startedAt) * 1000) / 1000;
412
+ Object.assign(tc, { done: true, snippet: data.preview, is_error: complete.is_error, duration: complete.duration ?? null });
388
413
  }
389
- put('tool_complete', data);
414
+ const id = (tc && toolIds.get(tc)) || str(data.tid) || mintToolId();
415
+ this.lastCompletedTool.set(streamId, id);
416
+ const redacted = deps.redactEnabled();
417
+ put('tool_complete', publicToolFrame(withToolId(complete, id), redacted), { redacted });
390
418
  return;
419
+ }
391
420
  // Python: the live chat frame carries the queue head plus depth, not the entry that just arrived.
392
421
  case 'approval': {
393
422
  deps.pending.submitApproval(sessionId, { ...data, session_id: sessionId });
@@ -419,7 +448,10 @@ export class TurnRunner {
419
448
  });
420
449
  settledAt.value = true;
421
450
  if (this.registry.cancelled.has(streamId) || result.status === 'cancelled') {
422
- this.finalizeCancelled(s, streamId, 'Task cancelled.', opts.ephemeral);
451
+ // The Stop's pre-interrupt snapshot is the boundary, even when its reply lands after this result (the sidecar
452
+ // answers each request on its own thread); without one, the Agent's interrupted result is its canonical transcript.
453
+ const interrupted = await this.interrupts.get(streamId);
454
+ this.finalizeCancelled(s, streamId, opts.ephemeral, interrupted?.checkpoint ?? result.messages);
423
455
  put('cancel', this.cancelFrame(sessionId));
424
456
  return;
425
457
  }
@@ -434,7 +466,7 @@ export class TurnRunner {
434
466
  }
435
467
  opts.onDone?.(answer);
436
468
  // Python `_ephemeral_session_payload`: only role and content leave the server for a btw turn.
437
- put('done', { session: { session_id: sessionId, messages: (result.messages).map((m) => ({ role: m.role, content: m.content })) }, usage: { input_tokens: 0, output_tokens: 0 }, ephemeral: true, answer });
469
+ put('done', { session: { session_id: sessionId, messages: (result.messages).map((m) => ({ role: m.role, content: m.content })) }, usage: { input_tokens: 0, output_tokens: 0 }, ephemeral: true, answer, terminal_state: answer.trim() ? 'completed' : 'no_response' });
438
470
  try {
439
471
  rmSync(deps.store.pathFor(sessionId), { force: true });
440
472
  }
@@ -462,7 +494,7 @@ export class TurnRunner {
462
494
  // sidecar reports `completed` whenever a failed run still carries messages).
463
495
  // Python's second chance: a turn that emitted no new row still counts when the merged transcript it produced
464
496
  // ends on a final answer (the current user row or trailing tool activity makes it "lacking").
465
- const mergedForCheck = () => mergeDisplayMessagesAfterAgentResult(previousMessages, previousContext, resultMessages, msgText, { source: opts.source ?? 'webui', activeTurnToken, now: deps.now() });
497
+ const mergedForCheck = () => mergeDisplayMessagesAfterAgentResult(previousMessages, previousContext, resultMessages, msgText, { source: opts.source ?? 'webui', activeTurnToken, now: deps.now(), turnId: streamId });
466
498
  const assistantAdded = assistantReplyAddedAfterCurrentTurn(resultMessages, previousContext, msgText) || !sessionLacksFinalAssistantAnswer(mergedForCheck());
467
499
  const lastErr = result.error ?? capturedTerminalError ?? '';
468
500
  // Python `_turn_transcript_lacks_final_assistant_answer`: a partial result with no final answer is a silent failure even if tokens streamed.
@@ -471,16 +503,22 @@ export class TurnRunner {
471
503
  const classification = classifyProviderError(lastErr, { silentFailure: !lastErr });
472
504
  const errStr = lastErr || `${classification.label}.`;
473
505
  const payload = providerErrorPayload(errStr, classification.type, classification.hint, deps.redactEnabled());
506
+ // Settle the steers first so the persisted turn carries every consumed one; the Agent's pending text stays a leftover.
507
+ const steerEvents = this.finalizeSteerEvents(streamId, str(result.pending_steer));
474
508
  this.persistError(s, streamId, classification.label, payload, activeTurnToken);
475
509
  payload.session = redactSessionData(this.terminalSessionPayload(s), deps.redactEnabled());
476
510
  payload.session_id = s.session_id;
477
511
  payload.old_session_id = sessionId;
512
+ for (const [event, data] of steerEvents)
513
+ put(event, data);
478
514
  put('apperror', payload);
479
515
  failed = true;
480
516
  return;
481
517
  }
482
518
  // ── settle the transcript ──
483
- s.messages = mergeDisplayMessagesAfterAgentResult(previousMessages, previousContext, resultMessages, msgText, { source: opts.source ?? 'webui', activeTurnToken, now: deps.now() });
519
+ // The Agent's last pending-steer text settles the remaining steers before the turn is written back.
520
+ const steerEvents = this.finalizeSteerEvents(streamId, result.pending_steer);
521
+ s.messages = mergeDisplayMessagesAfterAgentResult(previousMessages, previousContext, resultMessages, msgText, { source: opts.source ?? 'webui', activeTurnToken, now: deps.now(), turnId: streamId });
484
522
  s.context_messages = dedupeContext(resultMessages);
485
523
  for (const m of s.messages) {
486
524
  if (m.role !== 'assistant')
@@ -516,7 +554,7 @@ export class TurnRunner {
516
554
  s.threshold_tokens = result.context.threshold_tokens;
517
555
  if (typeof result.context.last_prompt_tokens === 'number')
518
556
  s.last_prompt_tokens = result.context.last_prompt_tokens;
519
- s.tool_calls = extractToolCallsFromMessages(s.messages, liveToolCalls);
557
+ s.tool_calls = extractToolCallsFromMessages(s.messages, liveToolCalls, s.tool_calls);
520
558
  s.active_stream_id = null;
521
559
  s.pending_user_message = null;
522
560
  s.pending_attachments = [];
@@ -524,14 +562,13 @@ export class TurnRunner {
524
562
  s.pending_user_source = null;
525
563
  const attachments = opts.attachments ?? [];
526
564
  if (attachments.length) {
527
- const names = attachments.map((a) => str(a.name || a.filename)).filter(Boolean);
528
565
  for (let i = s.messages.length - 1; i >= 0; i -= 1) {
529
566
  const m = s.messages[i];
530
567
  if (m.role === 'user') {
531
568
  const content = messageText(m.content);
532
569
  const base = msgText.includes('\n\n[Attached files:') ? (msgText.split('\n\n[Attached files:')[0] ?? '').trim() : msgText;
533
570
  if (content.includes(base.slice(0, 60)) || msgText.includes(content.slice(0, 60)))
534
- m.attachments = names;
571
+ m.attachments = [...attachments];
535
572
  break;
536
573
  }
537
574
  }
@@ -565,9 +602,13 @@ export class TurnRunner {
565
602
  m._turnTps = Math.round((usage.completion_tokens / duration) * 10) / 10;
566
603
  if (result.model)
567
604
  m._usedModel = result.model;
605
+ // The live `done` frame says the tool budget ran out; the persisted row says so too, so a reload agrees.
606
+ if (result.tool_limit_reached && m._turn_id === streamId)
607
+ m._terminal_state = 'tool_limit_reached';
568
608
  break;
569
609
  }
570
610
  }
611
+ this.persistConsumedSteers(s, streamId, previousStartedAt(s, activeRun), now);
571
612
  deps.store.save(s);
572
613
  deps.pending.clearApprovals(sessionId);
573
614
  deps.pending.clearClarifies(sessionId);
@@ -604,13 +645,12 @@ export class TurnRunner {
604
645
  const donePayload = {
605
646
  session: redactSessionData(this.terminalSessionPayload(s), deps.redactEnabled()),
606
647
  usage: doneUsage,
648
+ terminal_state: turnTerminalState(s.messages, streamId),
607
649
  };
608
- if (result.tool_limit_reached) {
609
- donePayload.terminal_state = 'tool_limit_reached';
650
+ if (result.tool_limit_reached)
610
651
  donePayload.terminal_reason = 'max_iterations';
611
- }
612
652
  put('done', donePayload);
613
- for (const [event, payload] of this.finalizeSteerEvents(streamId, result.pending_steer))
653
+ for (const [event, payload] of steerEvents)
614
654
  put(event, payload);
615
655
  // The turn is over: release admission before the title work so a follow-up message is accepted while the
616
656
  // (up to two) title prompts run; the channel and journal stay open for the `title` events (Python retired the
@@ -640,7 +680,7 @@ export class TurnRunner {
640
680
  }
641
681
  failed = true;
642
682
  if (this.registry.cancelled.has(streamId)) {
643
- this.finalizeCancelled(s, streamId, 'Task cancelled.', opts.ephemeral);
683
+ this.finalizeCancelled(s, streamId, opts.ephemeral);
644
684
  put('cancel', this.cancelFrame(sessionId));
645
685
  return;
646
686
  }
@@ -661,6 +701,8 @@ export class TurnRunner {
661
701
  current = s;
662
702
  }
663
703
  if (current.active_stream_id === streamId) {
704
+ for (const [event, data] of this.takeSteerEventsBefore(streamId, 'apperror'))
705
+ put(event, data);
664
706
  this.persistError(current, streamId, classification.label, payload, activeTurnToken);
665
707
  payload.session = redactSessionData(this.terminalSessionPayload(current), deps.redactEnabled());
666
708
  }
@@ -755,8 +797,9 @@ export class TurnRunner {
755
797
  * msg_limit=` (renderable rows, limited payload shape, windowed tool calls, todo state) with the full count.
756
798
  */
757
799
  terminalSessionPayload(s) {
758
- const payload = s.compact({ includeRuntime: true, activeStreamIds: this.registry.liveIds });
759
- const [window, offset] = messageWindowForDisplay(s.messages, TERMINAL_SSE_VISIBLE_MESSAGE_LIMIT, null);
800
+ const payload = withSessionWireFlags(s.compact(), this.registry.liveIds);
801
+ const scened = hydrateAnchorActivityScenes(withToolCallOutcomes(withTurnIds(s.messages), s.tool_calls, s.active_stream_id), s.anchor_activity_scenes, { activeTurnId: s.active_stream_id, clipToolResults: true });
802
+ const [window, offset] = messageWindowForDisplay(scened, TERMINAL_SSE_VISIBLE_MESSAGE_LIMIT, null);
760
803
  const limited = messagesForLimitedPayload(window);
761
804
  payload.messages = limited;
762
805
  payload.message_count = s.messages.length;
@@ -769,7 +812,7 @@ export class TurnRunner {
769
812
  /** Python `_materialize_pending_user_turn_before_error` + error message append + save. */
770
813
  persistError(s, streamId, label, payload, activeTurnToken) {
771
814
  const startedAt = s.pending_started_at;
772
- this.materializePendingUserTurn(s, activeTurnToken);
815
+ this.materializePendingUserTurn(s, activeTurnToken, streamId);
773
816
  const duration = typeof startedAt === 'number' && startedAt > 0 ? Math.max(0, this.deps.now() - startedAt) : null;
774
817
  s.active_stream_id = null;
775
818
  s.pending_user_message = null;
@@ -778,9 +821,11 @@ export class TurnRunner {
778
821
  s.pending_user_source = null;
779
822
  this.appendPartialSnapshot(s, streamId);
780
823
  const hint = str(payload.hint);
781
- const errorMessage = { role: 'assistant', content: `**${label}:** ${str(payload.message) || label}${hint ? `\n\n*${hint}*` : ''}`, timestamp: Math.trunc(this.deps.now()), _error: true };
824
+ const errorMessage = { role: 'assistant', content: `**${label}:** ${str(payload.message) || label}${hint ? `\n\n*${hint}*` : ''}`, timestamp: Math.trunc(this.deps.now()), _error: true, _turn_id: streamId };
782
825
  if (duration !== null)
783
826
  errorMessage._turnDuration = Math.round(duration * 1000) / 1000;
827
+ // The outcome the live frame reported stays on the row, so the settled scene says the same.
828
+ errorMessage._terminal_state = str(payload.terminal_state) || 'error';
784
829
  if (payload.type === 'compression_exhausted') {
785
830
  // Python `stamp_compression_exhausted_recovery`: durable recovery metadata on the session, the marker, and the frame.
786
831
  const recovery = stampCompressionExhaustedRecovery(s, str(payload.message) || label, str(payload.details));
@@ -795,6 +840,7 @@ export class TurnRunner {
795
840
  else if (payload.type === 'interrupted')
796
841
  errorMessage.provider_details_label = 'Interruption details';
797
842
  s.messages.push(errorMessage);
843
+ this.persistConsumedSteers(s, streamId, startedAt, this.deps.now());
798
844
  try {
799
845
  this.deps.store.save(s);
800
846
  }
@@ -805,7 +851,7 @@ export class TurnRunner {
805
851
  this.deps.pending.clearClarifies(s.session_id);
806
852
  this.deps.events.publish('session_error', { profile: s.profile, sessionId: s.session_id });
807
853
  }
808
- materializePendingUserTurn(s, activeTurnToken) {
854
+ materializePendingUserTurn(s, activeTurnToken, turnId) {
809
855
  const pendingText = str(s.pending_user_message);
810
856
  if (!pendingText)
811
857
  return false;
@@ -816,7 +862,7 @@ export class TurnRunner {
816
862
  for (let i = s.messages.length - 1; i >= 0; i -= 1) {
817
863
  const m = s.messages[i];
818
864
  if (m.role === 'user' && m._active_turn_token === activeTurnToken) {
819
- if (!s.messages.slice(i + 1).some((later) => later.role === 'user'))
865
+ if (!s.messages.slice(i + 1).some((later) => later.role === 'user' && !later._steer))
820
866
  return false;
821
867
  break;
822
868
  }
@@ -826,7 +872,7 @@ export class TurnRunner {
826
872
  // Python `_synthesize_user_message_on_cancel`: a worker that already merged this prompt (same text, not older than the pending start) wins.
827
873
  if (last?.role === 'user' && messageText(last.content).trim() === pendingText.trim() && Math.trunc(Number(last.timestamp)) >= Math.trunc(recoveredTs))
828
874
  return false;
829
- const recovered = { role: 'user', content: pendingText, timestamp: recoveredTs, _recovered: true };
875
+ const recovered = { role: 'user', content: pendingText, timestamp: recoveredTs, _recovered: true, _turn_id: turnId };
830
876
  if (source !== 'webui')
831
877
  recovered._source = source;
832
878
  if (attachments.length)
@@ -840,27 +886,33 @@ export class TurnRunner {
840
886
  const partial = buildPartialMessage((this.registry.partialText.get(streamId) ?? []).join(''), (this.registry.reasoningText.get(streamId) ?? []).join(''), this.registry.liveToolCalls.get(streamId) ?? [], this.deps.now());
841
887
  if (!partial)
842
888
  return;
889
+ partial._turn_id = streamId;
843
890
  const key = messageIdentity(partial);
844
891
  if (key !== null && s.messages.some((m) => m._partial && messageIdentity(m) === key))
845
892
  return;
846
893
  s.messages.push(partial);
847
894
  }
848
- cancelPayload(message = 'Cancelled by user') {
849
- return { type: 'cancelled', message, hint: cancelledTurnHint(this.deps.agentName()) };
850
- }
851
- /** Python `_emit_cancel_event`: the terminal frame carries the settled session so tabs can render the partial without a reload. */
895
+ /**
896
+ * Python `_emit_cancel_event`: the terminal frame carries the settled session so tabs can render the partial without a
897
+ * reload. TAL-364: no copy — clients show their one localized status for the `cancelled` outcome.
898
+ */
852
899
  cancelFrame(sessionId) {
853
900
  let snapshot = null;
854
- try {
855
- snapshot = redactSessionData(this.terminalSessionPayload(this.deps.store.get(sessionId)), this.deps.redactEnabled());
856
- }
857
- catch {
858
- snapshot = null;
859
- }
860
- return { ...this.cancelPayload(), status: 'cancelled', session_id: sessionId, ...(snapshot ? { session: snapshot } : {}) };
901
+ if (sessionId)
902
+ try {
903
+ snapshot = redactSessionData(this.terminalSessionPayload(this.deps.store.get(sessionId)), this.deps.redactEnabled());
904
+ }
905
+ catch {
906
+ snapshot = null;
907
+ }
908
+ return { type: 'cancelled', status: 'cancelled', terminal_state: 'cancelled', ...(sessionId ? { session_id: sessionId } : {}), ...(snapshot ? { session: snapshot } : {}) };
861
909
  }
862
- /** Python `_finalize_cancelled_turn`/`_persist_cancelled_turn`: only while this stream still owns writeback. */
863
- finalizeCancelled(s, streamId, message, ephemeral) {
910
+ /**
911
+ * Python `_finalize_cancelled_turn`/`_persist_cancelled_turn`: only while this stream still owns writeback. TAL-364: the
912
+ * turn's model context keeps what the Agent had captured (`checkpoint`, its canonical transcript) and the prose that
913
+ * streamed, so the next request continues from it; the terminal row carries the outcome and no copy.
914
+ */
915
+ finalizeCancelled(s, streamId, ephemeral, checkpoint = null) {
864
916
  if (this.registry.writebackOwners.get(s.session_id) !== streamId)
865
917
  return false;
866
918
  let current = s;
@@ -880,17 +932,44 @@ export class TurnRunner {
880
932
  this.deps.store.sessions.delete(current.session_id);
881
933
  return true;
882
934
  }
883
- if (current.messages.some((m) => m._error && str(m.content).startsWith('**Task cancelled:**')) && current.active_stream_id === null && !current.pending_user_message)
935
+ const stop = this.stopContexts.get(streamId);
936
+ const canonical = stop !== undefined && checkpoint !== null && checkpointTurnStart(checkpoint, stop.msgText, stop.historyLength) !== null;
937
+ const settle = () => {
938
+ if (!stop)
939
+ return null;
940
+ if (canonical)
941
+ stop.checkpointed = true;
942
+ return stoppedTurnContext(stop.previousContext, checkpoint, stop.prompt, stop.msgText, (this.registry.partialText.get(streamId) ?? []).join(''), stop.historyLength);
943
+ };
944
+ if (current.messages.some((m) => isCancelMarker(m)) && current.active_stream_id === null && !current.pending_user_message) {
945
+ // The worker's canonical result can arrive after cancel() settled without a checkpoint (the interrupt reply failed
946
+ // or timed out): it replaces this stream's context and never adds a second Stop row. A settlement that already had
947
+ // one keeps it, so work the Agent finished after the Stop stays out.
948
+ const late = canonical && !stop.checkpointed && current.messages.some((m) => isCancelMarker(m) && m._turn_id === streamId) ? settle() : null;
949
+ if (late) {
950
+ current.context_messages = dedupeContext(late);
951
+ try {
952
+ this.deps.store.save(current);
953
+ }
954
+ catch {
955
+ return false;
956
+ }
957
+ }
884
958
  return true;
885
- this.materializePendingUserTurn(current, buildActiveTurnToken(streamId, current.pending_started_at));
959
+ }
960
+ const startedAt = current.pending_started_at;
961
+ this.materializePendingUserTurn(current, buildActiveTurnToken(streamId, current.pending_started_at), streamId);
962
+ const context = settle();
963
+ if (context)
964
+ current.context_messages = dedupeContext(context);
886
965
  current.active_stream_id = null;
887
966
  current.pending_user_message = null;
888
967
  current.pending_attachments = [];
889
968
  current.pending_started_at = null;
890
969
  current.pending_user_source = null;
891
970
  this.appendPartialSnapshot(current, streamId);
892
- const text = message.trim().endsWith('.') ? message.trim() : `${message.trim()}.`;
893
- current.messages.push({ role: 'assistant', content: `**Task cancelled:** ${text}\n\n*${cancelledTurnHint(this.deps.agentName())}*`, _error: true, provider_details: text, provider_details_label: 'Cancellation details', timestamp: Math.trunc(this.deps.now()) });
971
+ current.messages.push({ role: 'assistant', content: '', _error: true, _terminal_state: 'cancelled', timestamp: Math.trunc(this.deps.now()), _turn_id: streamId });
972
+ this.persistConsumedSteers(current, streamId, startedAt, this.deps.now());
894
973
  try {
895
974
  this.deps.store.save(current);
896
975
  }
@@ -916,7 +995,11 @@ export class TurnRunner {
916
995
  this.writers.delete(streamId);
917
996
  }
918
997
  this.abortControllers.delete(streamId);
998
+ this.interrupts.delete(streamId);
999
+ this.stopContexts.delete(streamId);
919
1000
  this.steers.delete(streamId);
1001
+ this.consumedSteers.delete(streamId);
1002
+ this.lastCompletedTool.delete(streamId);
920
1003
  this.settledStreams.delete(streamId);
921
1004
  this.registry.retire(streamId, this.deps.now());
922
1005
  this.registry.forgetOwner(streamId);
@@ -1079,16 +1162,19 @@ export class TurnRunner {
1079
1162
  }
1080
1163
  // Python `_finalize_webui_steers` drained the Agent's pending steer text at cancel time: the interrupt reply
1081
1164
  // carries it so queued steers settle as consumed / leftover before the terminal row.
1082
- let leftover = '';
1083
- const sidecar = this.deps.sidecar();
1084
- if (sidecar) {
1085
- try {
1086
- leftover = str((await sidecar.call('chat.interrupt', { stream_id: streamId }, { timeoutMs: 5_000 })).pending_steer);
1087
- }
1088
- catch {
1089
- leftover = '';
1090
- }
1165
+ // A duplicate Stop (another tab, a retried request) shares the first interrupt: its snapshot is the boundary, and a
1166
+ // later interrupt finds the run already unwinding.
1167
+ let interrupt = this.interrupts.get(streamId);
1168
+ if (!interrupt) {
1169
+ const sidecar = this.deps.sidecar();
1170
+ interrupt = sidecar ? sidecar.call('chat.interrupt', { stream_id: streamId }, { timeoutMs: 5_000 }).catch(() => null) : Promise.resolve(null);
1171
+ this.interrupts.set(streamId, interrupt);
1091
1172
  }
1173
+ const reply = await interrupt;
1174
+ const leftover = str(reply?.pending_steer);
1175
+ const checkpoint = reply?.checkpoint ?? null;
1176
+ // Settle the steers first so the persisted cancel carries every consumed one.
1177
+ const steerEvents = (this.steers.get(streamId) ?? []).length ? this.finalizeSteerEvents(streamId, leftover) : [];
1092
1178
  if (sessionId) {
1093
1179
  let current = null;
1094
1180
  try {
@@ -1098,7 +1184,7 @@ export class TurnRunner {
1098
1184
  current = null;
1099
1185
  }
1100
1186
  if (current?.active_stream_id === streamId) {
1101
- this.finalizeCancelled(current, streamId, 'Task cancelled.', run?.ephemeral);
1187
+ this.finalizeCancelled(current, streamId, run?.ephemeral, checkpoint);
1102
1188
  }
1103
1189
  }
1104
1190
  // The worker may already have written its terminal row while the interrupt was in flight; the stream is then
@@ -1117,10 +1203,9 @@ export class TurnRunner {
1117
1203
  }
1118
1204
  channel.put([event, data, eventId]);
1119
1205
  };
1120
- if ((this.steers.get(streamId) ?? []).length)
1121
- for (const [event, data] of this.finalizeSteerEvents(streamId, leftover))
1122
- emit(event, data);
1123
- emit('cancel', sessionId ? this.cancelFrame(sessionId) : this.cancelPayload());
1206
+ for (const [event, data] of steerEvents)
1207
+ emit(event, data);
1208
+ emit('cancel', this.cancelFrame(sessionId));
1124
1209
  this.registry.streams.delete(streamId);
1125
1210
  this.registry.liveIds.delete(streamId);
1126
1211
  }
@@ -1196,7 +1281,70 @@ export class TurnRunner {
1196
1281
  return null;
1197
1282
  }
1198
1283
  consumedSteerPayload(record) {
1199
- return { ...record, agent_text: record.text, text: record.display_text || record.text, consumed_at: this.deps.now() };
1284
+ const consumedAt = this.deps.now();
1285
+ const afterToolCallId = this.lastCompletedTool.get(record.stream_id) ?? null;
1286
+ const consumed = this.consumedSteers.get(record.stream_id) ?? [];
1287
+ if (!consumed.some((c) => c.steer_id === record.steer_id)) {
1288
+ consumed.push({ steer_id: record.steer_id, text: record.display_text || record.text, agent_text: record.text, submitted_at: record.created_at, consumed_at: consumedAt, after_tool_call_id: afterToolCallId });
1289
+ this.consumedSteers.set(record.stream_id, consumed);
1290
+ }
1291
+ return { ...record, agent_text: record.text, text: record.display_text || record.text, consumed_at: consumedAt, after_tool_call_id: afterToolCallId };
1292
+ }
1293
+ /**
1294
+ * Consumed steers become display-only `_steer` user rows at their causal place in the turn: after the tool results of
1295
+ * the call that had completed when the Agent took them (else right after the turn's prompt), in consumption order,
1296
+ * each with the phase it ended. Returns where the last phase starts. Replaces the turn's earlier placement, so the
1297
+ * mid-turn save and the terminal one agree. Leftover steers are not persisted.
1298
+ */
1299
+ placeConsumedSteers(s, streamId, startedAt) {
1300
+ const steers = this.consumedSteers.get(streamId) ?? [];
1301
+ if (!steers.length)
1302
+ return null;
1303
+ s.messages = s.messages.filter((m) => !(m._steer && m._turn_id === streamId));
1304
+ let boundary = typeof startedAt === 'number' && startedAt > 0 ? startedAt : steers[0].submitted_at;
1305
+ for (const steer of steers) {
1306
+ const row = {
1307
+ role: 'user', content: steer.text, timestamp: steer.consumed_at, _turn_id: streamId,
1308
+ _steer: { steer_id: steer.steer_id, submitted_at: steer.submitted_at, consumed_at: steer.consumed_at, phase_duration: roundDuration(steer.consumed_at - boundary) },
1309
+ };
1310
+ // An Agent that records the steer it delivered does so at its exact place: that row becomes the steer, not a copy.
1311
+ const agentRows = s.messages.flatMap((m, i) => (m._turn_id === streamId && agentSteerText(m) !== null ? [i] : []));
1312
+ const agentRow = agentRows.find((i) => agentSteerText(s.messages[i]) === steer.agent_text.trim()) ?? agentRows[0];
1313
+ if (agentRow === undefined)
1314
+ s.messages.splice(steerInsertIndex(s.messages, streamId, steer.after_tool_call_id), 0, row);
1315
+ else
1316
+ s.messages[agentRow] = row;
1317
+ boundary = steer.consumed_at;
1318
+ }
1319
+ return boundary;
1320
+ }
1321
+ /** A steer is saved as it enters the stream, so a reload mid-turn already has it; the terminal save re-places it. */
1322
+ saveConsumedSteers(sessionId, streamId) {
1323
+ let s;
1324
+ try {
1325
+ s = this.deps.store.get(sessionId);
1326
+ }
1327
+ catch {
1328
+ return;
1329
+ }
1330
+ if (s.active_stream_id !== streamId || this.placeConsumedSteers(s, streamId, s.pending_started_at) === null)
1331
+ return;
1332
+ try {
1333
+ this.deps.store.save(s, { touchUpdatedAt: false });
1334
+ }
1335
+ catch (error) {
1336
+ this.deps.log(`[webui] WARNING: failed to save consumed steer for ${sessionId}: ${error.message}`);
1337
+ }
1338
+ }
1339
+ /** Terminal placement: the turn's rows are final now, and its last reply carries the final phase. */
1340
+ persistConsumedSteers(s, streamId, startedAt, endedAt) {
1341
+ const boundary = this.placeConsumedSteers(s, streamId, startedAt);
1342
+ this.consumedSteers.delete(streamId);
1343
+ if (boundary === null)
1344
+ return;
1345
+ const last = s.messages.findLast((m) => m.role === 'assistant' && m._turn_id === streamId);
1346
+ if (last)
1347
+ last._final_phase_duration = roundDuration(endedAt - boundary);
1200
1348
  }
1201
1349
  /**
1202
1350
  * Python `_take_consumed_webui_steers` (live, `keepLeftovers`) and `_finalize_webui_steers` (terminal): records the
@@ -1315,11 +1463,18 @@ export class TurnRunner {
1315
1463
  return this.deps.service().setYolo(sessionId, false);
1316
1464
  });
1317
1465
  }
1318
- /** `stale` when no such prompt is queued; `ok:false` without `stale` when the sidecar did not acknowledge (prompt retained). */
1319
- async respondClarify(sessionId, clarifyId, response) {
1466
+ /**
1467
+ * `reply` is the raw Agent reply or keyed step answers the server shapes for the prompt. `stale` when no such
1468
+ * prompt is queued; `invalid` when the answers do not fit its steps; `ok:false` without either when the sidecar
1469
+ * did not acknowledge (prompt retained).
1470
+ */
1471
+ async respondClarify(sessionId, clarifyId, reply) {
1320
1472
  const entry = this.deps.pending.peekClarify(sessionId, clarifyId);
1321
1473
  if (!entry)
1322
1474
  return { ok: false, stale: true };
1475
+ const response = typeof reply === 'string' ? reply : clarifyReply(entry, reply);
1476
+ if (response === null)
1477
+ return { ok: false, invalid: true };
1323
1478
  const sidecar = this.deps.sidecar();
1324
1479
  if (!sidecar)
1325
1480
  return { ok: false, error: 'The Agent sidecar is not running; retry in a moment.' };
@@ -1335,12 +1490,16 @@ export class TurnRunner {
1335
1490
  const { head } = this.deps.pending.resolveClarify(sessionId, str(entry.clarify_id));
1336
1491
  if (head)
1337
1492
  this.emitToSession(sessionId, 'clarify', head);
1338
- return { ok: true };
1493
+ return { ok: true, response };
1339
1494
  }
1340
1495
  }
1341
1496
  function previousStartedAt(s, run) {
1342
1497
  return typeof s.pending_started_at === 'number' && s.pending_started_at > 0 ? s.pending_started_at : (run?.started_at ?? Date.now() / 1000);
1343
1498
  }
1499
+ /** A persisted Stop row: TAL-364 marks its outcome; older rows carry only their English copy. */
1500
+ function isCancelMarker(m) {
1501
+ return m._error === true && (m._terminal_state === 'cancelled' || str(m.content).startsWith('**Task cancelled:**'));
1502
+ }
1344
1503
  function dedupeContext(messages) {
1345
1504
  const out = [];
1346
1505
  let lastKey = null;
@@ -1354,4 +1513,35 @@ function dedupeContext(messages) {
1354
1513
  return out;
1355
1514
  }
1356
1515
  export { HttpFailure };
1516
+ /** Where a consumed steer goes: past the tool results of the call it followed, else past the turn's prompt. */
1517
+ function roundDuration(seconds) {
1518
+ return Math.round(Math.max(0, seconds) * 1000) / 1000;
1519
+ }
1520
+ function steerInsertIndex(messages, turnId, afterToolCallId) {
1521
+ // OpenAI-style `tool_calls` and Anthropic-style `tool_use` content blocks both name the call a steer followed.
1522
+ const callIds = (m) => [
1523
+ ...(Array.isArray(m.tool_calls) ? m.tool_calls : []).map((tc) => str(tc.id) || str(tc.call_id) || str(tc.tool_call_id)),
1524
+ ...(Array.isArray(m.content) ? m.content : []).flatMap((part) => (isDict(part) && part.type === 'tool_use' ? [str(part.id)] : [])),
1525
+ ];
1526
+ // The steer followed that call's result: right after its row (a `tool` row, or a user row of `tool_result` blocks), even
1527
+ // when the same assistant message made later calls; steers already placed there keep their consumption order.
1528
+ const answers = (m) => (m.role === 'tool' && (str(m.tool_call_id) || str(m.tool_use_id)) === afterToolCallId)
1529
+ || (m.role === 'user' && Array.isArray(m.content) && m.content.some((part) => isDict(part) && part.type === 'tool_result' && str(part.tool_use_id) === afterToolCallId));
1530
+ const result = afterToolCallId ? messages.findIndex((m) => m._turn_id === turnId && answers(m)) : -1;
1531
+ if (result !== -1) {
1532
+ let i = result + 1;
1533
+ while (i < messages.length && messages[i]._turn_id === turnId && messages[i]._steer)
1534
+ i += 1;
1535
+ return i;
1536
+ }
1537
+ let at = afterToolCallId ? messages.findIndex((m) => m._turn_id === turnId && m.role === 'assistant' && callIds(m).includes(afterToolCallId)) : -1;
1538
+ if (at === -1)
1539
+ at = messages.findIndex((m) => m._turn_id === turnId && m.role === 'user' && !m._steer);
1540
+ if (at === -1)
1541
+ return messages.length;
1542
+ let i = at + 1;
1543
+ while (i < messages.length && messages[i]._turn_id === turnId && (messages[i].role === 'tool' || messages[i]._steer))
1544
+ i += 1;
1545
+ return i;
1546
+ }
1357
1547
  //# sourceMappingURL=turn.js.map