@maudecode/talaria-web 1.17.2 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (387) hide show
  1. package/_release.json +8 -8
  2. package/dist/api/auth-raw.d.ts +1 -1
  3. package/dist/api/auth-raw.js +4 -4
  4. package/dist/api/auth-raw.js.map +1 -1
  5. package/dist/api/automation-raw.d.ts +6 -0
  6. package/dist/api/automation-raw.js +53 -6
  7. package/dist/api/automation-raw.js.map +1 -1
  8. package/dist/api/automation-router.d.ts +26 -1
  9. package/dist/api/automation-router.js +16 -2
  10. package/dist/api/automation-router.js.map +1 -1
  11. package/dist/api/chat-router.d.ts +649 -15
  12. package/dist/api/chat-router.js +52 -50
  13. package/dist/api/chat-router.js.map +1 -1
  14. package/dist/api/git-router.d.ts +35 -1
  15. package/dist/api/git-router.js +18 -19
  16. package/dist/api/git-router.js.map +1 -1
  17. package/dist/api/raw-routes.d.ts +8 -0
  18. package/dist/api/raw-routes.js +247 -36
  19. package/dist/api/raw-routes.js.map +1 -1
  20. package/dist/api/router.d.ts +10 -0
  21. package/dist/api/router.js +10 -8
  22. package/dist/api/router.js.map +1 -1
  23. package/dist/api/sessions-router.d.ts +1492 -115
  24. package/dist/api/sessions-router.js +134 -8
  25. package/dist/api/sessions-router.js.map +1 -1
  26. package/dist/api/settings-router.d.ts +31 -0
  27. package/dist/api/settings-router.js +8 -3
  28. package/dist/api/settings-router.js.map +1 -1
  29. package/dist/api/sse-routes.d.ts +5 -0
  30. package/dist/api/sse-routes.js +50 -3
  31. package/dist/api/sse-routes.js.map +1 -1
  32. package/dist/api/tools-raw.js +6 -23
  33. package/dist/api/tools-raw.js.map +1 -1
  34. package/dist/api/tools-router.d.ts +356 -15
  35. package/dist/api/tools-router.js +148 -19
  36. package/dist/api/tools-router.js.map +1 -1
  37. package/dist/app.js +35 -15
  38. package/dist/app.js.map +1 -1
  39. package/dist/auth/gate.js +5 -2
  40. package/dist/auth/gate.js.map +1 -1
  41. package/dist/auth/oidc.d.ts +5 -2
  42. package/dist/auth/oidc.js +8 -7
  43. package/dist/auth/oidc.js.map +1 -1
  44. package/dist/bin/talaria-web.js +1 -1
  45. package/dist/bin/talaria-web.js.map +1 -1
  46. package/dist/cli/dotenv.js +1 -0
  47. package/dist/cli/dotenv.js.map +1 -1
  48. package/dist/config/agent-config.js +3 -2
  49. package/dist/config/agent-config.js.map +1 -1
  50. package/dist/config.d.ts +2 -0
  51. package/dist/config.js +5 -1
  52. package/dist/config.js.map +1 -1
  53. package/dist/http/context.d.ts +17 -3
  54. package/dist/http/context.js +31 -4
  55. package/dist/http/context.js.map +1 -1
  56. package/dist/http/csp.d.ts +2 -1
  57. package/dist/http/csp.js +3 -2
  58. package/dist/http/csp.js.map +1 -1
  59. package/dist/profiles/profiles.d.ts +10 -0
  60. package/dist/profiles/profiles.js +40 -2
  61. package/dist/profiles/profiles.js.map +1 -1
  62. package/dist/providers/catalog.d.ts +6 -2
  63. package/dist/providers/catalog.js +30 -11
  64. package/dist/providers/catalog.js.map +1 -1
  65. package/dist/raster-image.d.ts +7 -0
  66. package/dist/raster-image.js +267 -0
  67. package/dist/raster-image.js.map +1 -0
  68. package/dist/redact.js +327 -40
  69. package/dist/redact.js.map +1 -1
  70. package/dist/runtime.js +89 -23
  71. package/dist/runtime.js.map +1 -1
  72. package/dist/server.js +5 -0
  73. package/dist/server.js.map +1 -1
  74. package/dist/sessions/background-tasks.d.ts +2 -9
  75. package/dist/sessions/background-tasks.js +2 -12
  76. package/dist/sessions/background-tasks.js.map +1 -1
  77. package/dist/sessions/cli-sessions.d.ts +15 -4
  78. package/dist/sessions/cli-sessions.js +120 -29
  79. package/dist/sessions/cli-sessions.js.map +1 -1
  80. package/dist/sessions/completions.d.ts +48 -8
  81. package/dist/sessions/completions.js +222 -26
  82. package/dist/sessions/completions.js.map +1 -1
  83. package/dist/sessions/export.d.ts +5 -0
  84. package/dist/sessions/export.js +30 -0
  85. package/dist/sessions/export.js.map +1 -1
  86. package/dist/sessions/gateway-watcher.d.ts +7 -3
  87. package/dist/sessions/gateway-watcher.js +31 -19
  88. package/dist/sessions/gateway-watcher.js.map +1 -1
  89. package/dist/sessions/handoff.d.ts +32 -0
  90. package/dist/sessions/handoff.js +114 -0
  91. package/dist/sessions/handoff.js.map +1 -0
  92. package/dist/sessions/list.d.ts +7 -1
  93. package/dist/sessions/list.js +13 -5
  94. package/dist/sessions/list.js.map +1 -1
  95. package/dist/sessions/merge.d.ts +2 -4
  96. package/dist/sessions/merge.js +8 -15
  97. package/dist/sessions/merge.js.map +1 -1
  98. package/dist/sessions/recovery.d.ts +22 -0
  99. package/dist/sessions/recovery.js +506 -0
  100. package/dist/sessions/recovery.js.map +1 -0
  101. package/dist/sessions/relay.d.ts +21 -2
  102. package/dist/sessions/relay.js +156 -11
  103. package/dist/sessions/relay.js.map +1 -1
  104. package/dist/sessions/service.d.ts +108 -6
  105. package/dist/sessions/service.js +403 -29
  106. package/dist/sessions/service.js.map +1 -1
  107. package/dist/sessions/session.d.ts +6 -0
  108. package/dist/sessions/session.js +40 -2
  109. package/dist/sessions/session.js.map +1 -1
  110. package/dist/sessions/state-db-worker.d.ts +1 -0
  111. package/dist/sessions/state-db-worker.js +33 -0
  112. package/dist/sessions/state-db-worker.js.map +1 -0
  113. package/dist/sessions/state-db.d.ts +61 -0
  114. package/dist/sessions/state-db.js +291 -0
  115. package/dist/sessions/state-db.js.map +1 -1
  116. package/dist/sessions/store.d.ts +7 -0
  117. package/dist/sessions/store.js +31 -22
  118. package/dist/sessions/store.js.map +1 -1
  119. package/dist/sessions/titles.d.ts +2 -0
  120. package/dist/sessions/titles.js +4 -0
  121. package/dist/sessions/titles.js.map +1 -1
  122. package/dist/sessions/tool-display.d.ts +3 -3
  123. package/dist/sessions/tool-display.js +11 -37
  124. package/dist/sessions/tool-display.js.map +1 -1
  125. package/dist/sessions/turn-context.d.ts +1 -1
  126. package/dist/sessions/turn-context.js +11 -7
  127. package/dist/sessions/turn-context.js.map +1 -1
  128. package/dist/sessions/turn.d.ts +37 -2
  129. package/dist/sessions/turn.js +182 -49
  130. package/dist/sessions/turn.js.map +1 -1
  131. package/dist/sessions/wakeup-pause.d.ts +59 -0
  132. package/dist/sessions/wakeup-pause.js +181 -0
  133. package/dist/sessions/wakeup-pause.js.map +1 -0
  134. package/dist/settings.js +5 -7
  135. package/dist/settings.js.map +1 -1
  136. package/dist/sidecar/fake.d.ts +2 -2
  137. package/dist/sidecar/fake.js.map +1 -1
  138. package/dist/startup.d.ts +13 -5
  139. package/dist/startup.js +48 -0
  140. package/dist/startup.js.map +1 -1
  141. package/dist/test/ts-worker-hooks.d.ts +1 -0
  142. package/dist/test/ts-worker-hooks.js +24 -0
  143. package/dist/test/ts-worker-hooks.js.map +1 -0
  144. package/dist/text/diff.d.ts +9 -14
  145. package/dist/text/diff.js +74 -108
  146. package/dist/text/diff.js.map +1 -1
  147. package/dist/tools/crons.js +45 -8
  148. package/dist/tools/crons.js.map +1 -1
  149. package/dist/tools/dashboard-plugins.d.ts +53 -0
  150. package/dist/tools/dashboard-plugins.js +188 -0
  151. package/dist/tools/dashboard-plugins.js.map +1 -0
  152. package/dist/tools/extensions.d.ts +13 -1
  153. package/dist/tools/extensions.js +132 -60
  154. package/dist/tools/extensions.js.map +1 -1
  155. package/dist/tools/health.d.ts +32 -0
  156. package/dist/tools/health.js +111 -19
  157. package/dist/tools/health.js.map +1 -1
  158. package/dist/tools/mcp.d.ts +13 -0
  159. package/dist/tools/mcp.js +88 -0
  160. package/dist/tools/mcp.js.map +1 -1
  161. package/dist/tools/project-os.d.ts +11 -0
  162. package/dist/tools/project-os.js +267 -0
  163. package/dist/tools/project-os.js.map +1 -0
  164. package/dist/tools/skills.d.ts +5 -1
  165. package/dist/tools/skills.js +5 -3
  166. package/dist/tools/skills.js.map +1 -1
  167. package/dist/tools/updates.d.ts +27 -9
  168. package/dist/tools/updates.js +163 -51
  169. package/dist/tools/updates.js.map +1 -1
  170. package/dist/tools/wiki.d.ts +68 -0
  171. package/dist/tools/wiki.js +413 -0
  172. package/dist/tools/wiki.js.map +1 -0
  173. package/dist/workspace/escape.d.ts +61 -0
  174. package/dist/workspace/escape.js +149 -0
  175. package/dist/workspace/escape.js.map +1 -0
  176. package/dist/workspace/extract.d.ts +22 -0
  177. package/dist/workspace/extract.js +308 -0
  178. package/dist/workspace/extract.js.map +1 -0
  179. package/dist/workspace/fs.d.ts +12 -0
  180. package/dist/workspace/fs.js +92 -23
  181. package/dist/workspace/fs.js.map +1 -1
  182. package/dist/workspace/git.d.ts +18 -0
  183. package/dist/workspace/git.js +38 -25
  184. package/dist/workspace/git.js.map +1 -1
  185. package/dist/workspace/media.d.ts +4 -4
  186. package/dist/workspace/media.js +16 -45
  187. package/dist/workspace/media.js.map +1 -1
  188. package/dist/workspace/rollback.d.ts +34 -20
  189. package/dist/workspace/rollback.js +171 -265
  190. package/dist/workspace/rollback.js.map +1 -1
  191. package/dist/workspace/unzip.js +4 -1
  192. package/dist/workspace/unzip.js.map +1 -1
  193. package/dist/workspace/upload.d.ts +7 -4
  194. package/dist/workspace/upload.js +5 -2
  195. package/dist/workspace/upload.js.map +1 -1
  196. package/dist/workspace/worktrees.d.ts +2 -1
  197. package/dist/workspace/worktrees.js +14 -15
  198. package/dist/workspace/worktrees.js.map +1 -1
  199. package/package.json +4 -2
  200. package/sidecar/agent_dependency.json +4 -4
  201. package/sidecar/scripts/replay_sidecar.py +24 -0
  202. package/sidecar/talaria_sidecar/__init__.py +1 -1
  203. package/sidecar/talaria_sidecar/home.py +13 -2
  204. package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
  205. package/sidecar/talaria_sidecar/methods/aux.py +39 -5
  206. package/sidecar/talaria_sidecar/methods/chat.py +9 -5
  207. package/sidecar/talaria_sidecar/methods/checkpoints.py +89 -0
  208. package/sidecar/talaria_sidecar/methods/commands.py +81 -0
  209. package/sidecar/talaria_sidecar/methods/cron.py +29 -2
  210. package/sidecar/talaria_sidecar/methods/gateway.py +32 -15
  211. package/sidecar/talaria_sidecar/methods/kanban.py +28 -7
  212. package/sidecar/talaria_sidecar/methods/mcp.py +1 -1
  213. package/sidecar/talaria_sidecar/methods/process.py +58 -4
  214. package/sidecar/talaria_sidecar/methods/profiles.py +77 -7
  215. package/sidecar/talaria_sidecar/methods/providers.py +27 -8
  216. package/sidecar/talaria_sidecar/methods/state_db.py +39 -5
  217. package/sidecar/talaria_sidecar/runtime.py +17 -1
  218. package/static/dist/FILES.txt +127 -126
  219. package/static/dist/assets/AppShell-g3bkgnvt.js +1 -0
  220. package/static/dist/assets/ChatPage-oo7bdan6.js +25 -0
  221. package/static/dist/assets/Dialog-g355qxhp.js +1 -0
  222. package/static/dist/assets/ExtensionRoute-i2y9fvjm.js +1 -0
  223. package/static/dist/assets/HubRoute-lmkrbw47.js +2 -0
  224. package/static/dist/assets/{Markdown-mdwlvj3w.js → Markdown-j4kut4j2.js} +114 -114
  225. package/static/dist/assets/{RightPanel-ftvrjlcp.js → RightPanel-ohlsdzfl.js} +1 -1
  226. package/static/dist/assets/Select-kddq75qc.js +1 -0
  227. package/static/dist/assets/SessionListPanel-lu3oy0hw.js +1 -0
  228. package/static/dist/assets/{SettingsLayout-jonjefmj.js → SettingsLayout-o9bv4mgb.js} +1 -1
  229. package/static/dist/assets/{States-nmejrwrm.js → States-eq6fe29m.js} +1 -1
  230. package/static/dist/assets/Toaster-pc247u9f.js +1 -0
  231. package/static/dist/assets/{_app-gdidzd94.js → _app-f8n1i0m1.js} +1 -1
  232. package/static/dist/assets/{_app.ext._extensionId-opyh9y0v.js → _app.ext._extensionId-gfbzpoou.js} +1 -1
  233. package/static/dist/assets/_app.index-bzzv80hk.js +1 -0
  234. package/static/dist/assets/_app.insights-dzixbryi.js +1 -0
  235. package/static/dist/assets/_app.kanban-c6hep2us.js +1 -0
  236. package/static/dist/assets/_app.logs-i69qds98.js +1 -0
  237. package/static/dist/assets/_app.memory-kl4vf0gd.js +1 -0
  238. package/static/dist/assets/_app.profiles-b8zinmyp.js +1 -0
  239. package/static/dist/assets/{_app.session._sessionId-bfd4h3id.js → _app.session._sessionId-fn1bhde6.js} +1 -1
  240. package/static/dist/assets/_app.settings-cg8plwmf.js +1 -0
  241. package/static/dist/assets/_app.settings._section-bkwbi9zn.js +3 -0
  242. package/static/dist/assets/_app.skills-cs47sbvs.js +1 -0
  243. package/static/dist/assets/_app.tasks-dwvk64tc.js +1 -0
  244. package/static/dist/assets/_app.todos-c8xjm8p3.js +1 -0
  245. package/static/dist/assets/_app.workspaces-ehvd1gds.js +1 -0
  246. package/static/dist/assets/{abnfDiagram-VCTEODGH-b2x1lqot.js → abnfDiagram-VCTEODGH-c8kdixke.js} +1 -1
  247. package/static/dist/assets/architecture-7GRP2DOG-6crvnylg.js +1 -0
  248. package/static/dist/assets/{architectureDiagram-5GKGNRK7-h38o5l7b.js → architectureDiagram-5GKGNRK7-cku06x1p.js} +1 -1
  249. package/static/dist/assets/{back-bemaer09.js → back-h7aun7ew.js} +1 -1
  250. package/static/dist/assets/{blockDiagram-I7D4REHJ-bqquxv2u.js → blockDiagram-I7D4REHJ-jzthp1wa.js} +1 -1
  251. package/static/dist/assets/{c4Diagram-7LVT6UL2-f3fb8w6z.js → c4Diagram-7LVT6UL2-mwr67mt8.js} +1 -1
  252. package/static/dist/assets/{cancel-drd0g6d9.js → cancel-fhi0vcnu.js} +1 -1
  253. package/static/dist/assets/channel-b2zo73z1.js +1 -0
  254. package/static/dist/assets/{chunk-4HAMMTFA-bzfyyzp3.js → chunk-4HAMMTFA-gpq1y228.js} +1 -1
  255. package/static/dist/assets/{chunk-75Z2AOVW-ly99rfx5.js → chunk-75Z2AOVW-ktje58c4.js} +1 -1
  256. package/static/dist/assets/{chunk-DU6HZSFF-bg6ecfqf.js → chunk-DU6HZSFF-ofajdixh.js} +3 -3
  257. package/static/dist/assets/{chunk-F27PBJKO-e8k7mmqw.js → chunk-F27PBJKO-cmev85ti.js} +1 -1
  258. package/static/dist/assets/{chunk-GMAD6QVW-cmalnoaj.js → chunk-GMAD6QVW-htjgt22y.js} +1 -1
  259. package/static/dist/assets/{chunk-GVQU2GXP-gmif5o01.js → chunk-GVQU2GXP-qndvoxl7.js} +1 -1
  260. package/static/dist/assets/{chunk-IMKFNOWR-f1nwwh6u.js → chunk-IMKFNOWR-e5bkd5q6.js} +1 -1
  261. package/static/dist/assets/{chunk-L3NEJ4N5-oktfh5v7.js → chunk-L3NEJ4N5-mj1155r6.js} +1 -1
  262. package/static/dist/assets/{chunk-OSK3NFVY-b1sdx96z.js → chunk-OSK3NFVY-e7gkikcw.js} +1 -1
  263. package/static/dist/assets/{chunk-P2QGCYS3-fhu4nzxt.js → chunk-P2QGCYS3-f7t54xfm.js} +1 -1
  264. package/static/dist/assets/{chunk-POPQ4Y6H-em31rnef.js → chunk-POPQ4Y6H-i927v9oa.js} +1 -1
  265. package/static/dist/assets/{chunk-PWAF6VOD-bxhhyftw.js → chunk-PWAF6VOD-m84lmnyt.js} +1 -1
  266. package/static/dist/assets/{chunk-SHT3W25Y-fmrvwcav.js → chunk-SHT3W25Y-mi0tlcu9.js} +1 -1
  267. package/static/dist/assets/{chunk-SVP7TREG-lymukl2j.js → chunk-SVP7TREG-dnau702j.js} +1 -1
  268. package/static/dist/assets/{chunk-TICWLB2K-lmza6k23.js → chunk-TICWLB2K-edem1gkg.js} +1 -1
  269. package/static/dist/assets/classDiagram-ZZMXUADV-oe99st8x.js +1 -0
  270. package/static/dist/assets/classDiagram-v2-VYDZK3BY-oe99st8x.js +1 -0
  271. package/static/dist/assets/{copied-ima3pysb.js → copied-hcplv46p.js} +1 -1
  272. package/static/dist/assets/{copy-fw5b1pc6.js → copy-magtofyp.js} +1 -1
  273. package/static/dist/assets/{cynefin-OW5HDTMX-czlhbyhs.js → cynefin-OW5HDTMX-siqlulkl.js} +1 -1
  274. package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-gq7mvswb.js → cynefinDiagram-5FMLGOSQ-i648tah9.js} +1 -1
  275. package/static/dist/assets/{dagre-GXQ25YYZ-cqdbf7dr.js → dagre-GXQ25YYZ-mwn6uc17.js} +1 -1
  276. package/static/dist/assets/{diagram-S7CK7UJ4-o6arxa58.js → diagram-S7CK7UJ4-l8abxaes.js} +1 -1
  277. package/static/dist/assets/{diagram-UQ7AKVKN-nhnd68tz.js → diagram-UQ7AKVKN-mekq7zgq.js} +1 -1
  278. package/static/dist/assets/{diagram-VSXAHHWV-dk2sclzn.js → diagram-VSXAHHWV-fnky6lvc.js} +1 -1
  279. package/static/dist/assets/{diagram-VX7I27RA-n3k5nyx3.js → diagram-VX7I27RA-xe6r5qfw.js} +1 -1
  280. package/static/dist/assets/{diagram-Z3DM3KII-m6q9cwdh.js → diagram-Z3DM3KII-nq07dg7k.js} +1 -1
  281. package/static/dist/assets/{ebnfDiagram-PWID7BFC-lsa5641p.js → ebnfDiagram-PWID7BFC-c62ipm3g.js} +1 -1
  282. package/static/dist/assets/endpoints-klbxod4t.js +64 -0
  283. package/static/dist/assets/{erDiagram-RLTQ6QDP-fo2ckbe6.js → erDiagram-RLTQ6QDP-h39kmnzy.js} +1 -1
  284. package/static/dist/assets/eventmodeling-NTZA5JFV-obfede4u.js +1 -0
  285. package/static/dist/assets/flowDiagram-HODETNUW-kdpsp1je.js +1 -0
  286. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-nzqz6x9g.js → ganttDiagram-EL5Y4UJY-ngbsyp37.js} +1 -1
  287. package/static/dist/assets/{gitGraph-4MIJSDKK-gukvcxe6.js → gitGraph-4MIJSDKK-fr2i7caa.js} +1 -1
  288. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-lqhdp8f0.js → gitGraphDiagram-WWUBYQGX-eravdti4.js} +1 -1
  289. package/static/dist/assets/{highlighted-body-KPVGNVTW-igw6cp39.js → highlighted-body-KPVGNVTW-jwpop9p8.js} +1 -1
  290. package/static/dist/assets/host-i828xuo7.js +1 -0
  291. package/static/dist/assets/index-eh8zilnv.css +2 -0
  292. package/static/dist/assets/{index-cxq58xb1.js → index-r648f4o1.js} +5 -5
  293. package/static/dist/assets/{info-A6RAGUB7-gvu7a93l.js → info-A6RAGUB7-h11x0dxo.js} +1 -1
  294. package/static/dist/assets/{infoDiagram-27XIBGKW-m8t8g6w1.js → infoDiagram-27XIBGKW-dfh47dw9.js} +1 -1
  295. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-l07vkbgj.js → ishikawaDiagram-5VMMS53U-i6ypq351.js} +1 -1
  296. package/static/dist/assets/{journeyDiagram-3NMN7TZE-hybkgj8l.js → journeyDiagram-3NMN7TZE-lduf1z95.js} +1 -1
  297. package/static/dist/assets/{kanban-definition-UXKFOSKX-by13uvtn.js → kanban-definition-UXKFOSKX-e18bu4pg.js} +1 -1
  298. package/static/dist/assets/{lazyRouteComponent-af0lvgjx.js → lazyRouteComponent-e1y4t2i9.js} +1 -1
  299. package/static/dist/assets/{line-osxq6fq7.js → line-meizqsvn.js} +1 -1
  300. package/static/dist/assets/{link-jyxicdhg.js → link-oot2yn73.js} +1 -1
  301. package/static/dist/assets/login-gu28l9ou.js +1 -0
  302. package/static/dist/assets/mermaid-HWGCJPDP-mtwrj8fn.js +1 -0
  303. package/static/dist/assets/{mermaid-parser.core-i91oxmou.js → mermaid-parser.core-ntne45i9.js} +3 -3
  304. package/static/dist/assets/{mindmap-definition-YA3MSWOX-m82mx8ww.js → mindmap-definition-YA3MSWOX-27b26eok.js} +1 -1
  305. package/static/dist/assets/onboarding-lkphzu1s.js +1 -0
  306. package/static/dist/assets/{onboarding_model_label-dp7xffoh.js → onboarding_model_label-ftpjdwqk.js} +1 -1
  307. package/static/dist/assets/{packet-AYTQ26CC-nbtgge2o.js → packet-AYTQ26CC-l6jdka8p.js} +1 -1
  308. package/static/dist/assets/{pegDiagram-XKGWAZYB-f1zxzga1.js → pegDiagram-XKGWAZYB-g1er3bxj.js} +1 -1
  309. package/static/dist/assets/{pie-WAS4IAKB-of8vuhxs.js → pie-WAS4IAKB-i0ylcrt9.js} +1 -1
  310. package/static/dist/assets/{pieDiagram-E7YTZNPT-o7y8ftio.js → pieDiagram-E7YTZNPT-fbz1e55z.js} +1 -1
  311. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-iqiwia1t.js → quadrantDiagram-AXDQQJYC-hhue7d8o.js} +1 -1
  312. package/static/dist/assets/{radar-RG4KPBEZ-delnsy2v.js → radar-RG4KPBEZ-f9b70aa4.js} +1 -1
  313. package/static/dist/assets/{railroad-74A4TZTK-nspnyq9u.js → railroad-74A4TZTK-lgsuoeda.js} +1 -1
  314. package/static/dist/assets/railroad-abnf-HS5TGJTU-nf9d6pdx.js +1 -0
  315. package/static/dist/assets/railroad-ebnf-LZEXJU2U-f7ummkt5.js +1 -0
  316. package/static/dist/assets/railroad-peg-WCYAUIDC-oczq5tqs.js +1 -0
  317. package/static/dist/assets/{railroadDiagram-O6MQD6OU-z41tqx5z.js → railroadDiagram-O6MQD6OU-eqpy9fnz.js} +1 -1
  318. package/static/dist/assets/{refresh-b4uams1i.js → refresh-abtu93ke.js} +1 -1
  319. package/static/dist/assets/{refresh-cw-nt1dtsx4.js → refresh-cw-hlwb02qv.js} +1 -1
  320. package/static/dist/assets/{requirementDiagram-BXWQKSXE-bdvif194.js → requirementDiagram-BXWQKSXE-ogf6puhi.js} +1 -1
  321. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-h73e0rol.js → sankeyDiagram-P5KCCOFB-fqb8xcjf.js} +1 -1
  322. package/static/dist/assets/{saved-kg52csy7.js → saved-girnabkf.js} +1 -1
  323. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-iv95mb1z.js → sequenceDiagram-WJ2MYXX4-dlcz6qjz.js} +1 -1
  324. package/static/dist/assets/{settings_section_extensions_title-ej0iapxe.js → settings_section_extensions_title-h14cwk6f.js} +1 -1
  325. package/static/dist/assets/share._token-kl0r7ido.js +2 -0
  326. package/static/dist/assets/{sliders-horizontal-fj18it47.js → sliders-horizontal-n1zm34qq.js} +1 -1
  327. package/static/dist/assets/sse-iwku0sjo.js +1 -0
  328. package/static/dist/assets/{stateDiagram-D77RDMKH-k417ixmf.js → stateDiagram-D77RDMKH-fyoznpcs.js} +1 -1
  329. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-ixhx8r46.js +1 -0
  330. package/static/dist/assets/{swimlanes-42K2YHIH-esola70w.js → swimlanes-42K2YHIH-oefldhfy.js} +1 -1
  331. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-h9ersk2e.js +8 -0
  332. package/static/dist/assets/{timeline-definition-24CTP7MA-getrz2y8.js → timeline-definition-24CTP7MA-b1vl2zil.js} +1 -1
  333. package/static/dist/assets/todoStore-jjr5yr78.js +1 -0
  334. package/static/dist/assets/{treeView-Q6P3EWNA-hc7ap1q7.js → treeView-Q6P3EWNA-f7hcci5o.js} +1 -1
  335. package/static/dist/assets/{treemap-WGGIJYW6-b7k1syy5.js → treemap-WGGIJYW6-nl85071a.js} +1 -1
  336. package/static/dist/assets/{tts-lxdxnmjd.js → units-kgxzxfhi.js} +1 -1
  337. package/static/dist/assets/{untitled-ovpfthg4.js → untitled-cikfjntn.js} +1 -1
  338. package/static/dist/assets/{useForm-bcblv1rx.js → useForm-kqoqg0k9.js} +1 -1
  339. package/static/dist/assets/useLocale-o59tbiea.js +1 -0
  340. package/static/dist/assets/{useParams-lhg566ew.js → useParams-b4dq6n4q.js} +1 -1
  341. package/static/dist/assets/{useSearch-lbrh6ejn.js → useSearch-ipfmv3he.js} +1 -1
  342. package/static/dist/assets/{vennDiagram-4TSXK5OY-n6pcy5qb.js → vennDiagram-4TSXK5OY-h5hzbk0n.js} +1 -1
  343. package/static/dist/assets/{wardley-WFR3VGLG-lpa18vz1.js → wardley-WFR3VGLG-jftvpm3e.js} +1 -1
  344. package/static/dist/assets/{wardleyDiagram-VM6X3IG4-kpclwwg7.js → wardleyDiagram-VM6X3IG4-hedh85ge.js} +1 -1
  345. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-paa2n8y0.js → xychartDiagram-S5SC5T6Z-e49523ab.js} +1 -1
  346. package/static/dist/index.html +1 -1
  347. package/static/dist/sw.js +1 -1
  348. package/static/dist/assets/AppShell-fb2td9zi.js +0 -1
  349. package/static/dist/assets/ChatPage-cj96kiy9.js +0 -25
  350. package/static/dist/assets/Dialog-kxcg7ard.js +0 -1
  351. package/static/dist/assets/ExtensionRoute-jf7h0754.js +0 -1
  352. package/static/dist/assets/HubRoute-gmanpd5y.js +0 -2
  353. package/static/dist/assets/Select-bilaypej.js +0 -1
  354. package/static/dist/assets/SessionListPanel-og8n36qs.js +0 -1
  355. package/static/dist/assets/Toaster-nnl8zkt9.js +0 -1
  356. package/static/dist/assets/_app.index-hbarzbjx.js +0 -1
  357. package/static/dist/assets/_app.insights-luywlarz.js +0 -1
  358. package/static/dist/assets/_app.kanban-kincnbr8.js +0 -1
  359. package/static/dist/assets/_app.logs-oi2gr3gs.js +0 -1
  360. package/static/dist/assets/_app.memory-b6jfivt6.js +0 -1
  361. package/static/dist/assets/_app.profiles-lpvixmqp.js +0 -1
  362. package/static/dist/assets/_app.settings-vowx3iq0.js +0 -1
  363. package/static/dist/assets/_app.settings._section-cnntg1tu.js +0 -3
  364. package/static/dist/assets/_app.skills-fb57lpau.js +0 -1
  365. package/static/dist/assets/_app.tasks-eszpx1q7.js +0 -2
  366. package/static/dist/assets/_app.todos-nr05ac6w.js +0 -1
  367. package/static/dist/assets/_app.workspaces-d2luomu6.js +0 -1
  368. package/static/dist/assets/architecture-7GRP2DOG-hd0o7bde.js +0 -1
  369. package/static/dist/assets/channel-cpdgf867.js +0 -1
  370. package/static/dist/assets/classDiagram-ZZMXUADV-ko09sguk.js +0 -1
  371. package/static/dist/assets/classDiagram-v2-VYDZK3BY-ko09sguk.js +0 -1
  372. package/static/dist/assets/endpoints-d33siokx.js +0 -64
  373. package/static/dist/assets/eventmodeling-NTZA5JFV-emctlo64.js +0 -1
  374. package/static/dist/assets/flowDiagram-HODETNUW-bfi3moqa.js +0 -1
  375. package/static/dist/assets/index-hgtx08n5.css +0 -2
  376. package/static/dist/assets/login-ec21t39x.js +0 -1
  377. package/static/dist/assets/mermaid-HWGCJPDP-gfl8z3ep.js +0 -1
  378. package/static/dist/assets/onboarding-btnltvyz.js +0 -1
  379. package/static/dist/assets/railroad-abnf-HS5TGJTU-nst1qxyn.js +0 -1
  380. package/static/dist/assets/railroad-ebnf-LZEXJU2U-ci37x8qy.js +0 -1
  381. package/static/dist/assets/railroad-peg-WCYAUIDC-aimkk07z.js +0 -1
  382. package/static/dist/assets/share._token-f97e94r0.js +0 -2
  383. package/static/dist/assets/sse-fwpqtzbg.js +0 -1
  384. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hwn4apct.js +0 -1
  385. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-d1h97oyg.js +0 -8
  386. package/static/dist/assets/todoStore-eginf9h4.js +0 -1
  387. package/static/dist/assets/useLocale-fi5jb8ma.js +0 -1
