@maudecode/talaria-web 1.11.0 → 1.13.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 (263) hide show
  1. package/LICENSE +22 -0
  2. package/_release.json +4 -4
  3. package/dist/api/chat-router.d.ts +68 -4
  4. package/dist/api/chat-router.js +11 -8
  5. package/dist/api/chat-router.js.map +1 -1
  6. package/dist/api/router.d.ts +10 -0
  7. package/dist/api/router.js +2 -1
  8. package/dist/api/router.js.map +1 -1
  9. package/dist/api/sessions-router.d.ts +407 -51
  10. package/dist/api/sessions-router.js +2 -2
  11. package/dist/api/sessions-router.js.map +1 -1
  12. package/dist/api/sse-routes.js +52 -8
  13. package/dist/api/sse-routes.js.map +1 -1
  14. package/dist/api/tools-router.d.ts +443 -0
  15. package/dist/api/tools-router.js +99 -4
  16. package/dist/api/tools-router.js.map +1 -1
  17. package/dist/app.d.ts +926 -55
  18. package/dist/auth/oidc.js +2 -0
  19. package/dist/auth/oidc.js.map +1 -1
  20. package/dist/auth/passkeys.js +1 -1
  21. package/dist/auth/passkeys.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/cli/ctl.d.ts +6 -3
  26. package/dist/cli/ctl.js +25 -16
  27. package/dist/cli/ctl.js.map +1 -1
  28. package/dist/cli/launcher.js +2 -2
  29. package/dist/cli/launcher.js.map +1 -1
  30. package/dist/fs/atomic.d.ts +8 -1
  31. package/dist/fs/atomic.js +52 -3
  32. package/dist/fs/atomic.js.map +1 -1
  33. package/dist/http/context.d.ts +2 -0
  34. package/dist/http/context.js.map +1 -1
  35. package/dist/mcp/server.js +1 -1
  36. package/dist/mcp/server.js.map +1 -1
  37. package/dist/redact.d.ts +15 -0
  38. package/dist/redact.js +1393 -9
  39. package/dist/redact.js.map +1 -1
  40. package/dist/release.js +3 -1
  41. package/dist/release.js.map +1 -1
  42. package/dist/runtime.js +20 -9
  43. package/dist/runtime.js.map +1 -1
  44. package/dist/server.d.ts +1 -1
  45. package/dist/server.js +12 -6
  46. package/dist/server.js.map +1 -1
  47. package/dist/sessions/anchor.d.ts +2 -0
  48. package/dist/sessions/anchor.js +22 -17
  49. package/dist/sessions/anchor.js.map +1 -1
  50. package/dist/sessions/export.js +1 -1
  51. package/dist/sessions/export.js.map +1 -1
  52. package/dist/sessions/file-changes.d.ts +15 -0
  53. package/dist/sessions/file-changes.js +58 -0
  54. package/dist/sessions/file-changes.js.map +1 -0
  55. package/dist/sessions/journal.d.ts +6 -1
  56. package/dist/sessions/journal.js +9 -15
  57. package/dist/sessions/journal.js.map +1 -1
  58. package/dist/sessions/list.d.ts +21 -1
  59. package/dist/sessions/list.js +81 -17
  60. package/dist/sessions/list.js.map +1 -1
  61. package/dist/sessions/merge.d.ts +66 -2
  62. package/dist/sessions/merge.js +291 -9
  63. package/dist/sessions/merge.js.map +1 -1
  64. package/dist/sessions/pending.d.ts +11 -2
  65. package/dist/sessions/pending.js +52 -3
  66. package/dist/sessions/pending.js.map +1 -1
  67. package/dist/sessions/relay.js +1 -1
  68. package/dist/sessions/relay.js.map +1 -1
  69. package/dist/sessions/service.d.ts +16 -3
  70. package/dist/sessions/service.js +92 -54
  71. package/dist/sessions/service.js.map +1 -1
  72. package/dist/sessions/state-db.d.ts +5 -0
  73. package/dist/sessions/state-db.js +34 -0
  74. package/dist/sessions/state-db.js.map +1 -1
  75. package/dist/sessions/streams.d.ts +4 -1
  76. package/dist/sessions/streams.js +3 -0
  77. package/dist/sessions/streams.js.map +1 -1
  78. package/dist/sessions/tool-display.d.ts +21 -0
  79. package/dist/sessions/tool-display.js +113 -0
  80. package/dist/sessions/tool-display.js.map +1 -0
  81. package/dist/sessions/turn.d.ts +25 -7
  82. package/dist/sessions/turn.js +153 -90
  83. package/dist/sessions/turn.js.map +1 -1
  84. package/dist/spa.d.ts +2 -0
  85. package/dist/spa.js +9 -0
  86. package/dist/spa.js.map +1 -1
  87. package/dist/test/harness.d.ts +2 -0
  88. package/dist/test/harness.js +1 -1
  89. package/dist/test/harness.js.map +1 -1
  90. package/dist/tools/terminal.d.ts +6 -0
  91. package/dist/tools/terminal.js +24 -3
  92. package/dist/tools/terminal.js.map +1 -1
  93. package/dist/tools/update-notifications.d.ts +133 -0
  94. package/dist/tools/update-notifications.js +407 -0
  95. package/dist/tools/update-notifications.js.map +1 -0
  96. package/dist/tools/updates.d.ts +53 -16
  97. package/dist/tools/updates.js +314 -351
  98. package/dist/tools/updates.js.map +1 -1
  99. package/package.json +3 -2
  100. package/sidecar/scripts/replay_sidecar.py +17 -0
  101. package/sidecar/talaria_sidecar/__init__.py +1 -1
  102. package/sidecar/talaria_sidecar/home.py +39 -16
  103. package/sidecar/talaria_sidecar/methods/chat.py +113 -4
  104. package/static/dist/FILES.txt +116 -116
  105. package/static/dist/THIRD_PARTY_LICENSES.md +5170 -0
  106. package/static/dist/assets/AppShell-irdqom6e.js +1 -0
  107. package/static/dist/assets/ChatPage-ldhqevjn.js +20 -0
  108. package/static/dist/assets/HubRoute-hq24vhj8.js +2 -0
  109. package/static/dist/assets/{Markdown-ftyfzds0.js → Markdown-bx4n7qs4.js} +7 -7
  110. package/static/dist/assets/{RightPanel-febk3dkd.js → RightPanel-ds4majj3.js} +1 -1
  111. package/static/dist/assets/Select-gpqmt6x1.js +1 -0
  112. package/static/dist/assets/SessionListPanel-hkvipik4.js +1 -0
  113. package/static/dist/assets/{SettingsLayout-pdpmhy1o.js → SettingsLayout-oigbg9rl.js} +1 -1
  114. package/static/dist/assets/States-fn4o2wdf.js +1 -0
  115. package/static/dist/assets/Toaster-cwnut53k.js +1 -0
  116. package/static/dist/assets/_app-h6ago6y6.js +1 -0
  117. package/static/dist/assets/_app.ext._extensionId-erqnfff5.js +1 -0
  118. package/static/dist/assets/_app.index-cq2fxmda.js +1 -0
  119. package/static/dist/assets/_app.insights-nzhld9gu.js +1 -0
  120. package/static/dist/assets/_app.kanban-m5uggety.js +1 -0
  121. package/static/dist/assets/_app.logs-d2kk9lza.js +1 -0
  122. package/static/dist/assets/_app.memory-gcmop1e7.js +1 -0
  123. package/static/dist/assets/_app.profiles-e9b2my7x.js +1 -0
  124. package/static/dist/assets/{_app.session._sessionId-n1ggcf5e.js → _app.session._sessionId-ciix6itb.js} +1 -1
  125. package/static/dist/assets/_app.settings-kcjum3c1.js +1 -0
  126. package/static/dist/assets/_app.settings._section-bi4jjex0.js +1 -0
  127. package/static/dist/assets/_app.skills-e4e61l23.js +1 -0
  128. package/static/dist/assets/_app.tasks-f1evdhbl.js +2 -0
  129. package/static/dist/assets/_app.todos-o7ijtk42.js +1 -0
  130. package/static/dist/assets/_app.workspaces-o18voh0w.js +1 -0
  131. package/static/dist/assets/{abnfDiagram-VCTEODGH-i4bgzu83.js → abnfDiagram-VCTEODGH-krt0yaru.js} +1 -1
  132. package/static/dist/assets/architecture-7GRP2DOG-notdhmpv.js +1 -0
  133. package/static/dist/assets/{architectureDiagram-5GKGNRK7-mx21lwqa.js → architectureDiagram-5GKGNRK7-i54t9ywl.js} +1 -1
  134. package/static/dist/assets/{blockDiagram-I7D4REHJ-o1hhchxv.js → blockDiagram-I7D4REHJ-fevbz4mz.js} +1 -1
  135. package/static/dist/assets/{c4Diagram-7LVT6UL2-jygqtvd8.js → c4Diagram-7LVT6UL2-np8eclmc.js} +1 -1
  136. package/static/dist/assets/channel-jpk1nas2.js +1 -0
  137. package/static/dist/assets/{chunk-4HAMMTFA-hjcpubgk.js → chunk-4HAMMTFA-owoi036m.js} +1 -1
  138. package/static/dist/assets/{chunk-75Z2AOVW-eibnex6m.js → chunk-75Z2AOVW-md44zf1n.js} +1 -1
  139. package/static/dist/assets/{chunk-DU6HZSFF-k1tp3qnp.js → chunk-DU6HZSFF-ka3rw8d6.js} +3 -3
  140. package/static/dist/assets/{chunk-F27PBJKO-lvcgn16f.js → chunk-F27PBJKO-iasnjsky.js} +1 -1
  141. package/static/dist/assets/{chunk-GMAD6QVW-ldlzruv1.js → chunk-GMAD6QVW-ex1up6tz.js} +1 -1
  142. package/static/dist/assets/{chunk-GVQU2GXP-hs1mva9d.js → chunk-GVQU2GXP-d4kmmg9p.js} +1 -1
  143. package/static/dist/assets/{chunk-IMKFNOWR-o7eqx3yb.js → chunk-IMKFNOWR-mwy42t85.js} +1 -1
  144. package/static/dist/assets/{chunk-L3NEJ4N5-bp1ofl70.js → chunk-L3NEJ4N5-fv7ry213.js} +1 -1
  145. package/static/dist/assets/{chunk-OSK3NFVY-o8m6mdz5.js → chunk-OSK3NFVY-jg9ir8ac.js} +1 -1
  146. package/static/dist/assets/{chunk-P2QGCYS3-kx83fti2.js → chunk-P2QGCYS3-pe9ms3ua.js} +1 -1
  147. package/static/dist/assets/{chunk-POPQ4Y6H-mwl99e9y.js → chunk-POPQ4Y6H-dimsjrn1.js} +1 -1
  148. package/static/dist/assets/{chunk-PWAF6VOD-oriuokxy.js → chunk-PWAF6VOD-m4hv98dy.js} +1 -1
  149. package/static/dist/assets/{chunk-SHT3W25Y-ab5cf00v.js → chunk-SHT3W25Y-gkjgl6qe.js} +1 -1
  150. package/static/dist/assets/{chunk-SVP7TREG-kqdrpr17.js → chunk-SVP7TREG-bc8qss5l.js} +1 -1
  151. package/static/dist/assets/{chunk-TICWLB2K-kfbnjabk.js → chunk-TICWLB2K-k9wzw38w.js} +1 -1
  152. package/static/dist/assets/classDiagram-ZZMXUADV-hld301gc.js +1 -0
  153. package/static/dist/assets/classDiagram-v2-VYDZK3BY-hld301gc.js +1 -0
  154. package/static/dist/assets/{copied-dp33szhq.js → copied-d5joolw7.js} +1 -1
  155. package/static/dist/assets/{cynefin-OW5HDTMX-mm5sj9s7.js → cynefin-OW5HDTMX-hueklikk.js} +1 -1
  156. package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-nfon5q38.js → cynefinDiagram-5FMLGOSQ-e83o1wr1.js} +1 -1
  157. package/static/dist/assets/{dagre-GXQ25YYZ-dzdp9ypb.js → dagre-GXQ25YYZ-ir1tj2ar.js} +1 -1
  158. package/static/dist/assets/{diagram-S7CK7UJ4-ckxzqo01.js → diagram-S7CK7UJ4-o5218p2q.js} +1 -1
  159. package/static/dist/assets/{diagram-UQ7AKVKN-hw16h1um.js → diagram-UQ7AKVKN-ciad4o1i.js} +1 -1
  160. package/static/dist/assets/{diagram-VSXAHHWV-mu7wu2zg.js → diagram-VSXAHHWV-oharqjbt.js} +1 -1
  161. package/static/dist/assets/{diagram-VX7I27RA-k9uprwtj.js → diagram-VX7I27RA-wkwy62iv.js} +1 -1
  162. package/static/dist/assets/{diagram-Z3DM3KII-csdjzrg9.js → diagram-Z3DM3KII-bl252i7r.js} +1 -1
  163. package/static/dist/assets/{ebnfDiagram-PWID7BFC-n8mbw2jj.js → ebnfDiagram-PWID7BFC-nm0rcly7.js} +1 -1
  164. package/static/dist/assets/endpoints-d8n28yf3.js +64 -0
  165. package/static/dist/assets/{erDiagram-RLTQ6QDP-fsfe1uj0.js → erDiagram-RLTQ6QDP-m1w0vta2.js} +1 -1
  166. package/static/dist/assets/eventmodeling-NTZA5JFV-eaqs19mj.js +1 -0
  167. package/static/dist/assets/flowDiagram-HODETNUW-eli5764n.js +1 -0
  168. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-gsba3iq9.js → ganttDiagram-EL5Y4UJY-czxo6iew.js} +1 -1
  169. package/static/dist/assets/{gitGraph-4MIJSDKK-na86q1pu.js → gitGraph-4MIJSDKK-ip9j1rph.js} +1 -1
  170. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-g72ew3xz.js → gitGraphDiagram-WWUBYQGX-kalxdole.js} +1 -1
  171. package/static/dist/assets/{highlighted-body-KPVGNVTW-okn18cp1.js → highlighted-body-KPVGNVTW-lgsj6fsk.js} +1 -1
  172. package/static/dist/assets/index-l08181vr.js +11 -0
  173. package/static/dist/assets/index-pdtcl20t.css +2 -0
  174. package/static/dist/assets/{info-A6RAGUB7-i01jeltk.js → info-A6RAGUB7-fv4fbc5m.js} +1 -1
  175. package/static/dist/assets/{infoDiagram-27XIBGKW-cayiv6fa.js → infoDiagram-27XIBGKW-kinx5jsl.js} +1 -1
  176. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-g4rf14bv.js → ishikawaDiagram-5VMMS53U-kozvlqdv.js} +1 -1
  177. package/static/dist/assets/{journeyDiagram-3NMN7TZE-fusb2424.js → journeyDiagram-3NMN7TZE-lgpouxoe.js} +1 -1
  178. package/static/dist/assets/{kanban-definition-UXKFOSKX-g831pwlr.js → kanban-definition-UXKFOSKX-b320wo7b.js} +1 -1
  179. package/static/dist/assets/{lazyRouteComponent-ol965zwk.js → lazyRouteComponent-hy8doeye.js} +1 -1
  180. package/static/dist/assets/{line-md6em6gc.js → line-gxfwyhjq.js} +1 -1
  181. package/static/dist/assets/{login-kfclsqd2.js → login-llplsuey.js} +1 -1
  182. package/static/dist/assets/mermaid-HWGCJPDP-f3scrlds.js +1 -0
  183. package/static/dist/assets/{mermaid-parser.core-mq3uns9m.js → mermaid-parser.core-loxeqsu3.js} +3 -3
  184. package/static/dist/assets/{mindmap-definition-YA3MSWOX-oxafrned.js → mindmap-definition-YA3MSWOX-edi534gc.js} +1 -1
  185. package/static/dist/assets/{onboarding-fo6hnkyv.js → onboarding-n1xhlgym.js} +1 -1
  186. package/static/dist/assets/{packet-AYTQ26CC-ggxyvbym.js → packet-AYTQ26CC-gmb3yy71.js} +1 -1
  187. package/static/dist/assets/{pegDiagram-XKGWAZYB-nk7xcqgp.js → pegDiagram-XKGWAZYB-fxy5j8cx.js} +1 -1
  188. package/static/dist/assets/{pie-WAS4IAKB-ito2razh.js → pie-WAS4IAKB-do2pm4s6.js} +1 -1
  189. package/static/dist/assets/{pieDiagram-E7YTZNPT-g02xexar.js → pieDiagram-E7YTZNPT-mgjvb365.js} +1 -1
  190. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-d4jn4xq5.js → quadrantDiagram-AXDQQJYC-dhok6m1j.js} +1 -1
  191. package/static/dist/assets/{radar-RG4KPBEZ-nebvfdzt.js → radar-RG4KPBEZ-n5giw3vb.js} +1 -1
  192. package/static/dist/assets/{railroad-74A4TZTK-ny3gebse.js → railroad-74A4TZTK-h6nfs9bj.js} +1 -1
  193. package/static/dist/assets/railroad-abnf-HS5TGJTU-d5gaaud9.js +1 -0
  194. package/static/dist/assets/railroad-ebnf-LZEXJU2U-ihudnbec.js +1 -0
  195. package/static/dist/assets/railroad-peg-WCYAUIDC-lgpvhel7.js +1 -0
  196. package/static/dist/assets/{railroadDiagram-O6MQD6OU-m09mmu67.js → railroadDiagram-O6MQD6OU-lfwg92r4.js} +1 -1
  197. package/static/dist/assets/{refresh-cw-eitd7yva.js → refresh-cw-bg6oc499.js} +1 -1
  198. package/static/dist/assets/{requirementDiagram-BXWQKSXE-evggpcbk.js → requirementDiagram-BXWQKSXE-c5k5pg07.js} +1 -1
  199. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-bun89039.js → sankeyDiagram-P5KCCOFB-o9auio4y.js} +1 -1
  200. package/static/dist/assets/{saved-culx14ii.js → saved-j5wyhbik.js} +1 -1
  201. package/static/dist/assets/{search-nu6kzq55.js → search-ipa17zlr.js} +1 -1
  202. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-hhs9f09o.js → sequenceDiagram-WJ2MYXX4-pfgodnmk.js} +1 -1
  203. package/static/dist/assets/{settings_section_extensions_title-mj0gcozh.js → settings_section_extensions_title-fcfjf6l8.js} +1 -1
  204. package/static/dist/assets/share._token-i339gqj4.js +2 -0
  205. package/static/dist/assets/{sliders-horizontal-b9ljga1o.js → sliders-horizontal-bfrh4rch.js} +1 -1
  206. package/static/dist/assets/{stateDiagram-D77RDMKH-gi7t1tro.js → stateDiagram-D77RDMKH-m1y2cr3q.js} +1 -1
  207. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hehvfv55.js +1 -0
  208. package/static/dist/assets/{swimlanes-42K2YHIH-ea5urvph.js → swimlanes-42K2YHIH-hzai6fwo.js} +1 -1
  209. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-nfk9mtst.js +8 -0
  210. package/static/dist/assets/{timeline-definition-24CTP7MA-mq64rm3b.js → timeline-definition-24CTP7MA-gwefy9gi.js} +1 -1
  211. package/static/dist/assets/todoStore-fhvdsen0.js +1 -0
  212. package/static/dist/assets/{treeView-Q6P3EWNA-cimbtxlz.js → treeView-Q6P3EWNA-7q91p0dn.js} +1 -1
  213. package/static/dist/assets/{treemap-WGGIJYW6-wig5gi1y.js → treemap-WGGIJYW6-occom39a.js} +1 -1
  214. package/static/dist/assets/{untitled-mbwemvxf.js → untitled-cgw2enra.js} +1 -1
  215. package/static/dist/assets/{useForm-li4bljlv.js → useForm-jwbfla68.js} +1 -1
  216. package/static/dist/assets/useLocale-gieo1fn7.js +1 -0
  217. package/static/dist/assets/{vennDiagram-4TSXK5OY-ez63kh7n.js → vennDiagram-4TSXK5OY-ew4bhldi.js} +1 -1
  218. package/static/dist/assets/{wardley-WFR3VGLG-r7rftz31.js → wardley-WFR3VGLG-njdacy0u.js} +1 -1
  219. package/static/dist/assets/{wardleyDiagram-VM6X3IG4-mvqg9eqc.js → wardleyDiagram-VM6X3IG4-mkvxx7u9.js} +1 -1
  220. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-eqxdfc91.js → xychartDiagram-S5SC5T6Z-8uriotdw.js} +1 -1
  221. package/static/dist/index.html +1 -1
  222. package/static/dist/manifest.webmanifest +1 -1
  223. package/static/dist/sw.js +114 -152
  224. package/static/dist/assets/AppShell-hikzkcql.js +0 -1
  225. package/static/dist/assets/ChatPage-ofqn89h9.js +0 -21
  226. package/static/dist/assets/HubRoute-f2zrs9si.js +0 -2
  227. package/static/dist/assets/Select-lvx8gihl.js +0 -1
  228. package/static/dist/assets/SessionListPanel-o70ljmik.js +0 -1
  229. package/static/dist/assets/States-i46tz8an.js +0 -1
  230. package/static/dist/assets/Toaster-fklyeeyh.js +0 -1
  231. package/static/dist/assets/_app-j3rors25.js +0 -1
  232. package/static/dist/assets/_app.ext._extensionId-c0ey5ec9.js +0 -1
  233. package/static/dist/assets/_app.index-coy9jrto.js +0 -1
  234. package/static/dist/assets/_app.insights-ebd2k0i5.js +0 -1
  235. package/static/dist/assets/_app.kanban-e2p7ypi5.js +0 -1
  236. package/static/dist/assets/_app.logs-ml21mfyz.js +0 -1
  237. package/static/dist/assets/_app.memory-lhu5mm47.js +0 -1
  238. package/static/dist/assets/_app.profiles-egx0fgjo.js +0 -1
  239. package/static/dist/assets/_app.settings-d0vqtyid.js +0 -1
  240. package/static/dist/assets/_app.settings._section-cnzj6pli.js +0 -1
  241. package/static/dist/assets/_app.skills-kkug8obu.js +0 -1
  242. package/static/dist/assets/_app.tasks-nf285d2o.js +0 -2
  243. package/static/dist/assets/_app.todos-dxalqp1e.js +0 -1
  244. package/static/dist/assets/_app.workspaces-bqtduujs.js +0 -1
  245. package/static/dist/assets/architecture-7GRP2DOG-o8zqevhv.js +0 -1
  246. package/static/dist/assets/channel-mw8l793e.js +0 -1
  247. package/static/dist/assets/classDiagram-ZZMXUADV-egajr1wp.js +0 -1
  248. package/static/dist/assets/classDiagram-v2-VYDZK3BY-egajr1wp.js +0 -1
  249. package/static/dist/assets/endpoints-f6ngsyu5.js +0 -64
  250. package/static/dist/assets/eventmodeling-NTZA5JFV-k3zhqcyl.js +0 -1
  251. package/static/dist/assets/flowDiagram-HODETNUW-bdxes51f.js +0 -1
  252. package/static/dist/assets/index-ab0wj9ik.css +0 -2
  253. package/static/dist/assets/index-fd27xooo.js +0 -11
  254. package/static/dist/assets/mermaid-HWGCJPDP-ejk0d88t.js +0 -1
  255. package/static/dist/assets/railroad-abnf-HS5TGJTU-gtd38edw.js +0 -1
  256. package/static/dist/assets/railroad-ebnf-LZEXJU2U-o238oax2.js +0 -1
  257. package/static/dist/assets/railroad-peg-WCYAUIDC-c4acqp2j.js +0 -1
  258. package/static/dist/assets/share._token-k4pcnxqu.js +0 -2
  259. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-jtadbol7.js +0 -1
  260. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-ki7weepw.js +0 -8
  261. package/static/dist/assets/todoStore-azvfuqbv.js +0 -1
  262. package/static/dist/assets/trash-osyo895g.js +0 -1
  263. package/static/dist/assets/useLocale-kdew6auf.js +0 -1