@@ -3,17 +3,20 @@ import { randomUUID } from 'node:crypto';
3
3
  import { rmSync } from 'node:fs';
4
4
  import { buildActiveTurnToken, copyJson, redactSessionData, redactValue, stripPublicInternalFields, withSceneToolDisplay } from '../redact.js';
5
5
  import { DraftVersionConflict, normalizeDraftVersion } from './drafts.js';
6
- import { allSessions, buildSessionListPayload, CLI_IDENTITY_FIELDS, isClaimableCliSource, isMessagingSessionRecord, withOwnerLocks, withSessionWireFlags, lineageRootId, mergeCliSidebarMetadata, sessionListResponse, sessionSearchMatches, sessionSearchMessageText, sessionSearchPreview, sessionSearchTerms } from './list.js';
6
+ import { allSessions, buildSessionListPayload, CLI_IDENTITY_FIELDS, isClaimableCliSource, isMessagingSessionRecord, ownerLockCandidates, withOwnerLocks, withSessionWireFlags, lineageRootId, mergeCliSidebarMetadata, sessionListResponse, sessionSearchMatches, sessionSearchMessageText, sessionSearchPreview, sessionSearchTerms } from './list.js';
7
7
  import { anchorSceneIntOrNull, fullToolResult, hydrateAnchorActivityScenes, normalizeAnchorSceneMessageRef, readAnchorSceneRows, storeAnchorScene, withTurnIds } from './anchor.js';
8
- import { isSafeSessionId, lastMessageTimestamp, Session, sharePath, stripAttachedFilesMarker, titleFrom } from './session.js';
8
+ import { COMPRESSION_RECOVERY_ACTION_START_FOCUSED, compressionRecoveryPayload, isSafeSessionId, lastMessageTimestamp, Session, sharePath, stripAttachedFilesMarker, titleFrom } from './session.js';
9
9
  import { SessionBusy, SessionNotFound, statSignature } from './store.js';
10
10
  import { UNSETTLED_TODO_KEY, attachTodoState } from './todo.js';
11
11
  import { isClaudeCodeSessionId } from './claude-code.js';
12
- import { stateDbCompressionLineage, stateDbSessionMessages, stateDbSessionRead, stateDbSessionRow, stateDbSessionSources } from './state-db.js';
12
+ import { auditSessionRecovery, repairSafeSessionRecovery } from './recovery.js';
13
+ import { agentSessionRowsExisting, agentSessionRowsExistingAsync, CONVERSATION_ROUND_THRESHOLD, countConversationRounds, stateDbCompressionLineage, stateDbTailToolContent, stateDbLineageReport, stateDbSessionMessages, stateDbSessionRead, stateDbSessionRow, stateDbSessionSources, stateDbSessionSourcesAsync, stateDbTimestampSeconds } from './state-db.js';
14
+ import { completionIncomplete, fallbackHandoffSummary, HANDOFF_SYSTEM_PROMPT, handoffMarker, handoffPayload, handoffTranscript, messageHandoffPayload, sameHandoff } from './handoff.js';
13
15
  import { anchorMessageKey, anchorSummary, CompressionJobs, compressionReference, visibleMessagesForAnchor } from './compress.js';