@@ -12,18 +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 { hydrateAnchorActivityScenes, withTurnIds } from './anchor.js';
23
+ import { withSessionWireFlags } from './list.js';
24
+ import { hydrateAnchorActivityScenes, turnTerminalState, withTurnIds } from './anchor.js';
24
25
  import { persistentStateChanges, persistentStateSnapshot } from './state-saved.js';
25
26
  import { maxIterationsFromConfig, maxTokensFromConfig, processWakeupMaxIterations, reasoningConfigFromConfig, webuiEphemeralSystemPrompt, workspaceSystemMessage } from './turn-context.js';
26
- import { agentSteerText, 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';
27
28
  import { fallbackTitleFromExchange, firstExchangeSnippets, isGenericFallbackTitle, latestExchangeSnippets, looksInvalidGeneratedTitle, sanitizeGeneratedTitle, titleLanguageMismatch, titlePrompts } from './titles.js';
28
29
  import { str } from '../util.js';
29
30
  export const CHAT_LOCK_WAIT_SECONDS = 2;
@@ -35,7 +36,7 @@ export function classifyProviderError(errStr, opts = {}) {
35
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.' };
36
37
  const cancelled = ['cancelled by user', 'canceled by user', 'user cancelled', 'user canceled', 'task cancelled', 'task canceled', 'cancellederror'].some((k) => lower.includes(k));
37
38
  if (cancelled)
38
- return { label: 'Task cancelled', type: 'cancelled', hint: cancelledTurnHint() };
39
+ return { label: 'Task cancelled', type: 'cancelled', hint: '' };
39
40
  if (['interrupted by user', 'response interrupted', 'operation interrupted', 'operation was interrupted', 'operation aborted', 'request was aborted', 'aborterror'].some((k) => lower.includes(k))) {
40
41
  return { label: 'Response interrupted', type: 'interrupted', hint: 'The run stopped before a provider response completed. If you did not cancel it, try again.' };
41
42
  }
@@ -61,12 +62,11 @@ export function classifyProviderError(errStr, opts = {}) {
61
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.' };
62
63
  return { label: 'Error', type: 'error', hint: '' };
63
64
  }
64
- export function cancelledTurnHint(agentName = 'Hermes') {
65
- return `${agentName} stopped this turn at your request. Send a new message to continue.`;
66
- }
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']);
67
67
  export function providerErrorPayload(message, errType, hint = '', redact = true) {
68
68
  const safe = redact ? redactString(message).trim() : message;
69
- const payload = { message: safe || message, type: errType };
69
+ const payload = { message: safe || message, type: errType, terminal_state: CLASSIFIED_OUTCOMES.has(errType) ? errType : 'error' };
70
70
  if (hint)
71
71
  payload.hint = hint;
72
72
  if (safe)
@@ -139,8 +139,6 @@ export function explicitTextSignal(cfg) {
139
139
  return !['', 'auto'].includes(provider) || Boolean(str(vision.model).trim()) || Boolean(str(vision.base_url).trim());
140
140
  }
141
141
  export const GATEWAY_APPROVAL_RELAY_UNAVAILABLE = 'Gateway approval could not be relayed because the active run is unavailable. Reopen the session or retry after it reconnects.';
142
- /** Error classifications that are turn outcomes in their own right (not a generic failure). */
143
- const CLASSIFIED_OUTCOMES = new Set(['no_response', 'compression_exhausted', 'interrupted', 'cancelled']);
144
142
  export class TurnRunner {
145
143
  deps;
146
144
  writers = new Map();
@@ -153,6 +151,13 @@ export class TurnRunner {
153
151
  lastCompletedTool = new Map();
154
152
  /** Streams whose run completed (`done` emitted) and only await title work; a late cancel is a no-op for these. */
155
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();
156
161
  constructor(deps) {
157
162
  this.deps = deps;
158
163
  }
@@ -225,17 +230,7 @@ export class TurnRunner {
225
230
  const latest = s.messages[s.messages.length - 1];
226
231
  if (latest?.role === 'user' && messageText(latest.content).split(/\s+/).join(' ') === msg.split(/\s+/).join(' '))
227
232
  return;
228
- const user = { role: 'user', content: msg, _turn_id: turnId };
229
- const token = buildActiveTurnToken(s.active_stream_id, startedAt);
230
- if (token)
231
- user._active_turn_token = token;
232
- if (source !== 'webui')
233
- user._source = source;
234
- if (typeof startedAt === 'number' && startedAt > 0)
235
- user.timestamp = startedAt;
236
- if (attachments.length)
237
- user.attachments = [...attachments];
238
- s.messages.push(user);
233
+ s.messages.push(pendingUserRow(msg, attachments, startedAt, source, turnId));
239
234
  }
240
235
  // ── worker ───────────────────────────────────────────────────────────────
241
236
  async run(sessionId, streamId, channel, opts) {
@@ -248,7 +243,7 @@ export class TurnRunner {
248
243
  catch (error) {
249
244
  deps.log(`[webui] WARNING: run journal degraded for stream ${streamId}: ${error.message}`);
250
245
  }
251
- const put = (event, data) => {
246
+ const put = (event, data, meta = {}) => {
252
247
  for (const steerEvent of this.takeSteerEventsBefore(streamId, event))
253
248
  put(steerEvent[0], steerEvent[1]);
254
249
  if (this.registry.cancelled.has(streamId) && !['cancel', 'apperror', 'steer_consumed', 'pending_steer_leftover'].includes(event))
@@ -264,16 +259,20 @@ export class TurnRunner {
264
259
  let eventId = null;
265
260
  if (writer) {
266
261
  try {
267
- eventId = writer.appendSseEvent(event, data).event_id;
262
+ eventId = writer.appendSseEvent(event, data, meta).event_id;
268
263
  }
269
264
  catch (error) {
265
+ this.registry.degradedJournals.add(streamId);
270
266
  deps.log(`[webui] WARNING: run journal append failed for ${streamId}/${event}: ${error.message}`);
271
267
  }
272
268
  }
273
- channel.put([event, data, eventId]);
269
+ channel.put([event, data, eventId, meta.redacted]);
274
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';
275
274
  try {
276
- deps.onTerminal?.(streamId, event === 'done' ? 'completed' : event === 'cancel' ? 'cancelled' : 'failed');
275
+ deps.onTerminal?.(streamId, phase);
277
276
  }
278
277
  catch { /* best effort */ }
279
278
  }
@@ -285,7 +284,7 @@ export class TurnRunner {
285
284
  s = deps.store.get(sessionId);
286
285
  }
287
286
  catch {
288
- 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 });
289
288
  this.teardown(sessionId, streamId);
290
289
  return;
291
290
  }
@@ -298,19 +297,32 @@ export class TurnRunner {
298
297
  const previousContext = structuredClone(localContext.some((m) => isContextCompressionMarker(m)) ? localContext : deps.service().mergedTranscript(s, localContext));
299
298
  // Python `_sanitize_messages_for_api`: the model never sees display-only rows or a replayed cancelled prompt.
300
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 });
301
304
  const activeTurnToken = buildActiveTurnToken(streamId, s.pending_started_at);
302
305
  const sidecar = deps.sidecar();
303
306
  const partialText = this.registry.partialText.get(streamId) ?? [];
304
307
  const reasoningText = this.registry.reasoningText.get(streamId) ?? [];
305
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();
306
315
  let tokenSent = false;
307
316
  let firstTokenAt = null;
308
317
  const titles = new ReasoningTitleTracker();
309
318
  let capturedTerminalError = null;
310
319
  const controller = new AbortController();
311
320
  this.abortControllers.set(streamId, controller);
312
- const workspaceCtx = workspaceContextPrefix(opts.workspace);
313
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;
314
326
  if (activeRun)
315
327
  activeRun.phase = 'running';
316
328
  const settledAt = { value: false };
@@ -330,8 +342,8 @@ export class TurnRunner {
330
342
  // Last yield before `chat.start`: a cancel that landed during the attachment, config or YOLO awaits must not
331
343
  // start an Agent turn (the client also refuses an already-aborted signal outright).
332
344
  if (this.registry.cancelled.has(streamId)) {
333
- this.finalizeCancelled(s, streamId, 'Task cancelled before start.', opts.ephemeral);
334
- put('cancel', this.cancelPayload('Cancelled before start'));
345
+ this.finalizeCancelled(s, streamId, opts.ephemeral);
346
+ put('cancel', this.cancelFrame(sessionId));
335
347
  return;
336
348
  }
337
349
  const frozenWorkspace = str(s.created_workspace) || str(s.workspace);
@@ -379,25 +391,32 @@ export class TurnRunner {
379
391
  put('steer_consumed', record);
380
392
  this.saveConsumedSteers(sessionId, streamId);
381
393
  return;
382
- case 'tool':
383
- liveToolCalls.push({ name: data.name, args: data.args ?? {}, tid: str(data.tid), done: false });
384
- 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 });
385
401
  return;
386
- case 'tool_complete':
387
- for (let i = liveToolCalls.length - 1; i >= 0; i -= 1) {
388
- const tc = liveToolCalls[i];
389
- if (tc.done)
390
- continue;
391
- if ((str(data.tid) && tc.tid === str(data.tid)) || (!tc.tid && tc.name === data.name)) {
392
- tc.done = true;
393
- tc.snippet = data.preview;
394
- break;
395
- }
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 });
396
413
  }
397
- if (str(data.tid))
398
- this.lastCompletedTool.set(streamId, str(data.tid));
399
- 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 });
400
418
  return;
419
+ }
401
420
  // Python: the live chat frame carries the queue head plus depth, not the entry that just arrived.
402
421
  case 'approval': {
403
422
  deps.pending.submitApproval(sessionId, { ...data, session_id: sessionId });
@@ -429,7 +448,10 @@ export class TurnRunner {
429
448
  });
430
449
  settledAt.value = true;
431
450
  if (this.registry.cancelled.has(streamId) || result.status === 'cancelled') {
432
- 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);
433
455
  put('cancel', this.cancelFrame(sessionId));
434
456
  return;
435
457
  }