14
16
  import { SidecarError } from '../sidecar/client.js';
15
- import { agentSteerText, attachedFilesPrompt, attachmentObjects, dedupeContext, isContextCompressionMarker, journalOutputRows, looksLikeCurrentUserTurn, stoppedTurnContext, workspaceContextPrefix, mergeSessionMessagesAppendOnly, messageIdentity, pendingUserRow, reasoningFieldsText, sanitizeMessagesForApi, stateDbSeenId, stripWorkspacePrefix, withAttachmentObjects, withBodyExcerpts, withDisplayMedia, withMarkerKinds, withSceneRowMedia, withPendingUserTurn, withToolCallOutcomes, withoutRunningTurnOutput } from './merge.js';
17
+ import { agentSteerText, attachedFilesPrompt, attachmentObjects, checkpointTurnStart, dedupeContext, isContextCompressionMarker, journalOutputRows, looksLikeCurrentUserTurn, stoppedTurnContext, mergeSessionMessagesAppendOnly, messageIdentity, pendingUserRow, reasoningFieldsText, sanitizeMessagesForApi, stateDbSeenId, stripWorkspacePrefix, withAttachmentObjects, withBodyExcerpts, withDisplayMedia, withMarkerKinds, withSceneRowMedia, withPendingUserTurn, withToolCallOutcomes, withoutRunningTurnOutput } from './merge.js';
16
18
  import { withBackgroundUpdates } from './background-updates.js';
19
+ import { sourceKind } from './source-kind.js';
17
20
  import { withBackgroundLinks } from './background-tasks.js';
18
21
  import { messagesForLimitedPayload, messageWindowForDisplay, MAX_MSG_LIMIT, parseMsgLimit, toolCallsForMessageWindow } from './window.js';
19
22
  import { redactText } from '../redact.js';
@@ -64,6 +67,40 @@ export class SessionService {
64
67
  this.compressionJobs = new CompressionJobs();
65
68
  }
66
69
  get store() { return this.deps.store; }
70
+ /**
71
+ * TAL-577: user-turn admission shared by `chat.start` and server-side wakeups, after `ensureAgentRuntimeCurrent`.
72
+ * A requested workspace must be trusted; the session's own one, when its directory is gone, falls back to the last one.
73
+ */
74
+ turnWorkspace(s, requested) {
75
+ const ws = this.deps.workspaces;
76
+ if (requested !== undefined && requested !== null && requested !== '') {
77
+ try {
78
+ return ws.resolveTrusted(str(requested), s.profile);
79
+ }
80
+ catch (error) {
81
+ throw new HttpFailure(400, error.message);
82
+ }
83
+ }
84
+ try {
85
+ const [workspace, recovered] = ws.resolveImplicitWithRecovery(s.workspace, (p) => ws.lastWorkspace(p), s.profile);
86
+ if (recovered)
87
+ s.workspace = workspace;
88
+ return workspace;
89
+ }
90
+ catch (error) {
91
+ throw new HttpFailure(400, error.message);
92
+ }
93
+ }
94
+ /** A `@provider:` id is split; any other model an explicit pick did not just choose is checked against the catalog (TAL-542). */
95
+ turnModel(s, body = {}) {
96
+ const requestedModel = str(body.model) || s.model;
97
+ const requestedProvider = 'model_provider' in body ? body.model_provider : s.model_provider;
98
+ const [model, provider] = this.deps.modelStateFromRequest(requestedModel, requestedProvider, s.model_provider);
99
+ if (model !== requestedModel || !model || body.explicit_model_pick === true)
100
+ return [model, provider, model !== requestedModel];
101
+ const repaired = this.deps.repairSessionModel?.(s.profile ?? null, model, provider);
102
+ return repaired ? [repaired[0], repaired[1], true] : [model, provider, false];
103
+ }
67
104
  /**
68
105
  * TAL-186: rewrites a session's media references to their URLs. A local path is rewritten only when `/api/media`
69
106
  * serves it for this session, under the same resolution, so every rewritten reference loads.
@@ -150,6 +187,52 @@ export class SessionService {
150
187
  }
151
188
  /** TAL-358: batched `sessions.source` owners from the active profile's state.db. */
152
189
  stateDbSources = (ids) => stateDbSessionSources(join(this.deps.profileHome(this.deps.activeProfile()), 'state.db'), ids);
190
+ /**
191
+ * TAL-590 (Python #3238): the read-only CLI or API-server imports among `rows` whose active-profile state.db row is
192
+ * gone, so their source was deleted outside Web and no Web action could remove them. An unreadable state.db proves
193
+ * nothing (`agentSessionRowsExisting`); another profile's session or one with a run the runtime still holds is never
194
+ * an orphan, while a stream id a crash left behind does not count as one.
195
+ */
196
+ orphanedImports(rows) {
197
+ const candidates = this.orphanCandidates(rows);
198
+ if (!candidates.length)
199
+ return new Set();
200
+ const existing = agentSessionRowsExisting(this.stateDbPath(null), candidates);
201
+ return new Set(candidates.filter((sid) => !existing.has(sid)));
202
+ }
203
+ orphanCandidates(rows) {
204
+ return rows.filter((r) => r.read_only && ['cli', 'api'].includes(sourceKind(r)) && this.visibleToActiveProfile(str(r.profile) || null) && !this.activeRunBlocking(str(r.session_id)))
205
+ .map((r) => str(r.session_id));
206
+ }
207
+ isOrphanedImport(sid) {
208
+ try {
209
+ return this.orphanedImports([this.store.get(sid, { metadataOnly: true }).compact()]).size > 0;
210
+ }
211
+ catch {
212
+ return false;
213
+ }
214
+ }
215
+ /**
216
+ * The list's owner locks and orphan prune (an orphan's sidecar goes, as nothing else ever removes it), with their
217
+ * state.db reads made on the listing worker up front so the synchronous build never opens state.db (TAL-920).
218
+ */
219
+ async listStateDbLookups(rows) {
220
+ const dbPath = this.stateDbPath(null);
221
+ const ownerIds = ownerLockCandidates(rows);
222
+ const owners = ownerIds.length ? await stateDbSessionSourcesAsync(dbPath, ownerIds) : new Map();
223
+ const checked = new Set(this.orphanCandidates(rows));
224
+ const existing = checked.size ? await agentSessionRowsExistingAsync(dbPath, checked) : new Set();
225
+ return {
226
+ stateDbSources: () => owners,
227
+ pruneOrphanedImports: (current) => {
228
+ // Only an id the worker checked can be an orphan; a row that became a candidate meanwhile waits for the next list.
229
+ const orphans = new Set(this.orphanCandidates(current).filter((sid) => checked.has(sid) && !existing.has(sid)));
230
+ for (const sid of orphans)
231
+ this.store.deleteFiles(sid);
232
+ return orphans;
233
+ },
234
+ };
235
+ }
153
236
  /** The persisted flag folded with the state.db owner lock (TAL-358): the one read-only rule for mutation gates and the wire. */