@@ -444,7 +466,7 @@ export class TurnRunner {
444
466
  }
445
467
  opts.onDone?.(answer);
446
468
  // Python `_ephemeral_session_payload`: only role and content leave the server for a btw turn.
447
- 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' });
448
470
  try {
449
471
  rmSync(deps.store.pathFor(sessionId), { force: true });
450
472
  }
@@ -532,7 +554,7 @@ export class TurnRunner {
532
554
  s.threshold_tokens = result.context.threshold_tokens;
533
555
  if (typeof result.context.last_prompt_tokens === 'number')
534
556
  s.last_prompt_tokens = result.context.last_prompt_tokens;
535
- s.tool_calls = extractToolCallsFromMessages(s.messages, liveToolCalls);
557
+ s.tool_calls = extractToolCallsFromMessages(s.messages, liveToolCalls, s.tool_calls);
536
558
  s.active_stream_id = null;
537
559
  s.pending_user_message = null;
538
560
  s.pending_attachments = [];
@@ -540,14 +562,13 @@ export class TurnRunner {
540
562
  s.pending_user_source = null;
541
563
  const attachments = opts.attachments ?? [];
542
564
  if (attachments.length) {
543
- const names = attachments.map((a) => str(a.name || a.filename)).filter(Boolean);
544
565
  for (let i = s.messages.length - 1; i >= 0; i -= 1) {
545
566
  const m = s.messages[i];
546
567
  if (m.role === 'user') {
547
568
  const content = messageText(m.content);
548
569
  const base = msgText.includes('\n\n[Attached files:') ? (msgText.split('\n\n[Attached files:')[0] ?? '').trim() : msgText;
549
570
  if (content.includes(base.slice(0, 60)) || msgText.includes(content.slice(0, 60)))
550
- m.attachments = names;
571
+ m.attachments = [...attachments];
551
572
  break;
552
573
  }
553
574
  }
@@ -624,11 +645,10 @@ export class TurnRunner {
624
645
  const donePayload = {
625
646
  session: redactSessionData(this.terminalSessionPayload(s), deps.redactEnabled()),
626
647
  usage: doneUsage,
648
+ terminal_state: turnTerminalState(s.messages, streamId),
627
649
  };
628
- if (result.tool_limit_reached) {
629
- donePayload.terminal_state = 'tool_limit_reached';
650
+ if (result.tool_limit_reached)
630
651
  donePayload.terminal_reason = 'max_iterations';
631
- }
632
652
  put('done', donePayload);
633
653
  for (const [event, payload] of steerEvents)
634
654
  put(event, payload);
@@ -660,7 +680,7 @@ export class TurnRunner {
660
680
  }
661
681
  failed = true;
662
682
  if (this.registry.cancelled.has(streamId)) {
663
- this.finalizeCancelled(s, streamId, 'Task cancelled.', opts.ephemeral);
683
+ this.finalizeCancelled(s, streamId, opts.ephemeral);
664
684
  put('cancel', this.cancelFrame(sessionId));
665
685
  return;
666
686
  }
@@ -777,8 +797,8 @@ export class TurnRunner {
777
797
  * msg_limit=` (renderable rows, limited payload shape, windowed tool calls, todo state) with the full count.
778
798
  */
779
799
  terminalSessionPayload(s) {
780
- const payload = s.compact({ includeRuntime: true, activeStreamIds: this.registry.liveIds });
781
- const scened = hydrateAnchorActivityScenes(withTurnIds(s.messages), s.anchor_activity_scenes, { activeTurnId: s.active_stream_id, clipToolResults: true });
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 });
782
802
  const [window, offset] = messageWindowForDisplay(scened, TERMINAL_SSE_VISIBLE_MESSAGE_LIMIT, null);
783
803
  const limited = messagesForLimitedPayload(window);
784
804
  payload.messages = limited;
@@ -804,9 +824,8 @@ export class TurnRunner {
804
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 };
805
825
  if (duration !== null)
806
826
  errorMessage._turnDuration = Math.round(duration * 1000) / 1000;
807
- // The classified outcome the live frame reported stays on the row, so the settled scene says the same.
808
- if (CLASSIFIED_OUTCOMES.has(str(payload.type)))
809
- errorMessage._terminal_state = str(payload.type);
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';
810
829
  if (payload.type === 'compression_exhausted') {
811
830
  // Python `stamp_compression_exhausted_recovery`: durable recovery metadata on the session, the marker, and the frame.
812
831
  const recovery = stampCompressionExhaustedRecovery(s, str(payload.message) || label, str(payload.details));
@@ -873,22 +892,27 @@ export class TurnRunner {
873
892
  return;
874
893
  s.messages.push(partial);
875
894
  }
876
- cancelPayload(message = 'Cancelled by user') {
877
- return { type: 'cancelled', message, hint: cancelledTurnHint(this.deps.agentName()) };
878
- }
879
- /** 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
+ */
880
899
  cancelFrame(sessionId) {
881
900
  let snapshot = null;
882
- try {
883
- snapshot = redactSessionData(this.terminalSessionPayload(this.deps.store.get(sessionId)), this.deps.redactEnabled());
884
- }
885
- catch {
886
- snapshot = null;
887
- }
888
- 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 } : {}) };
889
909
  }
890
- /** Python `_finalize_cancelled_turn`/`_persist_cancelled_turn`: only while this stream still owns writeback. */
891
- 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) {
892
916
  if (this.registry.writebackOwners.get(s.session_id) !== streamId)
893
917
  return false;
894
918
  let current = s;
@@ -908,18 +932,43 @@ export class TurnRunner {
908
932
  this.deps.store.sessions.delete(current.session_id);
909
933
  return true;
910
934
  }
911
- 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
+ }
912
958
  return true;
959
+ }
913
960
  const startedAt = current.pending_started_at;
914
961
  this.materializePendingUserTurn(current, buildActiveTurnToken(streamId, current.pending_started_at), streamId);
962
+ const context = settle();
963
+ if (context)
964
+ current.context_messages = dedupeContext(context);
915
965
  current.active_stream_id = null;
916
966
  current.pending_user_message = null;
917
967
  current.pending_attachments = [];
918
968
  current.pending_started_at = null;
919
969
  current.pending_user_source = null;
920
970
  this.appendPartialSnapshot(current, streamId);
921
- const text = message.trim().endsWith('.') ? message.trim() : `${message.trim()}.`;
922
- 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()), _turn_id: streamId });
971
+ current.messages.push({ role: 'assistant', content: '', _error: true, _terminal_state: 'cancelled', timestamp: Math.trunc(this.deps.now()), _turn_id: streamId });
923
972
  this.persistConsumedSteers(current, streamId, startedAt, this.deps.now());
924
973
  try {
925
974
  this.deps.store.save(current);
@@ -946,6 +995,8 @@ export class TurnRunner {
946
995
  this.writers.delete(streamId);
947
996
  }
948
997
  this.abortControllers.delete(streamId);
998
+ this.interrupts.delete(streamId);
999
+ this.stopContexts.delete(streamId);
949
1000
  this.steers.delete(streamId);
950
1001
  this.consumedSteers.delete(streamId);
951
1002
  this.lastCompletedTool.delete(streamId);
@@ -1111,16 +1162,17 @@ export class TurnRunner {
1111
1162
  }
1112
1163
  // Python `_finalize_webui_steers` drained the Agent's pending steer text at cancel time: the interrupt reply
1113
1164
  // carries it so queued steers settle as consumed / leftover before the terminal row.
1114
- let leftover = '';
1115
- const sidecar = this.deps.sidecar();
1116
- if (sidecar) {
1117
- try {
1118
- leftover = str((await sidecar.call('chat.interrupt', { stream_id: streamId }, { timeoutMs: 5_000 })).pending_steer);
1119
- }
1120
- catch {
1121
- leftover = '';
1122
- }
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);
1123
1172
  }
1173
+ const reply = await interrupt;
1174
+ const leftover = str(reply?.pending_steer);
1175
+ const checkpoint = reply?.checkpoint ?? null;
1124
1176
  // Settle the steers first so the persisted cancel carries every consumed one.
1125
1177
  const steerEvents = (this.steers.get(streamId) ?? []).length ? this.finalizeSteerEvents(streamId, leftover) : [];
1126
1178
  if (sessionId) {
@@ -1132,7 +1184,7 @@ export class TurnRunner {
1132
1184
  current = null;
1133
1185
  }
1134
1186
  if (current?.active_stream_id === streamId) {
1135
- this.finalizeCancelled(current, streamId, 'Task cancelled.', run?.ephemeral);
1187
+ this.finalizeCancelled(current, streamId, run?.ephemeral, checkpoint);
1136
1188
  }
1137
1189
  }
1138
1190
  // The worker may already have written its terminal row while the interrupt was in flight; the stream is then
@@ -1153,7 +1205,7 @@ export class TurnRunner {
1153
1205
  };
1154
1206
  for (const [event, data] of steerEvents)
1155
1207
  emit(event, data);
1156
- emit('cancel', sessionId ? this.cancelFrame(sessionId) : this.cancelPayload());
1208
+ emit('cancel', this.cancelFrame(sessionId));
1157
1209
  this.registry.streams.delete(streamId);
1158
1210
  this.registry.liveIds.delete(streamId);
1159
1211
  }
@@ -1411,11 +1463,18 @@ export class TurnRunner {
1411
1463
  return this.deps.service().setYolo(sessionId, false);
1412
1464
  });
1413
1465
  }
1414
- /** `stale` when no such prompt is queued; `ok:false` without `stale` when the sidecar did not acknowledge (prompt retained). */
1415
- 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) {
1416
1472
  const entry = this.deps.pending.peekClarify(sessionId, clarifyId);
1417
1473
  if (!entry)
1418
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 };
1419
1478
  const sidecar = this.deps.sidecar();
1420
1479
  if (!sidecar)
1421
1480
  return { ok: false, error: 'The Agent sidecar is not running; retry in a moment.' };
@@ -1431,12 +1490,16 @@ export class TurnRunner {
1431
1490
  const { head } = this.deps.pending.resolveClarify(sessionId, str(entry.clarify_id));
1432
1491
  if (head)
1433
1492
  this.emitToSession(sessionId, 'clarify', head);
1434
- return { ok: true };
1493
+ return { ok: true, response };
1435
1494
  }
1436
1495
  }
1437
1496
  function previousStartedAt(s, run) {
1438
1497
  return typeof s.pending_started_at === 'number' && s.pending_started_at > 0 ? s.pending_started_at : (run?.started_at ?? Date.now() / 1000);
1439
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
+ }
1440
1503
  function dedupeContext(messages) {
1441
1504
  const out = [];
1442
1505
  let lastKey = null;