154
237
  isReadOnly(s) {
155
238
  if (s.read_only)
@@ -178,6 +261,7 @@ export class SessionService {
178
261
  const synth = this.claimOrSynthesizeCliSession(sid);
179
262
  if (synth.session && synth.reason === 'materialized') {
180
263
  this.store.save(synth.session);
264
+ this.deps.titleClaimed?.(synth.session);
181
265
  return synth.session;
182
266
  }
183
267
  if (synth.session)
@@ -242,7 +326,7 @@ export class SessionService {
242
326
  * turn started from but the transcript lacks (a CLI continuation read into its context) are kept before the turn.
243
327
  */
244
328
  settleStateDb(s, turn) {
245
- const read = this.stateDbRead(s);
329
+ const read = turn.read ?? this.stateDbRead(s);
246
330
  const startedWith = new Set(turn.previousContext.flatMap((m) => (typeof m._state_db_row_id === 'number' ? [m._state_db_row_id] : [])));
247
331
  const earlier = mergeSessionMessagesAppendOnly(s.messages, read.rows.filter((m) => typeof m._state_db_row_id === 'number' && startedWith.has(m._state_db_row_id)), { stateDbSeenId: -1 }).slice(s.messages.length);
248
332
  const added = new Map();
@@ -296,6 +380,30 @@ export class SessionService {
296
380
  this.markStateDbSeen(s);
297
381
  }
298
382
  }
383
+ /**
384
+ * TAL-921: the model context a turn leaves when it ends without its settled result (an error, a Stop, a dead run), as a
385
+ * Stop's: the Agent's rows when they hold the turn's prompt, else its own rows among `stateRows` (the state.db rows
386
+ * committed after it started), then the prose that streamed past them. Null when it captured nothing the model can use.
387
+ */
388
+ unsettledTurnContext(turn) {
389
+ const reported = turn.agentRows && checkpointTurnStart(turn.agentRows, turn.msgText, turn.historyLength) !== null ? turn.agentRows : null;
390
+ const own = reported ? null : runStateDbRows(turn.stateRows, turn.msgText);
391
+ const checkpoint = reported ?? (own && [...turn.previousContext, ...own]);
392
+ const context = stoppedTurnContext(turn.previousContext, checkpoint, turn.prompt, turn.msgText, turn.streamedText, turn.historyLength);
393
+ if (!context)
394
+ return null;
395
+ if (!own || !checkpoint || checkpointTurnStart(checkpoint, turn.msgText, turn.historyLength) === null)
396
+ return dedupeContext(context);
397
+ // The context holds the turn's own state.db rows from its prompt on. The Agent persisted the prompt's text only, so the
398
+ // prompt row carries what the turn sent (attached files, images). This turn's settlement covers the rows: without
399
+ // their ids, the next one never reads them as a CLI continuation the transcript lacks.
400
+ const at = turn.previousContext.length;
401
+ return dedupeContext(context.map((m, i) => {
402
+ if (i === at)
403
+ return { role: 'user', content: turn.prompt, ...(m.timestamp != null ? { timestamp: m.timestamp } : {}) };
404
+ return i > at && i < at + own.length ? Object.fromEntries(Object.entries(m).filter(([k]) => k !== '_state_db_row_id' && k !== '_state_db_generation')) : m;
405
+ }));
406
+ }
299
407
  /**
300
408
  * Python `reconciled_state_db_messages_for_session(prefer_context=True)`: the model history is the owner context
301
409
  * extended append-only with the Agent's state.db rows (a CLI continuation of this session reaches the model), except
@@ -314,12 +422,22 @@ export class SessionService {
314
422
  const pending = this.pendingTurn(s);
315
423
  return pending ? withPendingUserTurn(context, pending) : context;
316
424
  }
425
+ /** Python `_is_messaging_session_id`: from the WebUI metadata or the Agent's row. */
426
+ isMessagingSession(sid) {
427
+ try {
428
+ if (isMessagingSessionRecord(this.store.get(sid, { metadataOnly: true }).compact()))
429
+ return true;
430
+ }
431
+ catch { /* absent */ }
432
+ const meta = this.lookupCliMeta(sid);
433
+ return meta !== null && isMessagingSessionRecord(meta);
434
+ }
317
435
  /** Python `_lookup_cli_session_metadata`: the sidebar row for a state.db session in the active profile. */
318
436
  lookupCliMeta(sid) {
319
437
  try {
320
438
  if (isClaudeCodeSessionId(sid))
321
439
  return this.claudeCodeRows().find((r) => str(r.session_id) === sid) ?? null;
322
- return this.deps.cliSessions(this.deps.activeProfile(), { sourceFilter: null }).rows.find((r) => str(r.session_id) === sid) ?? null;
440
+ return this.deps.cliSessionsSync(this.deps.activeProfile(), { sourceFilter: null }).rows.find((r) => str(r.session_id) === sid) ?? null;
323
441
  }
324
442
  catch {
325
443
  return null;
@@ -498,7 +616,8 @@ export class SessionService {
498
616
  }
499
617
  this.clearStaleStreamState(s);
500
618
  const journaled = loadMessages ? this.journaledActiveTurn(s) : null;
501
- let transcript = loadMessages ? this.mergedTranscript(s) : [];
619
+ const stateRows = loadMessages ? this.stateDbRows(s) : [];
620
+ let transcript = loadMessages ? this.mergedTranscript(s, s.messages, stateRows) : [];
502
621
  const pending = loadMessages ? this.pendingTurn(s) : null;
503
622
  if (pending)
504
623
  transcript = withPendingUserTurn(transcript, pending);
@@ -572,10 +691,42 @@ export class SessionService {
572
691
  raw.read_only = this.isReadOnly(s);
573
692
  raw.assistant_name = this.assistantName(s);
574
693
  raw.workspace_name = this.workspaceNames()(s);
694
+ raw.branched_from = this.branchedFrom(s);
575
695
  withSessionWireFlags(raw, activeStreamIds);
696
+ // A metadata-only read lacks the history the gate counts, so it carries no flag.
697
+ if (loadMessages)
698
+ raw.can_compress = this.canCompress(s, raw.read_only === true, stateRows);
576
699
  raw.pending_steers = raw.active_stream_id ? (this.deps.runtime.pendingSteers?.(str(raw.active_stream_id)) ?? []) : [];
577
700
  return redactSessionData(raw, this.deps.redactEnabled());
578
701
  }
702
+ /**
703
+ * TAL-454: the chat `/branch` copied, while it still loads (archived included). A compression continuation is also a
704
+ * fork of its parent but not a branch, so it links nowhere.
705
+ */
706
+ branchedFrom(s) {
707
+ const parentId = s.parent_session_id;
708
+ if (!parentId || str(s.session_source).trim().toLowerCase() !== 'fork' || s.compression_recovery_source_session_id)
709
+ return null;
710
+ let title;
711
+ try {
712
+ const parent = this.store.get(parentId, { metadataOnly: true, promote: false, cacheOnMiss: false });
713
+ if (!this.visibleToActiveProfile(parent.profile))
714
+ return null;
715
+ title = parent.title;
716
+ }
717
+ catch {
718
+ // A read-only foreign parent (a cron run) has no sidecar and opens from the active profile's state.db while it
719
+ // has transcript rows and is not a deleted chat (`claimOrSynthesizeCliSession`'s `was_webui` and `no_foreign_state`).
720
+ const dbPath = this.stateDbPath(null);
721
+ const row = stateDbSessionRow(dbPath, parentId);
722
+ if (!row || this.store.wasDeleted(parentId) || this.indexMarksWasWebui(parentId))
723
+ return null;
724
+ if (!stateDbSessionMessages(dbPath, parentId, { stitch: true }).length)
725
+ return null;
726
+ title = row.title;
727
+ }
728
+ return { session_id: parentId, title: str(redactText(str(title) || 'Untitled', this.deps.redactEnabled())) };
729
+ }
579
730
  /**
580
731
  * TAL-316: the active run whose journal can replay its output from the start. The detail then leaves that output to the
581
732
  * replay; with no active run, no identifiable prompt, or no complete journal, it returns the persisted transcript unchanged.
@@ -674,7 +825,8 @@ export class SessionService {
674
825
  const read = this.stateDbRead(target);
675
826
  const runStart = target.pending_started_at || events[0]?.created_at || this.deps.now();
676
827
  const before = read.rows.filter((m) => Number(m.timestamp) < runStart);
677
- const ownRows = target.hasPendingPrompt ? runStateDbRows(read.rows.filter((m) => Number(m.timestamp) >= runStart), pendingText) : null;
828
+ const runRows = read.rows.filter((m) => Number(m.timestamp) >= runStart);
829
+ const ownRows = target.hasPendingPrompt ? runStateDbRows(runRows, pendingText) : null;
678
830
  // Without this turn's rows: an eager save already put its prompt in the transcript a fresh context falls back to.
679
831
  const previousContext = this.modelContext(target, before).filter((m) => m._turn_id !== turnId);
680
832
  if (target.hasPendingPrompt) {
@@ -684,16 +836,16 @@ export class SessionService {
684
836
  target.messages.push({ role: 'user', content: pendingText, timestamp: Math.trunc(startedAt), ...(attachments.length ? { attachments } : {}), _recovered: true, _source: target.pending_user_source ?? 'webui', _turn_id: turnId });
685
837
  // The model context settles as a Stop's does: the Agent's own rows when it committed the prompt, else the prompt it
686
838
  // was sent; then the prose that streamed past them.
687
- const prompt = attachedFilesPrompt((str(target.workspace) ? workspaceContextPrefix(str(target.workspace)) : '') + pendingText, attachments);
839
+ const prompt = attachedFilesPrompt(pendingText, attachments);
688
840
  const streamed = output.map((m) => str(m.content)).filter(Boolean).join('\n\n');
689
- target.context_messages = dedupeContext(stoppedTurnContext(previousContext, ownRows ? [...previousContext, ...ownRows] : null, prompt, pendingText, streamed, previousContext.length) ?? [...structuredClone(previousContext), { role: 'user', content: prompt }]);
841
+ target.context_messages = this.unsettledTurnContext({ previousContext, prompt, msgText: pendingText, streamedText: streamed, historyLength: previousContext.length, stateRows: runRows }) ?? dedupeContext([...structuredClone(previousContext), { role: 'user', content: prompt }]);
690
842
  }
691
843
  if (target.hasPendingPrompt || output.length) {
692
844
  target.messages.push(...output);
693
845
  if (!answered)
694
846
  target.messages.push({ role: 'assistant', content: '**Interrupted:** The reply was interrupted before it could be saved.', timestamp: Math.trunc(this.deps.now()), _error: true, _turn_id: turnId });
695
847
  // Settled like a turn: the Agent's own rows are covered by the recovered turn; without them, every row past the run's start is.
696
- this.settleStateDb(target, { turnId, previousContext, agentRows: ownRows, startId: stateDbSeenId(before) ?? (read.idCapable ? 0 : null) });
848
+ this.settleStateDb(target, { turnId, previousContext, agentRows: ownRows, startId: stateDbSeenId(before) ?? (read.idCapable ? 0 : null), read });
697
849
  }
698
850
  target.active_stream_id = null;
699
851
  target.pending_user_message = null;
@@ -717,16 +869,18 @@ export class SessionService {
717
869
  return true;
718
870
  }
719
871
  // ── list / search ────────────────────────────────────────────────────────
720
- list(params) {
872
+ async list(params) {
721
873
  const activeProfile = this.deps.activeProfile();
722
874
  const wantState = params.showCliSessions || params.showCronSessions || params.showWebhookSessions || params.showKanbanSessions;
723
875
  // Python reads every profile's state.db under all_profiles; this port projects the active profile only.
724
- const cliRead = wantState ? this.deps.cliSessions(activeProfile, { sourceFilter: params.sourceFilter ?? null }) : undefined;
876
+ const cliRead = wantState ? await this.deps.cliSessions(activeProfile, { sourceFilter: params.sourceFilter ?? null }) : undefined;
725
877
  const claudeCodeRows = params.showCliSessions && params.showClaudeCodeSessions ? this.claudeCodeRows() : [];
726
878
  const cliRows = cliRead ? [...cliRead.rows, ...claudeCodeRows] : undefined;
727
879
  const gatewayIdentity = loadGatewaySessionIdentityMap(join(this.deps.profileHome(activeProfile), 'sessions', 'sessions.json'));
728
880
  const truncatedSources = cliRead?.truncated;
729
- const payload = buildSessionListPayload(this.store, { ...params, ...(cliRows ? { cliRows } : {}), ...(truncatedSources ? { truncatedSources } : {}), gatewayIdentity, stateDbSources: this.stateDbSources, activeProfile, isolatedProfileMode: this.deps.isolatedProfileMode(), profilesMatch: this.deps.profilesMatch });
881
+ const webuiRows = allSessions(this.store, { sidebarMetadataOnly: true });
882
+ const lookups = await this.listStateDbLookups(webuiRows);
883
+ const payload = buildSessionListPayload(this.store, { ...params, ...(cliRows ? { cliRows } : {}), ...(truncatedSources ? { truncatedSources } : {}), gatewayIdentity, ...lookups, webuiRows, activeProfile, isolatedProfileMode: this.deps.isolatedProfileMode(), profilesMatch: this.deps.profilesMatch });
730
884
  return sessionListResponse(payload, this.deps.runtime, this.deps.redactEnabled(), this.deps.now(), this.workspaceNames());
731
885
  }
732
886
  search(q, opts) {
@@ -770,8 +924,8 @@ export class SessionService {
770
924
  * archived, projection and order), narrowed to `projectId` (`none`: rows without a project), then matched on the
771
925
  * title, then the metadata the row shows (workspace path and name, model, provider, profile, source label), then message content.
772
926
  */
773
- sidebarSearch(q, params, opts) {
774
- let rows = this.list(params).body.sessions;
927
+ async sidebarSearch(q, params, opts) {
928
+ let rows = (await this.list(params)).body.sessions;
775
929
  if (opts.projectId === 'none')
776
930
  rows = rows.filter((r) => !r.project_id);
777
931
  else if (opts.projectId)
@@ -916,7 +1070,8 @@ export class SessionService {
916
1070
  markSessionTitleGenerated(current);
917
1071
  this.store.save(current, { touchUpdatedAt: false });
918
1072
  });
919
- await this.deps.syncTitle(current);
1073
+ // An explicit regenerate is user-requested, so state.db takes it over a manual name.
1074
+ await this.deps.syncTitle(current, true);
920
1075
  this.publish(eventReason, current.profile, current.session_id);
921
1076
  return current;
922
1077
  }
@@ -926,7 +1081,7 @@ export class SessionService {
926
1081
  applySessionTitleRename(s, rawTitle);
927
1082
  this.store.save(s);
928
1083
  });
929
- await this.deps.syncTitle(s);
1084
+ await this.deps.syncTitle(s, true);
930
1085
  this.publish('session_rename', s.profile, s.session_id);
931
1086
  return { session: this.wireRow(s) };
932
1087
  }
@@ -1113,12 +1268,146 @@ export class SessionService {
1113
1268
  compressionTarget(sid) {
1114
1269
  this.rejectSubagent(sid, 'compressed');
1115
1270
  const s = this.mutationTarget(sid, 'compressed');
1271
+ return { s, history: this.compressionHistory(s) };
1272
+ }
1273
+ /** The loaded session's own compression guards: no running turn and enough model history. */
1274
+ compressionHistory(s, stateRows) {
1116
1275
  if (s.active_stream_id)
1117
1276
  throw new HttpFailure(409, 'Session is still streaming; wait for the current turn to finish.');
1118
- const history = sanitizeMessagesForApi(this.modelContext(s));
1277
+ const history = sanitizeMessagesForApi(this.modelContext(s, stateRows));
1119
1278
  if (history.length < 4)
1120
1279
  throw new HttpFailure(400, 'Not enough conversation to compress (need at least 4 messages).');
1121
- return { s, history };
1280
+ return history;
1281
+ }
1282
+ /** TAL-614: whether a manual compression would start now: `compressionTarget`'s gates, with `readOnly` the wire flag. */
1283
+ canCompress(s, readOnly, stateRows) {
1284
+ if (readOnly || isClaudeCodeSessionId(s.session_id))
1285
+ return false;
1286
+ try {
1287
+ this.compressionHistory(s, stateRows);
1288
+ return true;
1289
+ }
1290
+ catch (error) {
1291
+ if (error instanceof HttpFailure)
1292
+ return false;
1293
+ throw error;
1294
+ }
1295
+ }
1296
+ /** TAL-258: the session's WebUI record (absent for a CLI or gateway session) and the profile whose state.db holds its rows. */
1297
+ handoffTarget(sid) {
1298
+ let local = null;
1299
+ try {
1300
+ local = this.store.get(sid);
1301
+ }
1302
+ catch { /* a foreign session without a WebUI record */ }
1303
+ const profile = local?.profile ?? this.deps.activeProfile();
1304
+ return { local, profile, dbPath: this.stateDbPath(profile) };
1305
+ }
1306
+ /** Python `_handle_conversation_rounds` (TAL-258): the session's rounds and whether the handoff dock is due. */
1307
+ conversationRounds(sid, since) {
1308
+ const rounds = countConversationRounds(this.handoffTarget(sid).dbPath, sid, since);
1309
+ return { ok: true, rounds, threshold: CONVERSATION_ROUND_THRESHOLD, should_show: rounds >= CONVERSATION_ROUND_THRESHOLD };
1310
+ }
1311
+ /**
1312
+ * Python `_handle_handoff_summary` (TAL-258): the session's main model summarizes its last 50 state.db messages after
1313
+ * `since`, and the summary is appended as a display-only `handoff_summary` tool row. A failed or cut-off answer falls
1314
+ * back to a deterministic local summary rather than an error.
1315
+ */
1316
+ async handoffSummary(sid, since) {
1317
+ this.rejectSubagent(sid, 'summarized');
1318
+ const { local, profile, dbPath } = this.handoffTarget(sid);
1319
+ const rounds = countConversationRounds(dbPath, sid, since);
1320
+ if (rounds < CONVERSATION_ROUND_THRESHOLD)
1321
+ throw new HttpFailure(400, 'Not enough conversation rounds to generate a summary.');
1322
+ // A running turn settles the transcript it started from, which would drop a card appended meanwhile.
1323
+ if (local?.active_stream_id)
1324
+ throw new HttpFailure(409, 'Session is still streaming; wait for the current turn to finish.');
1325
+ const all = stateDbSessionMessages(dbPath, sid, { stitch: true });
1326
+ const messages = (since === null ? all : all.filter((m) => { const ts = stateDbTimestampSeconds(m.timestamp); return ts !== null && ts > since; })).slice(-50);
1327
+ if (messages.length < 2)
1328
+ throw new HttpFailure(400, 'Not enough messages to summarize.');
1329
+ const sidecar = this.deps.sidecar?.() ?? null;
1330
+ await ensureAgentRuntimeCurrent(sidecar);
1331
+ const meta = local ? null : this.lookupCliMeta(sid);
1332
+ const generated = await this.generateHandoffSummary(sidecar, profile, local, meta, messages);
1333
+ const pick = (row, ...keys) => { for (const key of keys) {
1334
+ const v = str(row?.[key]).trim();
1335
+ if (v)
1336
+ return v;
1337
+ } return ''; };
1338
+ // Python `get_cli_sessions` never listed WebUI's own state.db rows, so they name no channel.
1339
+ const cliRow = meta ?? this.lookupCliMeta(sid);
1340
+ const channel = pick(local, 'source_label', 'raw_source', 'source_tag', 'session_source') || (str(cliRow?.raw_source || cliRow?.source).trim().toLowerCase() === 'webui' ? '' : pick(cliRow, 'source_label', 'raw_source', 'source_tag', 'source')) || null;
1341
+ await this.persistHandoffMarker(sid, profile, handoffMarker(sid, generated.summary, channel, rounds, generated.fallback, this.deps.now()));
1342
+ return { ok: true, summary: generated.summary, message_count: messages.length, rounds, fallback: generated.fallback, ...(generated.warning ? { warning: generated.warning } : {}) };
1343
+ }
1344
+ /** The main model's summary at 700 tokens, once more at 1400 when it was cut off, else the local fallback. */
1345
+ async generateHandoffSummary(sidecar, profile, local, meta, messages) {
1346
+ const fallback = (warning) => ({ summary: fallbackHandoffSummary(messages), fallback: true, ...(warning ? { warning: `Summary generation used local fallback: ${warning}` } : {}) });
1347
+ if (!sidecar)
1348
+ return fallback('the Agent sidecar is not running');
1349
+ const [model, provider] = local ? this.deps.modelStateFromRequest(local.model, local.model_provider, local.model_provider) : [str(meta?.model).trim() || null, null];
1350
+ const prompt = [{ role: 'system', content: HANDOFF_SYSTEM_PROMPT }, { role: 'user', content: `Conversation transcript:\n${handoffTranscript(messages)}` }];
1351
+ const ask = (maxTokens) => sidecar.call('aux.complete', { profile_home: this.deps.profileHome(profile), task: 'handoff_summary', messages: prompt, model: model ?? '', provider, max_tokens: maxTokens, temperature: 0.2 }, { timeoutMs: 60_000 });
1352
+ try {
1353
+ let result = await ask(700);
1354
+ if (completionIncomplete(result))
1355
+ result = await ask(1400);
1356
+ return completionIncomplete(result) ? fallback() : { summary: result.text, fallback: false };
1357
+ }
1358
+ catch (error) {
1359
+ if (error instanceof SidecarError && error.condition === 'agent_runtime_stale')
1360
+ throw staleRuntimeFailure(error);
1361
+ // No credential is not a failure: the predecessor answered with the local summary and no warning.
1362
+ if (error instanceof SidecarError && error.condition === 'credential_missing')
1363
+ return fallback();
1364
+ return fallback(sanitizePaths(error));
1365
+ }
1366
+ }
1367
+ /**
1368
+ * Python `_persist_handoff_summary`: a messaging session gets the marker in state.db and its WebUI record; any other
1369
+ * session in its WebUI record, or in state.db when it has none. Each store skips a marker its tail already holds, and
1370
+ * the session lock keeps two summaries from both passing that check. A card neither store saved answers 503.
1371
+ */
1372
+ async persistHandoffMarker(sid, profile, marker) {
1373
+ const card = messageHandoffPayload(marker);
1374
+ const toStateDb = async () => {
1375
+ if (sameHandoff(handoffPayload(stateDbTailToolContent(this.stateDbPath(profile), sid)), card))
1376
+ return true;
1377
+ const sidecar = this.deps.sidecar?.();
1378
+ if (!sidecar)
1379
+ return false;
1380
+ try {
1381
+ return (await sidecar.call('state_db.append_message', { profile_home: this.deps.profileHome(profile), session_id: sid, role: 'tool', content: str(marker.content), tool_name: 'handoff_summary', timestamp: Number(marker.timestamp) })).ok;
1382
+ }
1383
+ catch {
1384
+ return false;
1385
+ }
1386
+ };
1387
+ await this.store.withLock(sid, async () => {
1388
+ let live = null;
1389
+ try {
1390
+ live = this.store.get(sid);
1391
+ }
1392
+ catch { /* no WebUI record */ }
1393
+ // A turn that started while the summary was generated would drop the card when it settles.
1394
+ if (live?.active_stream_id)
1395
+ throw new HttpFailure(409, 'Session is still streaming; wait for the current turn to finish.');
1396
+ const toLocal = () => {
1397
+ if (!live)
1398
+ return false;
1399
+ if (sameHandoff(messageHandoffPayload(live.messages.at(-1)), card))
1400
+ return true;
1401
+ live.messages.push(marker);
1402
+ this.store.save(live);
1403
+ return true;
1404
+ };
1405
+ const messaging = this.isMessagingSession(sid);
1406
+ const saved = messaging ? [await toStateDb(), toLocal()].some(Boolean) : toLocal() || await toStateDb();
1407
+ // The caller may switch away on success, so a card saved nowhere is an error, not a summary.
1408
+ if (!saved)
1409
+ throw new HttpFailure(503, 'The handoff summary could not be saved; please retry.');
1410
+ });
1122
1411
  }
1123
1412
  /** The session's compression job; a deleted session's finished result is dropped rather than served. */
1124
1413
  compressionJob(sid) {
@@ -1397,6 +1686,87 @@ export class SessionService {
1397
1686
  }
1398
1687
  return { session_id: branch.session_id, title, parent_session_id: source.session_id };
1399
1688
  }
1689
+ /** Python `read_session_lineage_report` on the active profile's state.db. */
1690
+ lineageReport(sid) {
1691
+ return stateDbLineageReport(this.stateDbPath(null), sid);
1692
+ }
1693
+ /** TAL-259: recovery reads the active profile's state.db, and a recovered sidecar joins that profile. */
1694
+ recoveryDeps() {
1695
+ return { store: this.store, stateDbPath: this.stateDbPath(null), profile: this.deps.activeProfile(), stampingSince: this.deps.recoveryStampingSince(), log: this.deps.log };
1696
+ }
1697
+ recoveryAudit() {
1698
+ return auditSessionRecovery(this.recoveryDeps());
1699
+ }
1700
+ recoveryRepairSafe() {
1701
+ return repairSafeSessionRecovery(this.recoveryDeps());
1702
+ }
1703
+ /**
1704
+ * Python `_handle_session_compression_recovery_start`: open a compression-exhausted session's focused continuation, an
1705
+ * empty fork keeping its workspace, model, profile, toolsets and worktree. A retry opens the oldest continuation
1706
+ * already started from it in the same profile; the source's lock makes concurrent starts converge on one child.
1707
+ */
1708
+ async compressionRecoveryStart(sid) {
1709
+ if (this.isSubagentViewOnly(sid))
1710
+ throw new HttpFailure(400, 'Subagent sessions are view-only and cannot start compression recovery from WebUI');
1711
+ const action = COMPRESSION_RECOVERY_ACTION_START_FOCUSED;
1712
+ let created = false;
1713
+ const child = await this.store.withLock(sid, () => {
1714
+ // Validated under the lock: a delete that won it leaves no source to fork.
1715
+ const source = this.store.get(sid);
1716
+ if (!this.visibleToActiveProfile(source.profile))
1717
+ throw new HttpFailure(404, 'Session not found');
1718
+ if (!compressionRecoveryPayload(source))
1719
+ throw new HttpFailure(409, 'Session does not have a compression recovery action.');
1720
+ const existing = this.compressionRecoveryChild(sid, action, source.profile);
1721
+ if (existing)
1722
+ return existing;
1723
+ const base = (source.title || 'Untitled').trim() || 'Untitled';
1724
+ const fresh = new Session({
1725
+ title: base.endsWith(' (focused continuation)') ? base : `${base} (focused continuation)`, workspace: source.workspace, model: source.model, model_provider: source.model_provider,
1726
+ project_id: source.project_id, profile: source.profile, session_source: 'fork', personality: source.personality, enabled_toolsets: copyJson(source.enabled_toolsets),
1727
+ context_length: source.context_length, threshold_tokens: source.threshold_tokens, gateway_routing: copyJson(source.gateway_routing), gateway_routing_history: copyJson(source.gateway_routing_history),
1728
+ parent_session_id: source.session_id, worktree_path: source.worktree_path, worktree_branch: source.worktree_branch, worktree_repo_root: source.worktree_repo_root, worktree_created_at: source.worktree_created_at,
1729
+ compression_recovery_source_session_id: sid, compression_recovery_action: action,
1730
+ // An empty model-facing transcript: the focused follow-up must not replay the exhausted context.
1731
+ messages: [], context_messages: [], composer_draft: { text: '', files: [] },
1732
+ }, { workspace: source.workspace, model: source.model });
1733
+ try {
1734
+ this.store.save(fresh);
1735
+ }
1736
+ catch (error) {
1737
+ throw new HttpFailure(500, `Failed to start compression recovery: ${sanitizePaths(error)}`);
1738
+ }
1739
+ this.store.touch(fresh);
1740
+ created = true;
1741
+ return fresh;
1742
+ });
1743
+ if (created)
1744
+ this.publish('session_compression_recovery', child.profile, child.session_id);
1745
+ return {
1746
+ ok: true, session: this.publicSession(child), source_session_id: sid, recommended_recovery_action: action,
1747
+ message: created ? 'Started a focused continuation. Describe the next narrow task to continue.' : 'Opened the existing focused continuation for this exhausted session.',
1748
+ };
1749
+ }
1750
+ /** Python `find_compression_recovery_session`: the oldest same-profile continuation started from `sid`, cached or persisted. */
1751
+ compressionRecoveryChild(sid, action, profile) {
1752
+ const matches = (s) => s.compression_recovery_source_session_id === sid && s.compression_recovery_action === action && this.deps.profilesMatch(s.profile, profile);
1753
+ const found = new Map();
1754
+ for (const s of this.store.sessions.values())
1755
+ if (matches(s))
1756
+ found.set(s.session_id, s);
1757
+ for (const id of this.store.persistedIds()) {
1758
+ if (found.has(id) || this.store.sessions.has(id))
1759
+ continue;
1760
+ try {
1761
+ const meta = this.store.get(id, { metadataOnly: true, promote: false, cacheOnMiss: false });
1762
+ if (matches(meta))
1763
+ found.set(id, meta);
1764
+ }
1765
+ catch { /* unreadable: not a match */ }
1766
+ }
1767
+ const [oldest] = [...found.values()].sort((a, b) => (a.created_at || 0) - (b.created_at || 0) || (a.updated_at || 0) - (b.updated_at || 0) || (a.session_id < b.session_id ? -1 : 1));
1768
+ return oldest ? this.store.get(oldest.session_id) : null;
1769
+ }
1400
1770
  /** The run that keeps `delete` answering 409, if any; stream status reports it so a draining client can wait on it (TAL-622). */
1401
1771
  activeRunBlocking(sid) {
1402
1772
  const live = this.deps.runtime.activeRunStream(sid);
@@ -1416,8 +1786,9 @@ export class SessionService {
1416
1786
  if (!isSafeSessionId(sid))
1417
1787
  throw new HttpFailure(400, 'Invalid session_id');
1418
1788
  // Python: a read-only import (persisted sidecar flag, sidebar metadata, or a foreign state.db owner such as a
1419
- // Claude Code session) is never deleted — that would erase the owner's authoritative transcript.
1420
- if (this.isReadOnlyImport(sid))
1789
+ // Claude Code session) is never deleted — that would erase the owner's authoritative transcript. An orphan has none.
1790
+ const orphaned = this.isOrphanedImport(sid);
1791
+ if (!orphaned && this.isReadOnlyImport(sid))
1421
1792
  throw new HttpFailure(400, 'Read-only imported sessions cannot be deleted from WebUI');
1422
1793
  if (this.isSubagentViewOnly(sid))
1423
1794
  throw new HttpFailure(400, 'Subagent sessions are view-only and cannot be deleted from WebUI');
@@ -1437,11 +1808,7 @@ export class SessionService {
1437
1808
  hadSidecar = false;
1438
1809
  }
1439
1810
  // Python `_is_messaging_session_id`: decided before the JSON is gone, from WebUI metadata or the Agent's row.
1440
- const isMessaging = (() => { try {
1441
- if (isMessagingSessionRecord(this.store.get(sid, { metadataOnly: true }).compact()))
1442
- return true;
1443
- }
1444
- catch { /* absent */ } const meta = this.lookupCliMeta(sid); return meta !== null && isMessagingSessionRecord(meta); })();
1811
+ const isMessaging = this.isMessagingSession(sid);
1445
1812
  if (this.activeRunBlocking(sid))
1446
1813
  throw new HttpFailure(409, 'Session has an active run; stop it before deleting');
1447
1814
  try {
@@ -1479,9 +1846,16 @@ export class SessionService {
1479
1846
  // Python: the Agent/CLI transcript in state.db goes too (else a claimable CLI row resurfaces in the sidebar), but a
1480
1847
  // messaging channel's memory is never erased from the WebUI; the actual outcome is reported.
1481
1848
  let stateDbCleanupFailed = false;
1482
- if (!isMessaging) {
1849
+ if (!isMessaging && !orphaned) {
1483
1850
  // TAL-529: the compression continuations go too, tip first; the Agent's delete keeps and detaches them otherwise.
1484
- for (const id of stateDbCompressionLineage(this.stateDbPath(eventProfile), sid).reverse()) {
1851
+ let lineage = [];
1852
+ try {
1853
+ lineage = stateDbCompressionLineage(this.stateDbPath(eventProfile), sid);
1854
+ }
1855
+ catch {
1856
+ stateDbCleanupFailed = true;
1857
+ }
1858
+ for (const id of lineage.reverse()) {
1485
1859
  try {
1486
1860
  if (!(await this.deps.runtime.deleteCliSession(eventProfile, id)))
1487
1861
  stateDbCleanupFailed = true;