@maudecode/talaria-web 1.19.0 → 1.20.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 (591) hide show
  1. package/_release.json +8 -8
  2. package/dist/api/automation-raw.js +61 -6
  3. package/dist/api/automation-raw.js.map +1 -1
  4. package/dist/api/automation-router.d.ts +8 -6
  5. package/dist/api/automation-router.js +7 -1
  6. package/dist/api/automation-router.js.map +1 -1
  7. package/dist/api/chat-router.d.ts +258 -33
  8. package/dist/api/chat-router.js +60 -25
  9. package/dist/api/chat-router.js.map +1 -1
  10. package/dist/api/git-router.d.ts +4 -0
  11. package/dist/api/git-router.js +1 -1
  12. package/dist/api/git-router.js.map +1 -1
  13. package/dist/api/raw-routes.js +13 -1
  14. package/dist/api/raw-routes.js.map +1 -1
  15. package/dist/api/router.d.ts +2 -2
  16. package/dist/api/session-visibility.js +2 -2
  17. package/dist/api/session-visibility.js.map +1 -1
  18. package/dist/api/sessions-router.d.ts +787 -107
  19. package/dist/api/sessions-router.js +16 -4
  20. package/dist/api/sessions-router.js.map +1 -1
  21. package/dist/api/settings-router.d.ts +34 -13
  22. package/dist/api/settings-router.js +54 -17
  23. package/dist/api/settings-router.js.map +1 -1
  24. package/dist/api/sse-routes.js +41 -19
  25. package/dist/api/sse-routes.js.map +1 -1
  26. package/dist/api/tools-router.d.ts +314 -32
  27. package/dist/api/tools-router.js +135 -38
  28. package/dist/api/tools-router.js.map +1 -1
  29. package/dist/app.js +15 -11
  30. package/dist/app.js.map +1 -1
  31. package/dist/auth/crypto.d.ts +3 -0
  32. package/dist/auth/crypto.js +18 -0
  33. package/dist/auth/crypto.js.map +1 -1
  34. package/dist/auth/gate.js +5 -1
  35. package/dist/auth/gate.js.map +1 -1
  36. package/dist/cli/ctl.js +6 -1
  37. package/dist/cli/ctl.js.map +1 -1
  38. package/dist/cli/main.js +23 -18
  39. package/dist/cli/main.js.map +1 -1
  40. package/dist/cli/supervise.d.ts +22 -0
  41. package/dist/cli/supervise.js +80 -2
  42. package/dist/cli/supervise.js.map +1 -1
  43. package/dist/config/agent-config.d.ts +3 -0
  44. package/dist/config/agent-config.js +36 -9
  45. package/dist/config/agent-config.js.map +1 -1
  46. package/dist/fs/atomic.d.ts +3 -0
  47. package/dist/fs/atomic.js +19 -5
  48. package/dist/fs/atomic.js.map +1 -1
  49. package/dist/fs/stat.d.ts +6 -0
  50. package/dist/fs/stat.js +18 -2
  51. package/dist/fs/stat.js.map +1 -1
  52. package/dist/http/context.d.ts +14 -0
  53. package/dist/http/context.js +48 -5
  54. package/dist/http/context.js.map +1 -1
  55. package/dist/profiles/profiles.d.ts +2 -0
  56. package/dist/profiles/profiles.js +1 -0
  57. package/dist/profiles/profiles.js.map +1 -1
  58. package/dist/providers/catalog.d.ts +11 -6
  59. package/dist/providers/catalog.js +76 -24
  60. package/dist/providers/catalog.js.map +1 -1
  61. package/dist/redact.js +38 -14
  62. package/dist/redact.js.map +1 -1
  63. package/dist/release.js +5 -3
  64. package/dist/release.js.map +1 -1
  65. package/dist/runtime.js +85 -21
  66. package/dist/runtime.js.map +1 -1
  67. package/dist/server.js +24 -21
  68. package/dist/server.js.map +1 -1
  69. package/dist/sessions/anchor.d.ts +2 -1
  70. package/dist/sessions/anchor.js +5 -1
  71. package/dist/sessions/anchor.js.map +1 -1
  72. package/dist/sessions/background-tasks.d.ts +23 -3
  73. package/dist/sessions/background-tasks.js +74 -4
  74. package/dist/sessions/background-tasks.js.map +1 -1
  75. package/dist/sessions/cli-sessions.d.ts +9 -2
  76. package/dist/sessions/cli-sessions.js +38 -57
  77. package/dist/sessions/cli-sessions.js.map +1 -1
  78. package/dist/sessions/completions.d.ts +8 -0
  79. package/dist/sessions/completions.js +55 -25
  80. package/dist/sessions/completions.js.map +1 -1
  81. package/dist/sessions/export.d.ts +2 -0
  82. package/dist/sessions/export.js +6 -2
  83. package/dist/sessions/export.js.map +1 -1
  84. package/dist/sessions/gateway-sessions.d.ts +44 -4
  85. package/dist/sessions/gateway-sessions.js +626 -90
  86. package/dist/sessions/gateway-sessions.js.map +1 -1
  87. package/dist/sessions/gateway-watcher.d.ts +10 -11
  88. package/dist/sessions/gateway-watcher.js +25 -21
  89. package/dist/sessions/gateway-watcher.js.map +1 -1
  90. package/dist/sessions/list.d.ts +9 -2
  91. package/dist/sessions/list.js +38 -26
  92. package/dist/sessions/list.js.map +1 -1
  93. package/dist/sessions/merge.d.ts +11 -5
  94. package/dist/sessions/merge.js +59 -17
  95. package/dist/sessions/merge.js.map +1 -1
  96. package/dist/sessions/prefix-index.d.ts +7 -4
  97. package/dist/sessions/prefix-index.js +21 -10
  98. package/dist/sessions/prefix-index.js.map +1 -1
  99. package/dist/sessions/recovery.d.ts +1 -1
  100. package/dist/sessions/recovery.js +4 -3
  101. package/dist/sessions/recovery.js.map +1 -1
  102. package/dist/sessions/service.d.ts +107 -37
  103. package/dist/sessions/service.js +490 -156
  104. package/dist/sessions/service.js.map +1 -1
  105. package/dist/sessions/session.d.ts +2 -0
  106. package/dist/sessions/session.js +4 -0
  107. package/dist/sessions/session.js.map +1 -1
  108. package/dist/sessions/state-db-worker.js +22 -5
  109. package/dist/sessions/state-db-worker.js.map +1 -1
  110. package/dist/sessions/state-db.d.ts +106 -15
  111. package/dist/sessions/state-db.js +651 -236
  112. package/dist/sessions/state-db.js.map +1 -1
  113. package/dist/sessions/state-saved.d.ts +1 -1
  114. package/dist/sessions/state-saved.js +35 -11
  115. package/dist/sessions/state-saved.js.map +1 -1
  116. package/dist/sessions/storage/backend.d.ts +10 -1
  117. package/dist/sessions/storage/backend.js.map +1 -1
  118. package/dist/sessions/storage/conformance.js +1 -1
  119. package/dist/sessions/storage/conformance.js.map +1 -1
  120. package/dist/sessions/storage/json-documents.d.ts +2 -0
  121. package/dist/sessions/storage/json-documents.js +70 -26
  122. package/dist/sessions/storage/json-documents.js.map +1 -1
  123. package/dist/sessions/storage/json-files.d.ts +13 -2
  124. package/dist/sessions/storage/json-files.js +126 -6
  125. package/dist/sessions/storage/json-files.js.map +1 -1
  126. package/dist/sessions/storage/json-journal.d.ts +6 -7
  127. package/dist/sessions/storage/json-journal.js +143 -73
  128. package/dist/sessions/storage/json-journal.js.map +1 -1
  129. package/dist/sessions/storage/json.d.ts +15 -1
  130. package/dist/sessions/storage/json.js +125 -33
  131. package/dist/sessions/storage/json.js.map +1 -1
  132. package/dist/sessions/store.d.ts +5 -1
  133. package/dist/sessions/store.js +67 -27
  134. package/dist/sessions/store.js.map +1 -1
  135. package/dist/sessions/streams.d.ts +8 -7
  136. package/dist/sessions/streams.js +49 -14
  137. package/dist/sessions/streams.js.map +1 -1
  138. package/dist/sessions/tool-display.d.ts +3 -1
  139. package/dist/sessions/tool-display.js +6 -0
  140. package/dist/sessions/tool-display.js.map +1 -1
  141. package/dist/sessions/turn-context.js +6 -2
  142. package/dist/sessions/turn-context.js.map +1 -1
  143. package/dist/sessions/turn.d.ts +23 -5
  144. package/dist/sessions/turn.js +168 -78
  145. package/dist/sessions/turn.js.map +1 -1
  146. package/dist/sidecar/client.d.ts +24 -1
  147. package/dist/sidecar/client.js +118 -38
  148. package/dist/sidecar/client.js.map +1 -1
  149. package/dist/sidecar/fake.d.ts +6 -2
  150. package/dist/sidecar/fake.js +15 -2
  151. package/dist/sidecar/fake.js.map +1 -1
  152. package/dist/spa.d.ts +9 -4
  153. package/dist/spa.js +36 -12
  154. package/dist/spa.js.map +1 -1
  155. package/dist/startup-timing.d.ts +66 -0
  156. package/dist/startup-timing.js +129 -0
  157. package/dist/startup-timing.js.map +1 -0
  158. package/dist/test/fs-handles.d.ts +11 -0
  159. package/dist/test/fs-handles.js +31 -0
  160. package/dist/test/fs-handles.js.map +1 -0
  161. package/dist/tools/connectors.d.ts +1 -1
  162. package/dist/tools/connectors.js +2 -2
  163. package/dist/tools/connectors.js.map +1 -1
  164. package/dist/tools/crons.d.ts +8 -4
  165. package/dist/tools/crons.js +25 -18
  166. package/dist/tools/crons.js.map +1 -1
  167. package/dist/tools/dashboard-plugins.d.ts +29 -0
  168. package/dist/tools/dashboard-plugins.js +132 -0
  169. package/dist/tools/dashboard-plugins.js.map +1 -1
  170. package/dist/tools/gateway-restart.d.ts +25 -5
  171. package/dist/tools/gateway-restart.js +103 -23
  172. package/dist/tools/gateway-restart.js.map +1 -1
  173. package/dist/tools/health.d.ts +13 -1
  174. package/dist/tools/health.js +80 -22
  175. package/dist/tools/health.js.map +1 -1
  176. package/dist/tools/insights.d.ts +1 -1
  177. package/dist/tools/insights.js +2 -2
  178. package/dist/tools/insights.js.map +1 -1
  179. package/dist/tools/mcp-health.js +74 -24
  180. package/dist/tools/mcp-health.js.map +1 -1
  181. package/dist/tools/mcp.js +19 -1
  182. package/dist/tools/mcp.js.map +1 -1
  183. package/dist/tools/plugin-oauth.d.ts +14 -0
  184. package/dist/tools/plugin-oauth.js +53 -0
  185. package/dist/tools/plugin-oauth.js.map +1 -0
  186. package/dist/tools/prune-advice.d.ts +50 -0
  187. package/dist/tools/prune-advice.js +114 -0
  188. package/dist/tools/prune-advice.js.map +1 -0
  189. package/dist/tools/skills.d.ts +5 -2
  190. package/dist/tools/skills.js +9 -6
  191. package/dist/tools/skills.js.map +1 -1
  192. package/dist/tools/update-notifications.d.ts +73 -7
  193. package/dist/tools/update-notifications.js +277 -43
  194. package/dist/tools/update-notifications.js.map +1 -1
  195. package/dist/tools/updates.d.ts +8 -1
  196. package/dist/tools/updates.js +51 -17
  197. package/dist/tools/updates.js.map +1 -1
  198. package/dist/workspace/media.js +69 -20
  199. package/dist/workspace/media.js.map +1 -1
  200. package/dist/workspace/preview.js +4 -14
  201. package/dist/workspace/preview.js.map +1 -1
  202. package/dist/workspace/rollback.d.ts +6 -2
  203. package/dist/workspace/rollback.js +17 -7
  204. package/dist/workspace/rollback.js.map +1 -1
  205. package/package.json +3 -3
  206. package/sidecar/agent_dependency.json +4 -4
  207. package/sidecar/agent_support.json +17 -14
  208. package/sidecar/scripts/suite_runner.py +183 -27
  209. package/sidecar/scripts/test.sh +5 -7
  210. package/sidecar/talaria_sidecar/__init__.py +1 -1
  211. package/sidecar/talaria_sidecar/__main__.py +10 -3
  212. package/sidecar/talaria_sidecar/agent_mutation.py +159 -23
  213. package/sidecar/talaria_sidecar/agent_services.py +74 -11
  214. package/sidecar/talaria_sidecar/context_detail.py +59 -0
  215. package/sidecar/talaria_sidecar/home.py +97 -20
  216. package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
  217. package/sidecar/talaria_sidecar/methods/aux.py +80 -18
  218. package/sidecar/talaria_sidecar/methods/chat.py +571 -106
  219. package/sidecar/talaria_sidecar/methods/checkpoints.py +29 -4
  220. package/sidecar/talaria_sidecar/methods/commands.py +277 -16
  221. package/sidecar/talaria_sidecar/methods/config.py +73 -3
  222. package/sidecar/talaria_sidecar/methods/connectors.py +45 -13
  223. package/sidecar/talaria_sidecar/methods/cron.py +8 -2
  224. package/sidecar/talaria_sidecar/methods/dashboard_plugins.py +301 -0
  225. package/sidecar/talaria_sidecar/methods/gateway.py +24 -4
  226. package/sidecar/talaria_sidecar/methods/gateway_sessions.py +211 -27
  227. package/sidecar/talaria_sidecar/methods/kanban.py +2 -3
  228. package/sidecar/talaria_sidecar/methods/oauth.py +139 -11
  229. package/sidecar/talaria_sidecar/methods/plugin_catalog.py +198 -0
  230. package/sidecar/talaria_sidecar/methods/plugin_oauth.py +234 -0
  231. package/sidecar/talaria_sidecar/methods/process.py +70 -6
  232. package/sidecar/talaria_sidecar/methods/profiles.py +4 -3
  233. package/sidecar/talaria_sidecar/methods/runtime.py +2 -0
  234. package/sidecar/talaria_sidecar/methods/skills.py +22 -7
  235. package/sidecar/talaria_sidecar/methods/state_db.py +128 -42
  236. package/sidecar/talaria_sidecar/methods/toolsets.py +4 -2
  237. package/sidecar/talaria_sidecar/methods/worktree.py +121 -2
  238. package/sidecar/talaria_sidecar/rpc.py +25 -3
  239. package/sidecar/talaria_sidecar/runtime.py +42 -3
  240. package/sidecar/talaria_sidecar/speed.py +13 -1
  241. package/sidecar/talaria_sidecar/startup_timing.py +119 -0
  242. package/static/dist/FILES.txt +241 -213
  243. package/static/dist/THIRD_PARTY_LICENSES.md +6 -22
  244. package/static/dist/assets/AppShell-3isgvoro.js +8 -0
  245. package/static/dist/assets/Button-f4n6dapf.js +1 -0
  246. package/static/dist/assets/ChatPage-k7b6xfax.js +4 -0
  247. package/static/dist/assets/ConnectTargets-juqvt5lm.js +1 -0
  248. package/static/dist/assets/Dialog-hta5utr3.js +1 -0
  249. package/static/dist/assets/ExtensionRoute-zrhjn0wm.js +1 -0
  250. package/static/dist/assets/Field-b6ez48oz.js +1 -0
  251. package/static/dist/assets/HubRoute-iwvso2tz.js +2 -0
  252. package/static/dist/assets/LinkCheckContext-ix6zqqkk.js +1 -0
  253. package/static/dist/assets/Markdown-jdeth7zq.js +2 -0
  254. package/static/dist/assets/MarkdownRenderer-fbswsgld.js +302 -0
  255. package/static/dist/assets/MarkdownRenderer-i9o6nr38.css +1 -0
  256. package/static/dist/assets/OAuthSignIn-bpd8hom3.js +1 -0
  257. package/static/dist/assets/ResourceSwitch-odi6dy2o.js +1 -0
  258. package/static/dist/assets/RightPanel-eu6enlo6.js +1 -0
  259. package/static/dist/assets/Select-kr07928w.js +1 -0
  260. package/static/dist/assets/SessionListPanel-etb2i54l.js +1 -0
  261. package/static/dist/assets/SettingsLayout-eu3xxp23.js +1 -0
  262. package/static/dist/assets/States-icab19yg.js +1 -0
  263. package/static/dist/assets/TerminalPanel-hngmsr8b.js +16 -0
  264. package/static/dist/assets/Toaster-nb2uzjw1.js +1 -0
  265. package/static/dist/assets/_app-ga1moszl.js +1 -0
  266. package/static/dist/assets/_app.ext._extensionId-na62087t.js +1 -0
  267. package/static/dist/assets/_app.index-jypgdqoi.js +1 -0
  268. package/static/dist/assets/_app.insights-kabejdtm.js +1 -0
  269. package/static/dist/assets/_app.kanban-m19kfcmx.js +1 -0
  270. package/static/dist/assets/_app.logs-iw4m96sz.js +1 -0
  271. package/static/dist/assets/_app.memory-jq5frwcg.js +1 -0
  272. package/static/dist/assets/_app.profiles-kwa3m7rp.js +1 -0
  273. package/static/dist/assets/_app.session._sessionId-o5lyeipj.js +1 -0
  274. package/static/dist/assets/_app.settings-ckl8hdll.js +1 -0
  275. package/static/dist/assets/_app.settings._section-dtdjk98a.js +3 -0
  276. package/static/dist/assets/_app.skills-n016rihd.js +1 -0
  277. package/static/dist/assets/_app.tasks-n8zuqsqe.js +1 -0
  278. package/static/dist/assets/_app.todos-btew5v2s.js +1 -0
  279. package/static/dist/assets/_app.workspaces-lnfk95ch.js +1 -0
  280. package/static/dist/assets/abnfDiagram-VCTEODGH-dttheegb.js +1 -0
  281. package/static/dist/assets/{angular-html-nu6l31w3.js → angular-html-l28bd5fo.js} +1 -1
  282. package/static/dist/assets/{angular-ts-fbx4f8mm.js → angular-ts-ffzx1vj0.js} +1 -1
  283. package/static/dist/assets/{apl-bx2xr7dh.js → apl-b7h7mmy4.js} +1 -1
  284. package/static/dist/assets/{arc-7popfhbk.js → arc-fk1b21lm.js} +1 -1
  285. package/static/dist/assets/architecture-7GRP2DOG-ic435919.js +1 -0
  286. package/static/dist/assets/architectureDiagram-5GKGNRK7-8r3glgiq.js +36 -0
  287. package/static/dist/assets/archive-il7c6053.js +1 -0
  288. package/static/dist/assets/{astro-g77i97cb.js → astro-k117fy0t.js} +1 -1
  289. package/static/dist/assets/back-gu3nu279.js +1 -0
  290. package/static/dist/assets/{blade-boo0u92y.js → blade-npefsmn8.js} +1 -1
  291. package/static/dist/assets/{blockDiagram-I7D4REHJ-joedghi3.js → blockDiagram-I7D4REHJ-jenyos6c.js} +4 -4
  292. package/static/dist/assets/{c-cat0w3sc.js → c-2umg6kbn.js} +1 -1
  293. package/static/dist/assets/{c4Diagram-7LVT6UL2-hyrjrohe.js → c4Diagram-7LVT6UL2-nzlx7yc4.js} +1 -1
  294. package/static/dist/assets/cancel-bsnfo36u.js +1 -0
  295. package/static/dist/assets/channel-dsbtk01y.js +1 -0
  296. package/static/dist/assets/{chunk-4HAMMTFA-koouqbut.js → chunk-4HAMMTFA-g0cru63b.js} +20 -20
  297. package/static/dist/assets/{chunk-75Z2AOVW-bl4bhdnt.js → chunk-75Z2AOVW-7rkswkjw.js} +1 -1
  298. package/static/dist/assets/chunk-DU6HZSFF-fn73jzdo.js +128 -0
  299. package/static/dist/assets/{chunk-F27PBJKO-d8xxi0zi.js → chunk-F27PBJKO-oey8e72m.js} +1 -1
  300. package/static/dist/assets/{chunk-FOHPRMQF-opuflp73.js → chunk-FOHPRMQF-ipewusgj.js} +8 -8
  301. package/static/dist/assets/{chunk-GMAD6QVW-jro0hl73.js → chunk-GMAD6QVW-l2a32wiy.js} +6 -6
  302. package/static/dist/assets/{chunk-GVQU2GXP-djn6gbyu.js → chunk-GVQU2GXP-o9dwzh8h.js} +1 -1
  303. package/static/dist/assets/{chunk-IMKFNOWR-vw4p5p7o.js → chunk-IMKFNOWR-eblwj20b.js} +5 -5
  304. package/static/dist/assets/{chunk-L3NEJ4N5-n1hj77q8.js → chunk-L3NEJ4N5-o2mufspb.js} +1 -1
  305. package/static/dist/assets/chunk-OSK3NFVY-ijp9pw29.js +10 -0
  306. package/static/dist/assets/{chunk-P2QGCYS3-onf1hz6c.js → chunk-P2QGCYS3-hs9hqwl4.js} +1 -1
  307. package/static/dist/assets/{chunk-POPQ4Y6H-m8sqn7cs.js → chunk-POPQ4Y6H-bp9rsqez.js} +1 -1
  308. package/static/dist/assets/{chunk-PWAF6VOD-ek4smc8x.js → chunk-PWAF6VOD-brkprov6.js} +1 -1
  309. package/static/dist/assets/{chunk-SHT3W25Y-g9gfvefo.js → chunk-SHT3W25Y-gmpv0pxz.js} +13 -13
  310. package/static/dist/assets/{chunk-SVP7TREG-f9nfv2cu.js → chunk-SVP7TREG-oldm08fu.js} +4 -4
  311. package/static/dist/assets/{chunk-TICWLB2K-bnk3m1mq.js → chunk-TICWLB2K-ooqilfv7.js} +6 -6
  312. package/static/dist/assets/{chunk-XXDRQBXY-n40pf9vy.js → chunk-XXDRQBXY-jkmnqu65.js} +1 -1
  313. package/static/dist/assets/classDiagram-ZZMXUADV-fvgcv24g.js +1 -0
  314. package/static/dist/assets/classDiagram-v2-VYDZK3BY-fvgcv24g.js +1 -0
  315. package/static/dist/assets/{cobol-go622aep.js → cobol-ssf9hkh7.js} +1 -1
  316. package/static/dist/assets/{coffee-jwo1xjhq.js → coffee-lsi9fwae.js} +1 -1
  317. package/static/dist/assets/copied-bz3gkazh.js +1 -0
  318. package/static/dist/assets/copy-jo006coq.js +1 -0
  319. package/static/dist/assets/cose-bilkent-JH36ORCC-layy2r27.js +1 -0
  320. package/static/dist/assets/{cpp-nre7j0kq.js → cpp-lfrh4y55.js} +1 -1
  321. package/static/dist/assets/{crystal-lviso2je.js → crystal-efh4pte7.js} +1 -1
  322. package/static/dist/assets/{css-ywz9j4hb.js → css-i82qdo89.js} +1 -1
  323. package/static/dist/assets/cynefin-OW5HDTMX-d47op7qs.js +1 -0
  324. package/static/dist/assets/cynefinDiagram-5FMLGOSQ-doa7594y.js +62 -0
  325. package/static/dist/assets/cytoscape.esm-fjn2algp.js +321 -0
  326. package/static/dist/assets/dagre-GXQ25YYZ-os768dp4.js +4 -0
  327. package/static/dist/assets/dagre-o5jmjz46.js +1 -0
  328. package/static/dist/assets/{diagram-S7CK7UJ4-m6aeoe7c.js → diagram-S7CK7UJ4-fyi81nxd.js} +4 -4
  329. package/static/dist/assets/diagram-UQ7AKVKN-j3exggaq.js +41 -0
  330. package/static/dist/assets/{diagram-VSXAHHWV-hbawi7hn.js → diagram-VSXAHHWV-omo1en2l.js} +1 -1
  331. package/static/dist/assets/diagram-VX7I27RA-oro2cyu0.js +24 -0
  332. package/static/dist/assets/diagram-Z3DM3KII-byt9vdb7.js +24 -0
  333. package/static/dist/assets/dist-crc46di9.js +7 -0
  334. package/static/dist/assets/dist-etpoyscq.js +44 -0
  335. package/static/dist/assets/{dist-c4c2m3kv.js → dist-nb670eu6.js} +1 -1
  336. package/static/dist/assets/ebnfDiagram-PWID7BFC-ee3haugh.js +1 -0
  337. package/static/dist/assets/{edge-dvv35byi.js → edge-lrob635n.js} +1 -1
  338. package/static/dist/assets/{elixir-i2f1ki1g.js → elixir-kdpxeq93.js} +1 -1
  339. package/static/dist/assets/{elm-cpepyjm4.js → elm-c8irj9uh.js} +1 -1
  340. package/static/dist/assets/erDiagram-RLTQ6QDP-d1e93yff.js +99 -0
  341. package/static/dist/assets/{erb-idlyfheg.js → erb-flwsv146.js} +1 -1
  342. package/static/dist/assets/eventmodeling-NTZA5JFV-i1ihb40c.js +1 -0
  343. package/static/dist/assets/flowDiagram-HODETNUW-jahfpiod.js +1 -0
  344. package/static/dist/assets/folder-plus-ivoglq98.js +1 -0
  345. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ma6jxj5i.js → ganttDiagram-EL5Y4UJY-m7g0jud4.js} +1 -1
  346. package/static/dist/assets/{git-rebase-d9lpfpbn.js → git-rebase-bli19aoq.js} +1 -1
  347. package/static/dist/assets/gitGraph-4MIJSDKK-hzdj07yy.js +1 -0
  348. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-hzltvsod.js → gitGraphDiagram-WWUBYQGX-dm9agbna.js} +1 -1
  349. package/static/dist/assets/{glimmer-js-39i0urfb.js → glimmer-js-d4icg38p.js} +1 -1
  350. package/static/dist/assets/{glimmer-ts-gdtd6n73.js → glimmer-ts-bk75772u.js} +1 -1
  351. package/static/dist/assets/{glsl-o1djm5pp.js → glsl-lqormp2b.js} +1 -1
  352. package/static/dist/assets/{graphql-kj6rz55t.js → graphql-hw6s5qzd.js} +1 -1
  353. package/static/dist/assets/{hack-pe1qqr2i.js → hack-ozrv6l4d.js} +1 -1
  354. package/static/dist/assets/{haml-cknnbcqs.js → haml-gytfosdt.js} +1 -1
  355. package/static/dist/assets/{handlebars-hwq64orh.js → handlebars-b6g7jffd.js} +1 -1
  356. package/static/dist/assets/highlighted-body-KPVGNVTW-mmgz2czu.js +1 -0
  357. package/static/dist/assets/host-fr6lni1p.js +1 -0
  358. package/static/dist/assets/{html-derivative-h2i2pyjc.js → html-derivative-f1tqcroo.js} +1 -1
  359. package/static/dist/assets/{html-ha05xtvt.js → html-on6oj6yj.js} +1 -1
  360. package/static/dist/assets/{http-cf94zu27.js → http-odhufsl7.js} +1 -1
  361. package/static/dist/assets/{hurl-lslax568.js → hurl-mu680336.js} +1 -1
  362. package/static/dist/assets/index-73649w7o.js +10 -0
  363. package/static/dist/assets/index-lhf1fsdf.css +2 -0
  364. package/static/dist/assets/info-A6RAGUB7-lll1dqmm.js +1 -0
  365. package/static/dist/assets/infoDiagram-27XIBGKW-k7esc639.js +2 -0
  366. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-e7bnwq7h.js → ishikawaDiagram-5VMMS53U-lgr6k9wo.js} +6 -6
  367. package/static/dist/assets/{java-ndjf2o89.js → java-nxbe8uju.js} +1 -1
  368. package/static/dist/assets/{javascript-coftlwy3.js → javascript-lz3kxcyz.js} +1 -1
  369. package/static/dist/assets/{jinja-etei9wy7.js → jinja-l5pk84gt.js} +1 -1
  370. package/static/dist/assets/{jison-hyekgft1.js → jison-jdutdwyy.js} +1 -1
  371. package/static/dist/assets/{journeyDiagram-3NMN7TZE-etd1bi6p.js → journeyDiagram-3NMN7TZE-ndmbxuqk.js} +1 -1
  372. package/static/dist/assets/{json-lok0yaho.js → json-dnge4ssf.js} +1 -1
  373. package/static/dist/assets/{jsx-b64w0nbx.js → jsx-c3lwhkep.js} +1 -1
  374. package/static/dist/assets/{jsx-runtime-eu92a5pu.js → jsx-runtime-o40ukmjl.js} +1 -1
  375. package/static/dist/assets/{julia-jxxzz2yn.js → julia-hwd4rrcb.js} +1 -1
  376. package/static/dist/assets/{just-mc29iw9t.js → just-nka3oxdx.js} +1 -1
  377. package/static/dist/assets/{kanban-definition-UXKFOSKX-emb1g9dz.js → kanban-definition-UXKFOSKX-mjxym0jp.js} +28 -28
  378. package/static/dist/assets/katex-cbdd1ohw.css +1 -0
  379. package/static/dist/assets/katex-pbij2oo6.js +257 -0
  380. package/static/dist/assets/{latex-pafa03ft.js → latex-iqmr7e40.js} +1 -1
  381. package/static/dist/assets/lazyRouteComponent-cwb6l7xh.js +1 -0
  382. package/static/dist/assets/{line-h0ojzsvs.js → line-mtr5k6il.js} +1 -1
  383. package/static/dist/assets/linear-jlwrvvy6.js +1 -0
  384. package/static/dist/assets/link-iadw4ghw.js +2 -0
  385. package/static/dist/assets/{liquid-hz5c2pov.js → liquid-okmk9tbp.js} +1 -1
  386. package/static/dist/assets/locale-cs-n41hptse.js +5 -0
  387. package/static/dist/assets/locale-de-k7ht47ew.js +5 -0
  388. package/static/dist/assets/locale-es-ih078lco.js +5 -0
  389. package/static/dist/assets/locale-fr-ojabnf41.js +5 -0
  390. package/static/dist/assets/locale-it-kb96jufy.js +5 -0
  391. package/static/dist/assets/locale-ja-jpsp3xs1.js +7 -0
  392. package/static/dist/assets/locale-ko-cpzuy0n7.js +5 -0
  393. package/static/dist/assets/locale-pl-dbap4leu.js +5 -0
  394. package/static/dist/assets/locale-pt-nci7w72x.js +5 -0
  395. package/static/dist/assets/locale-ru-soghe4cs.js +7 -0
  396. package/static/dist/assets/locale-tr-ks00mu68.js +5 -0
  397. package/static/dist/assets/locale-vi-ij7twfm6.js +5 -0
  398. package/static/dist/assets/locale-zh-Hant-feakwr7a.js +5 -0
  399. package/static/dist/assets/locale-zh-ren0vjyv.js +7 -0
  400. package/static/dist/assets/login-lkhtzfcl.js +1 -0
  401. package/static/dist/assets/{lua-leyf8mlb.js → lua-krx4xj5h.js} +1 -1
  402. package/static/dist/assets/{marko-cn1fzpdv.js → marko-gbvpfehz.js} +1 -1
  403. package/static/dist/assets/{mdc-huhy88df.js → mdc-c21zlyi5.js} +1 -1
  404. package/static/dist/assets/mermaid-HWGCJPDP-jx0q5dm2.js +1 -0
  405. package/static/dist/assets/mermaid-parser.core-k91vdnvi.js +7 -0
  406. package/static/dist/assets/micromark-factory-space-fnmnpi6s.js +1 -0
  407. package/static/dist/assets/{mindmap-definition-YA3MSWOX-o1vathns.js → mindmap-definition-YA3MSWOX-ehztfea4.js} +28 -28
  408. package/static/dist/assets/{nginx-e7lvhsmn.js → nginx-f3d68ed4.js} +1 -1
  409. package/static/dist/assets/{nim-dun5d8mk.js → nim-dw0jcy70.js} +1 -1
  410. package/static/dist/assets/onboarding-f67z0a2r.js +1 -0
  411. package/static/dist/assets/packet-AYTQ26CC-ok2xrblz.js +1 -0
  412. package/static/dist/assets/pegDiagram-XKGWAZYB-ec3rpj0m.js +1 -0
  413. package/static/dist/assets/{perl-bj3oqtet.js → perl-3c0r45c8.js} +1 -1
  414. package/static/dist/assets/{php-buzp12r0.js → php-rznhidvp.js} +1 -1
  415. package/static/dist/assets/pie-WAS4IAKB-iwkir23u.js +1 -0
  416. package/static/dist/assets/pieDiagram-E7YTZNPT-fcpqpekh.js +39 -0
  417. package/static/dist/assets/{pug-mjad7he9.js → pug-f2le4f3y.js} +1 -1
  418. package/static/dist/assets/{qml-d1gdp10s.js → qml-e2wbq9cr.js} +1 -1
  419. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-owqc4uqn.js → quadrantDiagram-AXDQQJYC-lqsync43.js} +1 -1
  420. package/static/dist/assets/queryKeys-ed4n2egc.js +9 -0
  421. package/static/dist/assets/{r-o3r9c7ao.js → r-epfzuw6q.js} +1 -1
  422. package/static/dist/assets/radar-RG4KPBEZ-hje4jbk6.js +1 -0
  423. package/static/dist/assets/railroad-74A4TZTK-gll9n6qi.js +1 -0
  424. package/static/dist/assets/railroad-abnf-HS5TGJTU-o6h6lpn4.js +1 -0
  425. package/static/dist/assets/railroad-ebnf-LZEXJU2U-juaefj6o.js +1 -0
  426. package/static/dist/assets/railroad-peg-WCYAUIDC-cgfhvpah.js +1 -0
  427. package/static/dist/assets/railroadDiagram-O6MQD6OU-lp3w7awf.js +1 -0
  428. package/static/dist/assets/{razor-ilc585yr.js → razor-pea1u1d0.js} +1 -1
  429. package/static/dist/assets/refresh-cw-iccm7a4e.js +1 -0
  430. package/static/dist/assets/refresh-i9ya9dvl.js +1 -0
  431. package/static/dist/assets/{regexp-mpjtcvwm.js → regexp-hzte0a5x.js} +1 -1
  432. package/static/dist/assets/registry-g7w3353u.js +1 -0
  433. package/static/dist/assets/requirementDiagram-BXWQKSXE-ha8eibcf.js +84 -0
  434. package/static/dist/assets/rolldown-runtime-bnnw9q4a.js +1 -0
  435. package/static/dist/assets/{rst-jrkwjdyi.js → rst-m6tugj5o.js} +1 -1
  436. package/static/dist/assets/{ruby-n4tjcbpz.js → ruby-l19vq3w1.js} +1 -1
  437. package/static/dist/assets/runtime-nlxpnegr.js +1 -0
  438. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-iqc02emv.js → sankeyDiagram-P5KCCOFB-oxf31pr9.js} +1 -1
  439. package/static/dist/assets/{sas-hjo6y93k.js → sas-ktdxo397.js} +1 -1
  440. package/static/dist/assets/{scss-ncidjv48.js → scss-kn3oxfwf.js} +1 -1
  441. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-i0v116q0.js → sequenceDiagram-WJ2MYXX4-jiq8lhyo.js} +11 -11
  442. package/static/dist/assets/settings_section_extensions_title-fadc7o65.js +1 -0
  443. package/static/dist/assets/share._token-o76ki0ly.js +2 -0
  444. package/static/dist/assets/{shellscript-m8whjpqd.js → shellscript-l100jtrg.js} +1 -1
  445. package/static/dist/assets/{shellsession-i5bj6syz.js → shellsession-m5qukwow.js} +1 -1
  446. package/static/dist/assets/sizeCapture-INFHLROL-dk1io13w.js +1 -0
  447. package/static/dist/assets/sliders-horizontal-ddcs88av.js +1 -0
  448. package/static/dist/assets/{soy-hhh36ysq.js → soy-mexlscur.js} +1 -1
  449. package/static/dist/assets/{sql-j21bzcl6.js → sql-o0ho5suh.js} +1 -1
  450. package/static/dist/assets/src-h89s0hs4.js +1 -0
  451. package/static/dist/assets/{stata-bbu2onh1.js → stata-be9ck1q3.js} +1 -1
  452. package/static/dist/assets/stateDiagram-D77RDMKH-f3fslnon.js +1 -0
  453. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hlt6yphw.js +1 -0
  454. package/static/dist/assets/{surrealql-l5vczuco.js → surrealql-gk1xkkyy.js} +1 -1
  455. package/static/dist/assets/{svelte-idn422g6.js → svelte-k6ecjilf.js} +1 -1
  456. package/static/dist/assets/swimlanes-42K2YHIH-k1hmk7jf.js +1 -0
  457. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-hd5t9tnt.js +8 -0
  458. package/static/dist/assets/{templ-e22qou3c.js → templ-gf3bftk6.js} +1 -1
  459. package/static/dist/assets/{tex-deqpc5ee.js → tex-d21fqv1z.js} +1 -1
  460. package/static/dist/assets/{timeline-definition-24CTP7MA-ol2hnvma.js → timeline-definition-24CTP7MA-n3ncfy9r.js} +8 -8
  461. package/static/dist/assets/toast-liparz2j.js +56 -0
  462. package/static/dist/assets/treeView-Q6P3EWNA-l3y2pejt.js +1 -0
  463. package/static/dist/assets/treemap-WGGIJYW6-jown9axh.js +1 -0
  464. package/static/dist/assets/{ts-tags-ctnibc27.js → ts-tags-n0g61gr6.js} +1 -1
  465. package/static/dist/assets/{tsx-i163n32g.js → tsx-cvv7wv6q.js} +1 -1
  466. package/static/dist/assets/{twig-x0fi7wm8.js → twig-n1f67204.js} +1 -1
  467. package/static/dist/assets/{typescript-g18c34xz.js → typescript-og7o55cx.js} +1 -1
  468. package/static/dist/assets/untitled-mj86r4gz.js +1 -0
  469. package/static/dist/assets/useDebounced-g8r0ggve.js +1 -0
  470. package/static/dist/assets/useForm-o4uu74mx.js +1 -0
  471. package/static/dist/assets/{useParams-gjc70qru.js → useParams-ftsveu84.js} +1 -1
  472. package/static/dist/assets/{useSearch-e6ae539u.js → useSearch-crqueqga.js} +1 -1
  473. package/static/dist/assets/vennDiagram-4TSXK5OY-dh59uq1c.js +34 -0
  474. package/static/dist/assets/{vue-mwu5l1bb.js → vue-ej4xpwqw.js} +1 -1
  475. package/static/dist/assets/{vue-html-nskj3du0.js → vue-html-d48xsjuo.js} +1 -1
  476. package/static/dist/assets/{vue-vine-cqe77smq.js → vue-vine-pev88ip6.js} +1 -1
  477. package/static/dist/assets/wardley-WFR3VGLG-e6d74b0j.js +1 -0
  478. package/static/dist/assets/wardleyDiagram-VM6X3IG4-hnofh78f.js +78 -0
  479. package/static/dist/assets/with-selector-nu1b5z0v.js +1 -0
  480. package/static/dist/assets/{xml-gau7gvrv.js → xml-ifxp6gqb.js} +1 -1
  481. package/static/dist/assets/{xsl-ew09zq8b.js → xsl-fug2cckc.js} +1 -1
  482. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-aqbgvujg.js → xychartDiagram-S5SC5T6Z-bpymjelb.js} +6 -6
  483. package/static/dist/assets/{yaml-ofnrxtvf.js → yaml-fjmafnlo.js} +1 -1
  484. package/static/dist/extension-sdk.js +1 -1
  485. package/static/dist/index.html +1 -1
  486. package/static/dist/sw.js +180 -136
  487. package/static/dist/assets/AppShell-cey33v9s.js +0 -8
  488. package/static/dist/assets/ChatPage-i8wnugr7.js +0 -18
  489. package/static/dist/assets/ConnectTargets-hg19553x.js +0 -1
  490. package/static/dist/assets/ExtensionRoute-cly4apm5.js +0 -1
  491. package/static/dist/assets/Field-j8uqoigz.js +0 -9
  492. package/static/dist/assets/HubRoute-e4bgz41j.js +0 -2
  493. package/static/dist/assets/Markdown-lahuichm.js +0 -598
  494. package/static/dist/assets/Markdown-lwviulmg.css +0 -1
  495. package/static/dist/assets/RightPanel-lbo4i0fn.js +0 -1
  496. package/static/dist/assets/Select-gqatm2e0.js +0 -1
  497. package/static/dist/assets/SessionListPanel-mfqayfjh.js +0 -1
  498. package/static/dist/assets/SettingsLayout-b8980u7k.js +0 -1
  499. package/static/dist/assets/States-e7dkgtxf.js +0 -1
  500. package/static/dist/assets/Toaster-bfqqy786.js +0 -1
  501. package/static/dist/assets/_app-n1n6pfx1.js +0 -1
  502. package/static/dist/assets/_app.ext._extensionId-buxjxav3.js +0 -1
  503. package/static/dist/assets/_app.index-jt8zwiu2.js +0 -1
  504. package/static/dist/assets/_app.insights-cv76ml71.js +0 -1
  505. package/static/dist/assets/_app.kanban-h6ts53j7.js +0 -1
  506. package/static/dist/assets/_app.logs-dm873z90.js +0 -1
  507. package/static/dist/assets/_app.memory-glk5y6m8.js +0 -1
  508. package/static/dist/assets/_app.profiles-ka6zpstq.js +0 -1
  509. package/static/dist/assets/_app.session._sessionId-g2vzghvc.js +0 -1
  510. package/static/dist/assets/_app.settings-hgjdhzcd.js +0 -1
  511. package/static/dist/assets/_app.settings._section-jf2kmhae.js +0 -3
  512. package/static/dist/assets/_app.skills-cii852yq.js +0 -1
  513. package/static/dist/assets/_app.tasks-fp9q7atm.js +0 -1
  514. package/static/dist/assets/_app.todos-dw94qzue.js +0 -1
  515. package/static/dist/assets/_app.workspaces-inyygzze.js +0 -1
  516. package/static/dist/assets/abnfDiagram-VCTEODGH-gjayq3ra.js +0 -1
  517. package/static/dist/assets/architecture-7GRP2DOG-lp56972r.js +0 -1
  518. package/static/dist/assets/architectureDiagram-5GKGNRK7-nwb3xovn.js +0 -36
  519. package/static/dist/assets/archive-b1j004vn.js +0 -1
  520. package/static/dist/assets/back-g74gkozu.js +0 -1
  521. package/static/dist/assets/channel-es95o4y8.js +0 -1
  522. package/static/dist/assets/chunk-DU6HZSFF-gpedttsp.js +0 -128
  523. package/static/dist/assets/chunk-OSK3NFVY-oy0gwn2n.js +0 -10
  524. package/static/dist/assets/classDiagram-ZZMXUADV-kmihoqxh.js +0 -1
  525. package/static/dist/assets/classDiagram-v2-VYDZK3BY-kmihoqxh.js +0 -1
  526. package/static/dist/assets/copied-bli9tbcx.js +0 -1
  527. package/static/dist/assets/copy-jamtd1pf.js +0 -1
  528. package/static/dist/assets/cose-bilkent-JH36ORCC-eclcy6ja.js +0 -1
  529. package/static/dist/assets/cynefin-OW5HDTMX-f8xfdd3f.js +0 -1
  530. package/static/dist/assets/cynefinDiagram-5FMLGOSQ-n9lb99h9.js +0 -62
  531. package/static/dist/assets/cytoscape.esm-bqu53u12.js +0 -321
  532. package/static/dist/assets/dagre-GXQ25YYZ-dgailddi.js +0 -4
  533. package/static/dist/assets/dagre-lkoknbjn.js +0 -1
  534. package/static/dist/assets/diagram-UQ7AKVKN-jall3j0x.js +0 -41
  535. package/static/dist/assets/diagram-VX7I27RA-hendl2tt.js +0 -24
  536. package/static/dist/assets/diagram-Z3DM3KII-igwc35p3.js +0 -24
  537. package/static/dist/assets/ebnfDiagram-PWID7BFC-c64knz08.js +0 -1
  538. package/static/dist/assets/erDiagram-RLTQ6QDP-brdu9lcm.js +0 -99
  539. package/static/dist/assets/eventmodeling-NTZA5JFV-njk1j8wl.js +0 -1
  540. package/static/dist/assets/flowDiagram-HODETNUW-nma6f0ga.js +0 -1
  541. package/static/dist/assets/folder-plus-lvt9kudb.js +0 -1
  542. package/static/dist/assets/gitGraph-4MIJSDKK-naa9w6xa.js +0 -1
  543. package/static/dist/assets/highlighted-body-KPVGNVTW-ghoavj8d.js +0 -1
  544. package/static/dist/assets/host-hzm8n85n.js +0 -1
  545. package/static/dist/assets/index-drdcaqeq.css +0 -2
  546. package/static/dist/assets/index-jzm844he.js +0 -11
  547. package/static/dist/assets/info-A6RAGUB7-mn2ut5ko.js +0 -1
  548. package/static/dist/assets/infoDiagram-27XIBGKW-h6kuqvsb.js +0 -2
  549. package/static/dist/assets/lazyRouteComponent-o816ytzl.js +0 -1
  550. package/static/dist/assets/linear-becwlnhk.js +0 -1
  551. package/static/dist/assets/link-k01ryaf9.js +0 -1
  552. package/static/dist/assets/login-bsae49ew.js +0 -1
  553. package/static/dist/assets/mermaid-HWGCJPDP-ihl34s74.js +0 -1
  554. package/static/dist/assets/mermaid-parser.core-bnre27u8.js +0 -7
  555. package/static/dist/assets/not-found-ccnnac39.js +0 -1
  556. package/static/dist/assets/onboarding-c65tysya.js +0 -1
  557. package/static/dist/assets/onboarding_model_label-gpn28uix.js +0 -1
  558. package/static/dist/assets/packet-AYTQ26CC-hatq7m2e.js +0 -1
  559. package/static/dist/assets/pegDiagram-XKGWAZYB-gwisz8jv.js +0 -1
  560. package/static/dist/assets/pie-WAS4IAKB-fpftf0mk.js +0 -1
  561. package/static/dist/assets/pieDiagram-E7YTZNPT-khm1o9a6.js +0 -39
  562. package/static/dist/assets/radar-RG4KPBEZ-cntfwjpq.js +0 -1
  563. package/static/dist/assets/railroad-74A4TZTK-a7t5eibp.js +0 -1
  564. package/static/dist/assets/railroad-abnf-HS5TGJTU-ol8cdktf.js +0 -1
  565. package/static/dist/assets/railroad-ebnf-LZEXJU2U-i8vzd429.js +0 -1
  566. package/static/dist/assets/railroad-peg-WCYAUIDC-nw3ulewz.js +0 -1
  567. package/static/dist/assets/railroadDiagram-O6MQD6OU-etuh0qa0.js +0 -1
  568. package/static/dist/assets/refresh-cw-mqbkv2a3.js +0 -1
  569. package/static/dist/assets/refresh-l29ljp76.js +0 -1
  570. package/static/dist/assets/requirementDiagram-BXWQKSXE-oq2ulk22.js +0 -84
  571. package/static/dist/assets/rolldown-runtime-b9miacsj.js +0 -1
  572. package/static/dist/assets/root-mbklpuv4.js +0 -1
  573. package/static/dist/assets/saved-ndd4t2xb.js +0 -1
  574. package/static/dist/assets/settings_section_extensions_title-jhfo6o8z.js +0 -1
  575. package/static/dist/assets/share._token-c1ri0hy1.js +0 -2
  576. package/static/dist/assets/sizeCapture-INFHLROL-g612l48j.js +0 -1
  577. package/static/dist/assets/sliders-horizontal-j3jjt9tv.js +0 -1
  578. package/static/dist/assets/src-hjyib4mu.js +0 -1
  579. package/static/dist/assets/stateDiagram-D77RDMKH-dnpt1ato.js +0 -1
  580. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-bxjacpk6.js +0 -1
  581. package/static/dist/assets/swimlanes-42K2YHIH-loqbdazb.js +0 -1
  582. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-id62p9hz.js +0 -8
  583. package/static/dist/assets/toast-i3h34v6o.js +0 -64
  584. package/static/dist/assets/treeView-Q6P3EWNA-idrj6iox.js +0 -1
  585. package/static/dist/assets/treemap-WGGIJYW6-lzr49vv5.js +0 -1
  586. package/static/dist/assets/untitled-oaoya4qj.js +0 -1
  587. package/static/dist/assets/useForm-nfaxgmsk.js +0 -1
  588. package/static/dist/assets/vennDiagram-4TSXK5OY-bvwr9o2z.js +0 -34
  589. package/static/dist/assets/wardley-WFR3VGLG-31ui8tma.js +0 -1
  590. package/static/dist/assets/wardleyDiagram-VM6X3IG4-k5wqtx4b.js +0 -78
  591. /package/static/dist/assets/{ChatPage-gmu5c7lf.css → TerminalPanel-gmu5c7lf.css} +0 -0
@@ -1,3 +1,4 @@
1
+ import { unavailableContextDetail } from '@maudecode/talaria-web-contracts';
1
2
  import { filterAsync } from '../util.js';
2
3
  import { statSignature } from '../fs/stat.js';
3
4
  import { SessionPrefixIndex } from './prefix-index.js';
@@ -8,16 +9,16 @@ import { buildActiveTurnToken, copyJson, redactSessionData, redactValue, stripPu
8
9
  import { DraftVersionConflict, normalizeDraftVersion } from './drafts.js';
9
10
  import { allSessions, buildSessionListPayload, reconcileLocalSessionRows, sessionLineageIds, CLI_IDENTITY_FIELDS, isClaimableCliSource, isMessagingSessionRecord, ownerLockCandidates, withOwnerLocks, withSessionWireFlags, lineageRootId, mergeCliSidebarMetadata, sessionListResponse, sessionSearchMatches, sessionSearchPreview, sessionSearchTerms } from './list.js';
10
11
  import { anchorSceneIntOrNull, fullToolResult, hydrateAnchorActivityScenes, normalizeAnchorSceneMessageRef, readAnchorSceneRows, storeAnchorScene, withTurnIds } from './anchor.js';
11
- import { contextUsage, COMPRESSION_RECOVERY_ACTION_START_FOCUSED, compressionRecoveryPayload, forgetContextFigures, isSafeSessionId, lastMessageTimestamp, Session, sharePath, stripAttachedFilesMarker, titleFrom } from './session.js';
12
+ import { contextUsage, COMPRESSION_RECOVERY_ACTION_START_FOCUSED, compressionRecoveryPayload, forgetContextFigures, isSafeSessionId, lastMessageTimestamp, Session, sharePath, stripAttachedFilesMarker, titleFrom, WORKTREE_RETAINED_KEY } from './session.js';
12
13
  import { SessionBusy, SessionNotFound } from './store.js';
13
14
  import { UNSETTLED_TODO_KEY, attachTodoSnapshot, attachTodoState } from './todo.js';
14
15
  import { isClaudeCodeSessionId } from './claude-code.js';
15
16
  import { auditSessionRecovery, repairSafeSessionRecovery } from './recovery.js';
16
- import { agentSessionRowsExisting, agentSessionRowsExistingAsync, CANONICAL_READ_ONLY_REASON, OWNERSHIP_UNAVAILABLE_REASON, CONVERSATION_ROUND_THRESHOLD, countConversationRounds, stateDbCompressionLineage, stateDbTailToolContent, stateDbLineageReport, stateDbLocalTarget, stateDbSessionMessages, stateDbSessionRead, stateDbSessionTailRead, stateDbSessionRow, stateDbSessionSources, stateDbSessionSourcesAsync, stateDbTimestampSeconds } from './state-db.js';
17
+ import { stateDbWebDiagnosticIdsAsync, agentSessionRowsExistingAsync, CANONICAL_READ_ONLY_REASON, OWNERSHIP_UNAVAILABLE_REASON, CONVERSATION_ROUND_THRESHOLD, emptyLineageReport, stateDbSessionReadAsync, stateDbSessionSourcesAsync, stateDbTimestampSeconds, withStateDbListingReads } from './state-db.js';
17
18
  import { completionIncomplete, fallbackHandoffSummary, HANDOFF_SYSTEM_PROMPT, handoffMarker, handoffPayload, handoffTranscript, messageHandoffPayload, sameHandoff } from './handoff.js';
18
19
  import { anchorMessageKey, anchorSummary, CompressionJobs, compressionReference, visibleMessagesForAnchor } from './compress.js';
19
20
  import { chatReadiness, runtimeFailureDetails, SidecarError } from '../sidecar/client.js';
20
- import { agentSteerText, ApiMessageSummary, appendStateDbIdentityRows, attachedFilesPrompt, attachmentObjects, checkpointTurnStart, dedupeContext, isContextCompressionMarker, journalOutputRows, looksLikeCurrentUserTurn, stoppedTurnContext, mergeSessionMessagesAppendOnly, messageIdentity, pendingUserRow, reasoningFieldsText, sanitizeMessagesForApi, stateDbSeenId, stripWorkspacePrefix, withAttachmentObjects, newestSettledReply, withBodyExcerpts, withDisplayMedia, withMarkerKinds, withMirrorSources, withSceneRowMedia, withPendingUserTurn, withToolCallOutcomes, withoutRunningTurnOutput } from './merge.js';
21
+ import { agentSteerText, ApiMessageSummary, appendStateDbIdentityRows, attachedFilesPrompt, attachmentObjects, checkpointTurnStart, dedupeContext, isContextCompressionMarker, journalOutputRows, looksLikeCurrentUserTurn, stoppedTurnContext, mergeSessionMessagesAppendOnly, messageIdentity, pendingUserRow, reasoningFieldsText, sanitizeMessagesForApi, withDataUseRequirements, stateDbSeenId, stripWorkspacePrefix, withAttachmentObjects, newestSettledReply, withBodyExcerpts, withDisplayMedia, withMarkerKinds, withMirrorSources, withSceneRowMedia, withPendingUserTurn, withToolCallOutcomes, withoutRunningTurnOutput } from './merge.js';
21
22
  import { withBackgroundUpdates } from './background-updates.js';
22
23
  import { markedStateDbGeneration, stateDbIdentityKeys, stampStateDbGeneration, stateDbMessageRepresented } from './state-db-identity.js';
23
24
  import { sourceKind } from './source-kind.js';
@@ -42,11 +43,16 @@ export class HttpFailure extends Error {
42
43
  }
43
44
  /**
44
45
  * Python `_agent_runtime_barrier_response`: a stale local Agent checkout is refused with a typed 409 before the caller
45
- * mutates anything. Any other failure of the check is not a verdict and lets the caller proceed.
46
+ * mutates anything. An unavailable connection is refused before dispatch; other check failures are not a verdict
47
+ * and let the caller proceed.
46
48
  */
47
49
  export async function ensureAgentRuntimeCurrent(sidecar) {
48
50
  if (!sidecar)
49
51
  return;
52
+ // Runtime RPCs bypass the client's ready gate and can queue behind an unfinished handshake.
53
+ if (chatReadiness(sidecar).condition === 'sidecar_unavailable') {
54
+ throw new HttpFailure(503, 'The Agent sidecar is not ready; retry once it finishes starting.', { condition: 'sidecar_unavailable' });
55
+ }
50
56
  try {
51
57
  await sidecar.call('runtime.ensure_current', {});
52
58
  }
@@ -57,6 +63,8 @@ export async function ensureAgentRuntimeCurrent(sidecar) {
57
63
  }
58
64
  /** The health probe is also the browser's server-reachability probe (30 s), so the readiness check answers well inside it. */
59
65
  const CHAT_READINESS_CHECK_TIMEOUT_MS = 5_000;
66
+ /** TAL-1254: how soon a worktree lock the sidecar could not confirm is tried again. */
67
+ export const WORKTREE_RETAIN_RETRY_MS = 60_000;
60
68
  /**
61
69
  * TAL-951: whether a chat turn would be admitted now. Ready and source-stale incompatible sidecars both run turn
62
70
  * admission's live check: recovery state can change after the handshake. A check
@@ -92,6 +100,8 @@ export class SessionService {
92
100
  compressionJobs;
93
101
  /** TAL-1005: the turns now waiting on their session's compression job before they read its history. */
94
102
  compactionWaiters = new Set();
103
+ worktreeRetainRetry = null;
104
+ worktreesUnconfirmed = 0;
95
105
  constructor(deps) {
96
106
  this.deps = deps;
97
107
  this.compressionJobs = new CompressionJobs();
@@ -121,7 +131,7 @@ export class SessionService {
121
131
  throw new HttpFailure(400, error.message);
122
132
  }
123
133
  }
124
- /** A `@provider:` id is split; any other model an explicit pick did not just choose is checked against the catalog (TAL-542). */
134
+ /** Split qualified ids and opportunistically repair against fresh evidence; missing discovery never delays a turn. */
125
135
  turnModel(s, body = {}) {
126
136
  const requestedModel = str(body.model) || s.model;
127
137
  const requestedProvider = 'model_provider' in body ? body.model_provider : s.model_provider;
@@ -190,87 +200,104 @@ export class SessionService {
190
200
  return this.visibleToActiveProfile(session.profile);
191
201
  }
192
202
  localTargets = null;
193
- /** One receipt-backed physical target for every operation on a local conversation alias. */
194
- resolveSessionId(sid) {
203
+ /** One receipt-backed physical target for every operation on a local conversation alias, read on the state.db worker. */
204
+ async resolveSessionId(sid) {
195
205
  try {
196
206
  const path = this.stateDbPath(null);
197
- const key = JSON.stringify([statSignature(path), statSignature(`${path}-wal`), path]);
207
+ const stamp = () => JSON.stringify([statSignature(path), statSignature(`${path}-wal`), path]);
208
+ const key = stamp();
198
209
  if (this.localTargets?.key !== key)
199
210
  this.localTargets = { key, ids: new Map() };
200
211
  const cached = this.localTargets.ids.get(sid);
201
212
  if (cached)
202
213
  return cached;
203
- return stateDbLocalTarget(path, sid, { onRead: (target) => {
204
- // A failed or changing read cannot seed a routing cache.
205
- if (key !== JSON.stringify([statSignature(path), statSignature(`${path}-wal`), path]))
206
- return;
207
- if (this.localTargets.ids.size >= 500)
208
- this.localTargets.ids.clear();
209
- this.localTargets.ids.set(sid, target);
210
- } });
214
+ const { target, read } = await stateDbSessionReadAsync({ target: sid, read: false }, 'stateDbLocalTargetRead', path, sid);
215
+ // A failed or changing read cannot seed a routing cache.
216
+ const targets = this.localTargets;
217
+ if (read && targets.key === key && stamp() === key) {
218
+ if (targets.ids.size >= 500)
219
+ targets.ids.clear();
220
+ targets.ids.set(sid, target);
221
+ }
222
+ return target;
211
223
  }
212
224
  catch {
213
225
  return sid;
214
226
  }
215
227
  }
216
228
  /** Python `_lookup_cli_session_metadata(...).get('read_only')` plus the persisted sidecar flag and a not-claimable foreign owner. */
217
- async isReadOnlyImport(sid) {
229
+ async isReadOnlyImport(sid, reads = this.sessionGateReads(sid)) {
218
230
  try {
219
231
  const s = (await this.store.get(sid, { metadataOnly: true }));
220
- if ((await this.isReadOnly(s)))
232
+ if (await this.loadedReadOnlyReason(s, undefined, reads))
221
233
  return true;
222
234
  }
223
235
  catch {
224
236
  // No sidecar: a foreign state.db transcript whose owner refuses claiming is read-only too.
225
- const { session, reason } = (await this.claimOrSynthesizeCliSession(sid));
237
+ const { session, reason } = (await this.claimOrSynthesizeCliSession(sid, undefined, reads));
226
238
  if (session && reason === 'not_claimable')
227
239
  return true;
228
240
  }
229
- const meta = (await this.lookupCliMeta(sid));
241
+ const meta = await reads.meta();
230
242
  return meta !== null && Boolean(meta.read_only);
231
243
  }
232
244
  /** Python `_session_is_subagent_view_only`: a delegated child by any signal — the persisted sidecar or the state.db row. */
233
- async isSubagentViewOnly(sid) {
245
+ async isSubagentViewOnly(sid, reads = this.sessionGateReads(sid)) {
246
+ let profile = this.deps.activeProfile();
234
247
  try {
235
248
  const s = (await this.store.get(sid, { metadataOnly: true, cacheOnMiss: false, promote: false }));
249
+ profile = s.profile || 'default';
236
250
  if (str(s.source_tag || s.raw_source || s.session_source).trim().toLowerCase() === 'subagent')
237
251
  return true;
238
252
  }
239
253
  catch { /* no sidecar */ }
240
- const meta = (await this.lookupCliMeta(sid));
241
- return meta !== null && str(meta.source_tag || meta.raw_source || meta.source).trim().toLowerCase() === 'subagent';
242
- }
243
- /** TAL-358: batched `sessions.source` owners from the active profile's state.db. */
244
- stateDbSources = (ids, profile = this.deps.activeProfile()) => {
254
+ // Admission needs authority, not the sidebar's visibility, message counts or enrichment.
255
+ const owners = await reads.owners(profile);
256
+ if (!owners)
257
+ throw new HttpFailure(403, OWNERSHIP_UNAVAILABLE_REASON);
258
+ const owner = owners.get(sid);
259
+ return str(typeof owner === 'object' ? owner.source : owner).trim().toLowerCase() === 'subagent';
260
+ }
261
+ /** TAL-358: batched `sessions.source` owners from the active profile's state.db, read on the state.db worker (TAL-923). */
262
+ stateDbSources = async (ids, profile = this.deps.activeProfile()) => {
245
263
  try {
246
- return stateDbSessionSources(this.stateDbPath(profile), ids);
264
+ return await stateDbSessionSourcesAsync(this.stateDbPath(profile), ids);
247
265
  }
248
266
  catch {
249
267
  return null;
250
268
  }
251
269
  };
270
+ /** The owners of `byProfile`'s ids, read up front, as the synchronous lookup `withOwnerLocks` takes. */
271
+ async ownerSources(byProfile, read = this.stateDbSources) {
272
+ const owners = new Map(await Promise.all([...byProfile].map(async ([profile, ids]) => [profile, ids.size ? await read([...ids], profile) : new Map()])));
273
+ return (_ids, profile = this.deps.activeProfile()) => owners.get(profile) ?? null;
274
+ }
275
+ /** `withOwnerLocks` with the owners read on the state.db worker, so a gate or payload never opens state.db on this thread. */
276
+ async ownerLocked(rows, read) {
277
+ return withOwnerLocks(rows, await this.ownerSources(ownerLockCandidates(rows), read));
278
+ }
252
279
  /**
253
280
  * TAL-590 (Python #3238): the read-only CLI or API-server imports among `rows` whose active-profile state.db row is
254
281
  * gone, so their source was deleted outside Web and no Web action could remove them. An unreadable state.db proves
255
282
  * nothing (`agentSessionRowsExisting`); another profile's session or one with a run the runtime still holds is never
256
283
  * an orphan, while a stream id a crash left behind does not count as one.
257
284
  */
258
- async orphanedImports(rows, existing, owners) {
285
+ async orphanedImports(rows, existing, owners, reads) {
259
286
  const candidates = (await this.orphanCandidates(rows));
260
287
  if (!candidates.length)
261
288
  return new Set();
262
- existing ??= agentSessionRowsExisting(this.stateDbPath(null), candidates);
289
+ existing ??= await (reads ? reads.existing() : agentSessionRowsExistingAsync(this.stateDbPath(null), candidates));
263
290
  if (owners === undefined)
264
- owners = this.stateDbSources(candidates);
291
+ owners = await (reads ? reads.owners() : this.stateDbSources(candidates));
265
292
  return new Set(candidates.filter((sid) => !existing.has(sid) && owners !== null && typeof owners.get(sid) !== 'object'));
266
293
  }
267
294
  async orphanCandidates(rows) {
268
295
  return (await filterAsync(rows, async (r) => r.read_only && ['cli', 'api'].includes(sourceKind(r)) && this.visibleToActiveProfile(str(r.profile) || null) && !(await this.activeRunBlocking(str(r.session_id)))))
269
296
  .map((r) => str(r.session_id));
270
297
  }
271
- async isOrphanedImport(sid) {
298
+ async isOrphanedImport(sid, reads = this.sessionGateReads(sid)) {
272
299
  try {
273
- return (await this.orphanedImports([(await this.store.get(sid, { metadataOnly: true })).compact()])).size > 0;
300
+ return (await this.orphanedImports([(await this.store.get(sid, { metadataOnly: true })).compact()], undefined, undefined, reads)).size > 0;
274
301
  }
275
302
  catch {
276
303
  return false;
@@ -288,14 +315,14 @@ export class SessionService {
288
315
  for (const id of cliRowIds)
289
316
  activeIds.add(id);
290
317
  byProfile.set(activeProfile, activeIds);
291
- const owners = new Map(await Promise.all([...byProfile].map(async ([profile, ids]) => [profile, ids.size ? await stateDbSessionSourcesAsync(this.stateDbPath(profile), ids) : new Map()])));
318
+ const owners = await this.ownerSources(byProfile);
292
319
  const checked = new Set((await this.orphanCandidates(rows)));
293
320
  const existing = checked.size ? await agentSessionRowsExistingAsync(dbPath, checked) : new Set();
294
321
  return {
295
- stateDbSources: (_ids, profile = activeProfile) => owners.get(profile) ?? null,
322
+ stateDbSources: owners,
296
323
  pruneOrphanedImports: async (current) => {
297
324
  // Only an id the worker checked can be an orphan; a row that became a candidate meanwhile waits for the next list.
298
- const orphans = (await this.orphanedImports(current.filter((r) => checked.has(str(r.session_id))), existing, owners.get(activeProfile) ?? null));
325
+ const orphans = (await this.orphanedImports(current.filter((r) => checked.has(str(r.session_id))), existing, owners([], activeProfile)));
299
326
  for (const sid of orphans)
300
327
  (await this.store.delete(sid));
301
328
  return orphans;
@@ -303,7 +330,7 @@ export class SessionService {
303
330
  };
304
331
  }
305
332
  /** The persisted flag folded with the state.db owner lock (TAL-358): the one read-only rule for mutation gates and the wire. */
306
- async readOnlyReason(s, profile) {
333
+ async readOnlyReason(s, profile, reads) {
307
334
  if (typeof s === 'string') {
308
335
  try {
309
336
  s = await this.store.get(s, { metadataOnly: true, cacheOnMiss: false, promote: false });
@@ -313,22 +340,25 @@ export class SessionService {
313
340
  return OWNERSHIP_UNAVAILABLE_REASON;
314
341
  }
315
342
  }
316
- return this.loadedReadOnlyReason(s, profile);
343
+ return await this.loadedReadOnlyReason(s, profile, reads);
317
344
  }
318
- loadedReadOnlyReason(s, profile) {
345
+ async loadedReadOnlyReason(s, profile, reads) {
319
346
  let raw = typeof s === 'string' ? { session_id: s, profile: this.deps.activeProfile() }
320
347
  : { session_id: s.session_id, profile: s.profile, source_tag: s.source_tag, raw_source: s.raw_source, session_source: s.session_source, read_only: s.read_only };
321
348
  if (profile !== undefined)
322
349
  raw = this.deps.profilesMatch(str(raw.profile) || null, profile) ? { ...raw, profile } : { session_id: raw.session_id, profile };
323
- const row = withOwnerLocks([raw], this.stateDbSources)[0];
350
+ const row = (await this.ownerLocked([raw], reads ? (_ids, ownerProfile) => reads.owners(ownerProfile) : undefined))[0];
324
351
  return row.read_only ? str(row.read_only_reason) || 'Read-only imported sessions cannot be modified from WebUI' : null;
325
352
  }
326
- assertWritable(session, profile) {
327
- const check = (reason) => { if (reason)
328
- throw new HttpFailure(403, reason); };
329
- if (typeof session === 'string')
330
- return this.readOnlyReason(session, profile).then(check);
331
- check(this.loadedReadOnlyReason(session, profile));
353
+ /**
354
+ * The final owner check before a protected sidecar dispatch or write: callers dispatch as soon as it resolves. The
355
+ * owner is the Agent's, which can change at any moment, so reading it on the state.db worker (TAL-923) widens no
356
+ * window Web could close; the session lock, where one is held, still serializes Web's own writes.
357
+ */
358
+ async assertWritable(session, profile, reads) {
359
+ const reason = await this.readOnlyReason(session, profile, reads);
360
+ if (reason)
361
+ throw new HttpFailure(403, reason);
332
362
  }
333
363
  /** Controls can exist without a local session file; use the same id the sidecar will address. */
334
364
  async idForMutation(sid) {
@@ -350,16 +380,16 @@ export class SessionService {
350
380
  }, opts);
351
381
  }
352
382
  /** Full session for mutation, refusing read-only imports (Python `_get_or_materialize_session`). */
353
- async getForMutation(sid) {
354
- let s = (await this.get404(sid));
383
+ async getForMutation(sid, reads) {
384
+ let s = (await this.get404(sid, {}, reads));
355
385
  s = (await this.store.ensureFull(sid, s));
356
- this.assertWritable(s);
386
+ await this.assertWritable(s, undefined, reads?.sid === s.session_id ? reads : undefined);
357
387
  if (str(s.source_tag || s.raw_source).trim().toLowerCase() === 'subagent')
358
388
  throw new HttpFailure(403, 'Read-only subagent child session');
359
389
  return s;
360
390
  }
361
- async get404(sid, opts = {}) {
362
- sid = this.resolveSessionId(sid);
391
+ async get404(sid, opts = {}, reads) {
392
+ sid = await this.resolveSessionId(sid);
363
393
  try {
364
394
  return (await this.store.get(sid, opts));
365
395
  }
@@ -367,7 +397,7 @@ export class SessionService {
367
397
  if (error instanceof SessionNotFound) {
368
398
  // Python `_get_or_materialize_session`: a claimable foreign (CLI/TUI/Desktop) session gains a WebUI sidecar
369
399
  // on its first mutation; a read-only foreign source answers 403 from `getForMutation`, a missing one 404.
370
- const synth = (await this.claimOrSynthesizeCliSession(sid));
400
+ const synth = (await this.claimOrSynthesizeCliSession(sid, undefined, reads));
371
401
  if (synth.session && synth.reason === 'materialized') {
372
402
  await this.store.save(synth.session);
373
403
  await this.store.touch(synth.session);
@@ -382,18 +412,33 @@ export class SessionService {
382
412
  }
383
413
  }
384
414
  /** The Agent's state.db rows for this session (empty for subagent views, which never merge). */
385
- stateDbRows(s) {
386
- return this.stateDbRead(s).rows;
415
+ async stateDbRows(s) {
416
+ return (await this.stateDbRead(s)).rows;
387
417
  }
388
- /** `stateDbRows` and whether the read succeeded on a state.db whose messages carry ids (TAL-493). */
418
+ /**
419
+ * `stateDbRows` and whether the read succeeded on a state.db whose messages carry ids (TAL-493). TAL-1162: read on the
420
+ * state.db worker; a worker failure is a failed read.
421
+ */
389
422
  stateDbRead(s) {
390
- if (str(s.source_tag || s.raw_source || s.session_source).trim().toLowerCase() === 'subagent')
391
- return { rows: [], idCapable: false, ok: false, reason: null, generation: null };
392
- // TAL-529: after a compression rotation the Agent writes to the continuation, so the read follows the recorded lineage.
393
- const lineage = s.state_db_lineage?.[0] === s.session_id ? s.state_db_lineage : null;
394
- return stateDbSessionRead(this.stateDbPath(s.profile), s.session_id, { stitch: false, lineage });
423
+ if (isSubagentView(s))
424
+ return Promise.resolve(failedStateDbRead());
425
+ return stateDbSessionReadAsync(failedStateDbRead(), 'stateDbSessionRead', this.stateDbPath(s.profile), s.session_id, { stitch: false, lineage: stateDbReadLineage(s) });
426
+ }
427
+ /** `stateDbRead`'s projection of the rows past `afterId` alone (the detail's page tail), on the state.db worker. */
428
+ stateDbTailRead(s, afterId, unidentified) {
429
+ return stateDbSessionReadAsync(failedStateDbRead(), 'stateDbSessionTailRead', this.stateDbPath(s.profile), s.session_id, afterId, { lineage: stateDbReadLineage(s), unidentified });
430
+ }
431
+ /** The one read a turn's settlement builds its context from and settles with (`stateDbSettlementRead`). */
432
+ settlementRead(s) {
433
+ const unchanged = { ...failedStateDbRead(), lineage: s.state_db_lineage ?? [s.session_id], rotated: null };
434
+ if (isSubagentView(s))
435
+ return Promise.resolve(unchanged);
436
+ return stateDbSessionReadAsync(unchanged, 'stateDbSettlementRead', this.stateDbPath(s.profile), s.session_id, s.state_db_lineage);
395
437
  }
396
- /** Preserve existing transcript provenance before a turn adds Agent rows from its starting database read. */
438
+ /**
439
+ * Preserve existing transcript provenance before a turn adds Agent rows from its starting database read. The stamp
440
+ * lands before this returns; the read follows.
441
+ */
397
442
  beginStateDbTurn(s) {
398
443
  stampStateDbGeneration([...s.messages, ...s.context_messages], markedStateDbGeneration(s));
399
444
  return this.stateDbRead(s);
@@ -406,7 +451,7 @@ export class SessionService {
406
451
  * rows (a WebUI conversation continued from the CLI shows the CLI turns). This is the coordinate space `GET
407
452
  * /api/session` exposes, so branching and the next model history slice/extend the same list.
408
453
  */
409
- mergedTranscript(s, local = s.messages, stateRows = this.stateDbRows(s)) {
454
+ mergedTranscript(s, local, stateRows) {
410
455
  if (!stateRows.length)
411
456
  return local;
412
457
  return mergeSessionMessagesAppendOnly(local, stateRows, { truncationWatermark: s.truncation_watermark, compressedWatermark: s.truncation_watermark_compressed, stateDbSeenId: currentStateDbSeenId(s, stateRows), stateDbGeneration: markedStateDbGeneration(s) });
@@ -416,7 +461,7 @@ export class SessionService {
416
461
  * so the merge never replays a covered row and appends every row committed after the read. Call it after the boundary
417
462
  * fields are set: the marker holds only while they and the row it names stay as recorded.
418
463
  */
419
- markStateDbSeen(s, read = this.stateDbRead(s), opts = {}) {
464
+ markStateDbSeen(s, read, opts = {}) {
420
465
  // Freeze numeric provenance before replacing the marker, including after a database replacement.
421
466
  const priorGeneration = markedStateDbGeneration(s);
422
467
  if (priorGeneration !== null)
@@ -445,7 +490,7 @@ export class SessionService {
445
490
  * turn started from but the transcript lacks (a CLI continuation read into its context) are kept before the turn.
446
491
  */
447
492
  settleStateDb(s, turn) {
448
- const read = turn.read ?? this.stateDbRead(s);
493
+ const read = turn.read;
449
494
  const agentAdded = withoutRows(turn.agentRows ?? [], turn.previousContext);
450
495
  const contextAdded = withoutRows(s.context_messages, turn.previousContext);
451
496
  if (typeof turn.startGeneration === 'string') {
@@ -511,10 +556,10 @@ export class SessionService {
511
556
  // The read follows the new lineage from here, and everything it holds now is covered.
512
557
  // ponytail: a CLI row committed to the continuation before this settlement is covered with them; telling them apart
513
558
  // needs the Agent to mark the rows it writes at rotation.
514
- const lineage = stateDbCompressionLineage(this.stateDbPath(s.profile), s.session_id);
559
+ const lineage = read.lineage;
515
560
  if (JSON.stringify(lineage) !== JSON.stringify(s.state_db_lineage ?? [s.session_id])) {
516
561
  s.state_db_lineage = lineage.length > 1 ? lineage : null;
517
- this.markStateDbSeen(s);
562
+ this.markStateDbSeen(s, read.rotated ?? failedStateDbRead());
518
563
  }
519
564
  }
520
565
  /**
@@ -548,35 +593,70 @@ export class SessionService {
548
593
  */
549
594
  modelContext(s, stateRows) {
550
595
  const local = s.context_messages.length ? s.context_messages : s.messages.filter((m) => !m._error && !m._partial);
551
- return local.some((m) => isContextCompressionMarker(m)) ? local : this.mergedTranscript(s, local, stateRows ?? this.stateDbRows(s));
596
+ return local.some((m) => isContextCompressionMarker(m)) ? local : this.mergedTranscript(s, local, stateRows);
597
+ }
598
+ /** `modelContext` over a state.db read taken now. */
599
+ async readModelContext(s) {
600
+ return this.modelContext(s, await this.stateDbRows(s));
552
601
  }
553
602
  /**
554
603
  * TAL-518: the model context a `/btw` clone inherits: the chat's history plus its running turn's prompt, which
555
604
  * deferred save keeps out of `messages` and `context_messages` until settlement.
556
605
  */
557
- sideQuestionContext(s) {
558
- const context = structuredClone(this.modelContext(s));
606
+ async sideQuestionContext(s) {
607
+ const context = structuredClone(await this.readModelContext(s));
559
608
  const pending = this.pendingTurn(s);
560
609
  return pending ? withPendingUserTurn(context, pending) : context;
561
610
  }
562
611
  /** Python `_is_messaging_session_id`: from the WebUI metadata or the Agent's row. */
563
- async isMessagingSession(sid) {
612
+ async isMessagingSession(sid, reads = this.sessionGateReads(sid)) {
564
613
  try {
565
614
  if (isMessagingSessionRecord((await this.store.get(sid, { metadataOnly: true })).compact()))
566
615
  return true;
567
616
  }
568
617
  catch { /* absent */ }
569
- const meta = (await this.lookupCliMeta(sid));
618
+ const meta = await reads.meta();
570
619
  return meta !== null && isMessagingSessionRecord(meta);
571
620
  }
572
- /** Python `_lookup_cli_session_metadata`: the sidebar row for a state.db session in the active profile. */
621
+ sessionGateReads(sid) {
622
+ const profile = this.deps.activeProfile();
623
+ const dbPath = this.stateDbPath(profile);
624
+ const owners = new Map();
625
+ let row;
626
+ let meta;
627
+ let transcript;
628
+ let context;
629
+ let existing;
630
+ return {
631
+ sid,
632
+ owners: (ownerProfile = profile) => {
633
+ let pending = owners.get(ownerProfile);
634
+ if (!pending) {
635
+ pending = this.stateDbSources([sid], ownerProfile);
636
+ owners.set(ownerProfile, pending);
637
+ }
638
+ return pending;
639
+ },
640
+ row: () => row ??= stateDbSessionReadAsync(null, 'stateDbSessionRow', dbPath, sid),
641
+ meta: () => meta ??= this.lookupCliMeta(sid),
642
+ messages: (stitch) => stitch
643
+ ? transcript ??= isClaudeCodeSessionId(sid) ? Promise.resolve(this.deps.claudeCode?.messages(sid) ?? []) : stateDbSessionReadAsync([], 'stateDbSessionMessages', dbPath, sid, { stitch: true })
644
+ : context ??= stateDbSessionReadAsync([], 'stateDbSessionMessages', dbPath, sid, { stitch: false }),
645
+ existing: () => existing ??= agentSessionRowsExistingAsync(dbPath, [sid]),
646
+ };
647
+ }
648
+ /**
649
+ * Python `_lookup_cli_session_metadata`: the sidebar row for a state.db session in the active profile, else its plain
650
+ * row (a segment its chain's row does not name). Both are read by id: the sidebar listing would scan every session to
651
+ * rank its window (TAL-923).
652
+ */
573
653
  async lookupCliMeta(sid) {
574
654
  try {
575
655
  if (isClaudeCodeSessionId(sid))
576
656
  return (await this.claudeCodeRows()).find((r) => str(r.session_id) === sid) ?? null;
577
657
  const profile = this.deps.activeProfile();
578
- return (await this.deps.cliSessionsSync(profile, { sourceFilter: null })).rows.find((r) => str(r.session_id) === sid)
579
- ?? (await this.deps.cliSessionsSync(profile, { sourceFilter: null, sessionIds: [sid] })).rows[0] ?? null;
658
+ return (await this.deps.cliSessions(profile, { sourceFilter: null, sessionIds: [sid], sidebarRows: true })).rows.find((r) => str(r.session_id) === sid)
659
+ ?? (await this.deps.cliSessions(profile, { sourceFilter: null, sessionIds: [sid] })).rows[0] ?? null;
580
660
  }
581
661
  catch {
582
662
  return null;
@@ -611,22 +691,23 @@ export class SessionService {
611
691
  * a WebUI sidecar. `materialized` sessions are writeable (the caller persists the sidecar), `not_claimable` ones are
612
692
  * read-only stubs, `was_webui`/`no_foreign_state`/`invalid_sid` answer 404.
613
693
  */
614
- async claimOrSynthesizeCliSession(sid, metaIn) {
694
+ async claimOrSynthesizeCliSession(sid, metaIn, reads) {
615
695
  if (!isSafeSessionId(sid))
616
696
  return { session: null, reason: 'invalid_sid' };
617
697
  const profile = this.deps.activeProfile();
618
- const dbPath = join(this.deps.profileHome(profile), 'state.db');
619
- sid = this.resolveSessionId(sid);
620
- const row = stateDbSessionRow(dbPath, sid);
698
+ sid = await this.resolveSessionId(sid);
699
+ if (reads?.sid !== sid)
700
+ reads = this.sessionGateReads(sid);
701
+ const [row, owners] = await Promise.all([reads.row(), reads.owners()]);
621
702
  const stateDbSource = str(row?.source).trim().toLowerCase();
622
703
  const subagentChild = stateDbSource === 'subagent';
623
- const owners = this.stateDbSources([sid]);
624
704
  const canonical = typeof owners?.get(sid) === 'object';
625
705
  if (((await this.indexMarksWasWebui(sid)) || ((await this.store.wasDeleted(sid)) && ['', 'webui', 'fork'].includes(stateDbSource))) && !subagentChild && !canonical)
626
706
  return { session: null, reason: 'was_webui' };
627
- const msgs = isClaudeCodeSessionId(sid) ? this.deps.claudeCode?.messages(sid) ?? [] : stateDbSessionMessages(dbPath, sid, { stitch: true });
628
- const meta = { ...(metaIn ?? (await this.lookupCliMeta(sid)) ?? {}) };
629
- if (!msgs.length && !(meta._lineage_root_id && meta._lineage_tip_id))
707
+ const msgs = await reads.messages(true);
708
+ const meta = { ...(metaIn ?? (await reads.meta()) ?? {}) };
709
+ // An owner receipt proves a session before its first turn (a fresh diagnostic chat, TAL-1040); a bare row does not.
710
+ if (!msgs.length && !(meta._lineage_root_id && meta._lineage_tip_id) && !canonical)
630
711
  return { session: null, reason: 'no_foreign_state' };
631
712
  if (row) {
632
713
  if (!meta.source_tag && stateDbSource)
@@ -648,7 +729,7 @@ export class SessionService {
648
729
  const claimable = owners !== null && isClaimableCliSource(meta, owners.get(sid) || stateDbSource);
649
730
  const workspace = str(meta.workspace || meta.cwd).trim() || (await this.deps.workspaces.lastWorkspace(profile));
650
731
  const defaults = (await this.store.deps.defaults(profile));
651
- const context = Array.isArray(meta._lineage_ids) ? stateDbSessionMessages(dbPath, sid, { stitch: false }) : null;
732
+ const context = Array.isArray(meta._lineage_ids) ? await reads.messages(false) : null;
652
733
  const session = new Session({
653
734
  session_id: sid, title: str(meta.title) || 'CLI Session', workspace, model: str(meta.model) || 'unknown', model_provider: str(meta.model_provider) || null,
654
735
  messages: msgs, created_at: Number(meta.created_at) || 0, updated_at: Number(meta.updated_at) || 0, profile: str(meta.profile) || null,
@@ -663,9 +744,9 @@ export class SessionService {
663
744
  }, defaults);
664
745
  return { session, reason: claimable ? 'materialized' : 'not_claimable' };
665
746
  }
666
- async mutationTarget(sid, verb) {
747
+ async mutationTarget(sid, verb, reads) {
667
748
  try {
668
- return (await this.getForMutation(sid));
749
+ return (await this.getForMutation(sid, reads));
669
750
  }
670
751
  catch (error) {
671
752
  if (error instanceof SessionNotFound)
@@ -683,19 +764,21 @@ export class SessionService {
683
764
  if (isClaudeCodeSessionId(sid))
684
765
  throw new HttpFailure(400, `Claude Code sessions are view-only and cannot be ${verb} from WebUI`);
685
766
  }
686
- async rejectViewOnly(sid, verb) {
767
+ async rejectViewOnly(sid, verb, reads) {
687
768
  sid = sid.trim();
769
+ if (reads?.sid !== sid)
770
+ reads = this.sessionGateReads(sid);
688
771
  this.rejectClaudeCode(sid, verb);
689
772
  let profile = this.deps.activeProfile();
690
773
  try {
691
774
  profile = (await this.store.get(sid, { metadataOnly: true, cacheOnMiss: false, promote: false })).profile || 'default';
692
775
  }
693
776
  catch { /* foreign row */ }
694
- const owners = this.stateDbSources([sid], profile);
777
+ const owners = await reads.owners(profile);
695
778
  const owner = owners?.get(sid);
696
779
  if (!owners || typeof owner === 'object')
697
780
  throw new HttpFailure(403, typeof owner === 'object' ? owner.reason ?? CANONICAL_READ_ONLY_REASON : OWNERSHIP_UNAVAILABLE_REASON);
698
- if ((await this.isSubagentViewOnly(sid)))
781
+ if ((await this.isSubagentViewOnly(sid, reads)))
699
782
  throw new HttpFailure(400, `Subagent sessions are view-only and cannot be ${verb} from WebUI`);
700
783
  return sid;
701
784
  }
@@ -717,9 +800,13 @@ export class SessionService {
717
800
  }
718
801
  return redactSessionData(payload, this.deps.redactEnabled());
719
802
  }
803
+ /** Opaque read identity, including context changes that do not alter display message counts. */
804
+ contextDetailKey(s) {
805
+ return createHash('sha256').update(JSON.stringify([s.session_id, s.profile, s.model, s.model_provider, s.workspace, s.updated_at, s.messages.length, s.context_messages, s.post_compression_context_tokens_estimate, s.active_stream_id])).digest('hex');
806
+ }
720
807
  /** `compact()` with the wire streaming/read-only flags (TAL-312), for replies that return the session row. */
721
808
  async wireRow(s) {
722
- return withSessionWireFlags(withOwnerLocks([{ ...s.compact({ contextLengthFor: this.deps.contextLengthFor, modelOptionFor: this.deps.modelOptionFor }), assistant_name: this.assistantName(s), workspace_name: (await this.workspaceNames([s]))(s) }], this.stateDbSources)[0], this.deps.runtime.activeStreamIds);
809
+ return withSessionWireFlags((await this.ownerLocked([{ ...s.compact({ contextLengthFor: this.deps.contextLengthFor, modelOptionFor: this.deps.modelOptionFor }), context_detail_key: this.contextDetailKey(s), assistant_name: this.assistantName(s), workspace_name: (await this.workspaceNames([s]))(s) }]))[0], this.deps.runtime.activeStreamIds);
723
810
  }
724
811
  /**
725
812
  * TAL-303: the label for a session's workspace, from its own profile's registry (a profileless row is the root's).
@@ -765,7 +852,7 @@ export class SessionService {
765
852
  async deriveRange(s, rows, baseIndex, initialTurn, journaled, refs, calls, reviewRows) {
766
853
  return (await this.backgroundLinked(s, hydrateAnchorActivityScenes(withToolCallOutcomes((await this.backgroundUpdates(s, withMarkerKinds(withTurnIds(withAttachmentObjects(rows), baseIndex, initialTurn), s), reviewRows)), calls, s.active_stream_id, baseIndex), s.anchor_activity_scenes, { activeTurnId: s.active_stream_id, runningScene: !journaled, clipToolResults: true, baseIndex, refCounts: refs })));
767
854
  }
768
- async limitedDetail(s, index, limit, before, pending, journaled) {
855
+ async limitedDetail(s, index, limit, before, pending, journaled, retried = false) {
769
856
  let prefixEnd = s.messages.length;
770
857
  const turnId = pending?.turnId ?? journaled?.turnId;
771
858
  if (turnId)
@@ -797,8 +884,18 @@ export class SessionService {
797
884
  let effectiveSeenId = null;
798
885
  let cacheable = true;
799
886
  let appended = null;
800
- if (typeof s.state_db_seen_id === 'number' && str(s.source_tag || s.raw_source || s.session_source).trim().toLowerCase() !== 'subagent') {
801
- const read = stateDbSessionTailRead(dbPath, s.session_id, s.state_db_seen_id, { lineage: s.state_db_lineage?.[0] === s.session_id ? s.state_db_lineage : null, unidentified: [...index.unidentified, ...context.unidentified] });
887
+ // TAL-1162: the transcript reads run on the state.db worker. A session that changed meanwhile no longer matches this
888
+ // index or the lifecycle snapshots: null asks the caller to take them again, and a second change is answered as
889
+ // read, uncached.
890
+ const [localRows, localCount, updatedAt, seenId] = [s.messages, s.messages.length, s.updated_at, s.state_db_seen_id];
891
+ const changed = () => s.messages !== localRows || s.messages.length !== localCount || s.updated_at !== updatedAt || s.state_db_seen_id !== seenId;
892
+ if (typeof seenId === 'number' && !isSubagentView(s)) {
893
+ const read = await this.stateDbTailRead(s, seenId, [...index.unidentified, ...context.unidentified]);
894
+ if (changed()) {
895
+ if (!retried)
896
+ return null;
897
+ cacheable = false;
898
+ }
802
899
  if (!read.ok)
803
900
  cacheable = false;
804
901
  const markerRows = read.anchor ? [read.anchor, ...read.rows] : read.rows;
@@ -808,14 +905,19 @@ export class SessionService {
808
905
  if (!ambiguous) {
809
906
  const includeUids = read.rows.some((m) => stateDbIdentityKeys(m).some((id) => id.startsWith('uid:')));
810
907
  const mode = includeUids ? 1 : 0;
811
- appended = appendStateDbIdentityRows(read.rows, s.state_db_seen_id, index.identities[mode], index.unidentified[mode], index.generation, includeUids);
908
+ appended = appendStateDbIdentityRows(read.rows, seenId, index.identities[mode], index.unidentified[mode], index.generation, includeUids);
812
909
  stateRows = read.rows;
813
- effectiveSeenId = s.state_db_seen_id;
910
+ effectiveSeenId = seenId;
814
911
  }
815
912
  }
816
913
  }
817
914
  if (appended === null) {
818
- stateRows = this.stateDbRows(s);
915
+ stateRows = await this.stateDbRows(s);
916
+ if (changed()) {
917
+ if (!retried)
918
+ return null;
919
+ cacheable = false;
920
+ }
819
921
  effectiveSeenId = currentStateDbSeenId(s, stateRows);
820
922
  appended = this.mergedTranscript(s, s.messages, stateRows).slice(s.messages.length);
821
923
  }
@@ -827,12 +929,12 @@ export class SessionService {
827
929
  const tailIndex = new SessionPrefixIndex(tail, index.revision, index.generation, { toolCalls: index.toolCalls(prefixEnd, tail.length), baseIndex: prefixEnd, initialTurn: prefixEnd ? index.turnId(prefixEnd - 1) : 'legacy:start', timestampFloor: index.summary(prefixEnd).maxTimestamp, todoFloor: index.summary(prefixEnd).maxTodoTimestamp, parent: { index, end: prefixEnd } });
828
930
  const enoughContext = !streaming && (context.enough || sanitizeMessagesForApi(context.marker ? context.local : mergeSessionMessagesAppendOnly(context.local, stateRows, { truncationWatermark: s.truncation_watermark, compressedWatermark: s.truncation_watermark_compressed, stateDbSeenId: effectiveSeenId, stateDbGeneration: index.generation }).filter((m) => !s.active_stream_id || m._turn_id !== s.active_stream_id), { maxMessages: 4 }).length >= 4);
829
931
  const owners = new Map();
830
- const sources = (ids, profile) => {
932
+ const sources = async (ids, profile) => {
831
933
  const ownerKey = JSON.stringify([ids, profile]);
832
934
  const existing = owners.get(ownerKey);
833
935
  if (existing)
834
936
  return existing;
835
- const result = this.stateDbSources(ids, profile);
937
+ const result = await this.stateDbSources(ids, profile);
836
938
  if (result)
837
939
  owners.set(ownerKey, result);
838
940
  return result;
@@ -923,7 +1025,7 @@ export class SessionService {
923
1025
  async detail(sid, query) {
924
1026
  if (!sid)
925
1027
  throw new HttpFailure(400, 'session_id is required');
926
- sid = this.resolveSessionId(sid);
1028
+ sid = await this.resolveSessionId(sid);
927
1029
  const loadMessages = (query.messages ?? '1') !== '0';
928
1030
  const msgLimit = parseMsgLimit(query.msg_limit ?? null);
929
1031
  const msgBeforeRaw = query.msg_before;
@@ -942,11 +1044,28 @@ export class SessionService {
942
1044
  throw new HttpFailure(404, 'Session not found');
943
1045
  }
944
1046
  await this.clearStaleStreamState(s);
945
- const journaled = loadMessages ? (await this.journaledActiveTurn(s)) : null;
946
- const pending = loadMessages ? this.pendingTurn(s) : null;
947
- const prefix = loadMessages && msgLimit !== null ? this.prefixIndex(s, revisionBefore) : null;
948
- const limited = prefix && msgLimit !== null ? (await this.limitedDetail(s, prefix, msgLimit, msgBefore, pending, journaled)) : null;
949
- const stateRows = loadMessages && !limited ? this.stateDbRows(s) : [];
1047
+ return this.detailPayload(s, loadMessages, msgLimit, msgBefore, revisionBefore);
1048
+ }
1049
+ /** Project an already settled session without reloading it or running endpoint recovery/profile gates. */
1050
+ async windowedDetail(s, limit) {
1051
+ return this.detailPayload(s, true, limit, null, await this.loadRevision(s));
1052
+ }
1053
+ async detailPayload(s, loadMessages, msgLimit, msgBefore, revisionBefore) {
1054
+ // TAL-1162: the full transcript read runs on the state.db worker before the lifecycle snapshots are taken, so they
1055
+ // describe the session the payload is built from.
1056
+ const stateRows = loadMessages && msgLimit === null ? await this.stateDbRows(s) : [];
1057
+ let journaled = loadMessages ? (await this.journaledActiveTurn(s)) : null;
1058
+ let pending = loadMessages ? this.pendingTurn(s) : null;
1059
+ let prefix = loadMessages && msgLimit !== null ? this.prefixIndex(s, revisionBefore) : null;
1060
+ let limited = prefix && msgLimit !== null ? (await this.limitedDetail(s, prefix, msgLimit, msgBefore, pending, journaled)) : null;
1061
+ if (prefix && msgLimit !== null && !limited) {
1062
+ // The session changed during the page's transcript read: every snapshot is taken again, once.
1063
+ revisionBefore = await this.loadRevision(s);
1064
+ journaled = await this.journaledActiveTurn(s);
1065
+ pending = this.pendingTurn(s);
1066
+ prefix = this.prefixIndex(s, revisionBefore);
1067
+ limited = await this.limitedDetail(s, prefix, msgLimit, msgBefore, pending, journaled, true);
1068
+ }
950
1069
  let transcript = loadMessages && !limited ? this.mergedTranscript(s, s.messages, stateRows) : [];
951
1070
  if (pending && !limited)
952
1071
  transcript = withPendingUserTurn(transcript, pending);
@@ -982,6 +1101,8 @@ export class SessionService {
982
1101
  let toolCalls = loadMessages ? s.tool_calls : [];
983
1102
  if (windowed)
984
1103
  toolCalls = prefix ? prefix.toolCalls(offset, truncated.length, true) : toolCallsForMessageWindow(toolCalls, offset, truncated.length);
1104
+ if (loadMessages && s.active_stream_id && msgBefore === null)
1105
+ toolCalls = [...toolCalls, ...(this.deps.runtime.liveToolProgress?.(s.active_stream_id) ?? [])];
985
1106
  const mergedCount = summaryCount ?? all.length;
986
1107
  let mergedLast = summaryLast ?? 0;
987
1108
  if (summaryLast === null && all.length) {
@@ -1019,7 +1140,7 @@ export class SessionService {
1019
1140
  raw._msg_limit_max = MAX_MSG_LIMIT;
1020
1141
  const revisionAfter = (await this.loadRevision(s));
1021
1142
  raw._load_revision = revisionBefore !== null && revisionBefore === revisionAfter ? hashRevision(revisionBefore) : `unstable-${randomUUID().replace(/-/g, '')}`;
1022
- Object.assign(raw, withOwnerLocks([raw], limited?.sources ?? this.stateDbSources)[0]);
1143
+ Object.assign(raw, (await this.ownerLocked([raw], limited?.sources))[0]);
1023
1144
  raw.assistant_name = this.assistantName(s);
1024
1145
  raw.workspace_name = (await this.workspaceNames([s]))(s);
1025
1146
  raw.branched_from = (await this.branchedFrom(s));
@@ -1058,10 +1179,10 @@ export class SessionService {
1058
1179
  // A read-only foreign parent (a cron run) has no sidecar and opens from the active profile's state.db while it
1059
1180
  // has transcript rows and is not a deleted chat (`claimOrSynthesizeCliSession`'s `was_webui` and `no_foreign_state`).
1060
1181
  const dbPath = this.stateDbPath(null);
1061
- const row = stateDbSessionRow(dbPath, parentId);
1182
+ const row = await stateDbSessionReadAsync(null, 'stateDbSessionRow', dbPath, parentId);
1062
1183
  if (!row || (await this.store.wasDeleted(parentId)) || (await this.indexMarksWasWebui(parentId)))
1063
1184
  return null;
1064
- if (!stateDbSessionMessages(dbPath, parentId, { stitch: true }).length)
1185
+ if (!(await stateDbSessionReadAsync(false, 'stateDbSessionHasMessages', dbPath, parentId)))
1065
1186
  return null;
1066
1187
  title = row.title;
1067
1188
  }
@@ -1093,7 +1214,7 @@ export class SessionService {
1093
1214
  }
1094
1215
  /** A session without a sidecar, synthesized from state.db for this profile; 404/409 like the detail. */
1095
1216
  async foreignSession(sid) {
1096
- sid = this.resolveSessionId(sid);
1217
+ sid = await this.resolveSessionId(sid);
1097
1218
  const meta = (await this.lookupCliMeta(sid));
1098
1219
  const profile = str(meta?.profile) || null;
1099
1220
  const profileAgnostic = str(meta?.source_tag || meta?.raw_source).trim().toLowerCase() === 'claude_code';
@@ -1125,7 +1246,7 @@ export class SessionService {
1125
1246
  assistant_name: this.assistantName(synth), workspace_name: (await this.workspaceNames([synth]))(synth), confirms_deep_truncate: true,
1126
1247
  };
1127
1248
  attachTodoState(sess, msgs);
1128
- const merged = withSessionWireFlags(withOwnerLocks([meta ? mergeCliSidebarMetadata(sess, meta) : sess], this.stateDbSources)[0], this.deps.runtime.activeStreamIds);
1249
+ const merged = withSessionWireFlags((await this.ownerLocked([meta ? mergeCliSidebarMetadata(sess, meta) : sess]))[0], this.deps.runtime.activeStreamIds);
1129
1250
  const publicRow = redactSessionData(merged, this.deps.redactEnabled());
1130
1251
  return this.owner ? await this.owner.project(publicRow, msgLimit, msgBefore, loadMessages) : publicRow;
1131
1252
  }
@@ -1170,7 +1291,7 @@ export class SessionService {
1170
1291
  const attachments = [...target.pending_attachments];
1171
1292
  // The run's starting read died with its worker: the state.db rows from before it started stand in for it. Its own
1172
1293
  // rows open with its prompt and run to the next prompt; anything else another client wrote meanwhile stays.
1173
- const read = this.stateDbRead(target);
1294
+ const read = await this.settlementRead(target);
1174
1295
  const runStart = target.pending_started_at || events[0]?.created_at || this.deps.now();
1175
1296
  const before = read.rows.filter((m) => Number(m.timestamp) < runStart);
1176
1297
  const runRows = read.rows.filter((m) => Number(m.timestamp) >= runStart);
@@ -1235,17 +1356,25 @@ export class SessionService {
1235
1356
  const cliRead = wantState ? await this.deps.cliSessions(activeProfile, { sourceFilter: params.sourceFilter ?? null }) : undefined;
1236
1357
  const claudeCodeRows = params.showCliSessions && params.showClaudeCodeSessions ? (await this.claudeCodeRows()) : [];
1237
1358
  let cliRows = cliRead ? [...cliRead.rows, ...claudeCodeRows] : undefined;
1359
+ let agentStorage = cliRead?.storage;
1360
+ // TAL-1040: diagnostic chats Web started are Web chats, read even when external Agent sessions are hidden.
1361
+ const webDiagnosticIds = new Set(params.sourceFilter ? [] : await stateDbWebDiagnosticIdsAsync(join(this.deps.profileHome(activeProfile), 'state.db')));
1362
+ const unread = [...webDiagnosticIds].filter((id) => !cliRows?.some((row) => sessionLineageIds(row).has(id)));
1363
+ if (unread.length) {
1364
+ const extra = await this.deps.cliSessions(activeProfile, { sourceFilter: null, sessionIds: unread, keepEmpty: true });
1365
+ cliRows = [...(cliRows ?? []), ...extra.rows];
1366
+ agentStorage ??= extra.storage;
1367
+ }
1238
1368
  const gatewayIdentity = loadGatewaySessionIdentityMap(join(this.deps.profileHome(activeProfile), 'sessions', 'sessions.json'));
1239
1369
  const truncatedSources = cliRead?.truncated;
1240
- let agentStorage = cliRead?.storage;
1241
1370
  if (cliRows) {
1242
1371
  const expanded = await this.persistedAliasMetadata(webuiRows, cliRows);
1243
1372
  cliRows = expanded.rows;
1244
1373
  if (expanded.storage && (expanded.storage.state === 'corrupt' || agentStorage?.state === 'ok'))
1245
1374
  agentStorage = expanded.storage;
1246
1375
  }
1247
- const lookups = await this.listStateDbLookups(webuiRows, (cliRows ?? []).map((r) => str(r.session_id)));
1248
- const payload = (await buildSessionListPayload(this.store, { ...params, ...(cliRows ? { cliRows } : {}), ...(truncatedSources ? { truncatedSources } : {}), ...(agentStorage ? { agentStorage } : {}), gatewayIdentity, ...lookups, webuiRows, activeProfile, isolatedProfileMode: this.deps.isolatedProfileMode(), profilesMatch: this.deps.profilesMatch }));
1376
+ const lookups = await withStateDbListingReads(() => this.listStateDbLookups(webuiRows, (cliRows ?? []).map((r) => str(r.session_id))));
1377
+ const payload = (await buildSessionListPayload(this.store, { ...params, ...(cliRows ? { cliRows } : {}), ...(truncatedSources ? { truncatedSources } : {}), ...(agentStorage ? { agentStorage } : {}), gatewayIdentity, ...lookups, webuiRows, webDiagnosticIds, activeProfile, isolatedProfileMode: this.deps.isolatedProfileMode(), profilesMatch: this.deps.profilesMatch }));
1249
1378
  return sessionListResponse(payload, this.deps.runtime, this.deps.redactEnabled(), this.deps.now(), await this.workspaceNames([...payload.sessions, ...payload.sidebar_reference_sessions]));
1250
1379
  }
1251
1380
  async search(q, opts) {
@@ -1255,7 +1384,7 @@ export class SessionService {
1255
1384
  sessions = sessions.filter((r) => this.deps.profilesMatch(str(r.profile) || null, activeProfile));
1256
1385
  const metadata = await this.persistedAliasMetadata(sessions);
1257
1386
  sessions = reconcileLocalSessionRows(sessions, metadata.rows, this.deps.profilesMatch);
1258
- sessions = withOwnerLocks(sessions, this.stateDbSources);
1387
+ sessions = await this.ownerLocked(sessions);
1259
1388
  const redact = this.deps.redactEnabled();
1260
1389
  const workspaceName = await this.workspaceNames(sessions);
1261
1390
  const redactRow = (item) => {
@@ -1347,6 +1476,29 @@ export class SessionService {
1347
1476
  input_tokens: inp, output_tokens: out, total_tokens: inp + out, estimated_cost: full.estimated_cost,
1348
1477
  };
1349
1478
  }
1479
+ async contextDetail(sid, expectedKey) {
1480
+ if (await this.owner?.isOwned(sid))
1481
+ return this.owner.contextDetail(sid, expectedKey);
1482
+ const session = await this.get404(sid);
1483
+ const profile = this.deps.activeProfile();
1484
+ if (session.profile && session.profile !== profile || await this.loadedReadOnlyReason(session))
1485
+ return unavailableContextDetail();
1486
+ const sidecar = this.deps.sidecar?.();
1487
+ if (!sidecar)
1488
+ return unavailableContextDetail();
1489
+ // The read owns no cache. Compression and model/workspace replacement invalidate its response.
1490
+ const marks = (current = session) => this.contextDetailKey(current);
1491
+ const before = marks();
1492
+ if (before !== expectedKey)
1493
+ return unavailableContextDetail('changed');
1494
+ try {
1495
+ const detail = await sidecar.call('chat.context_detail', { session_id: sid, profile_home: this.deps.profileHome(profile), model: session.model, model_provider: session.model_provider, workspace: session.workspace });
1496
+ return this.deps.activeProfile() === profile && before === marks(await this.get404(sid)) ? detail : unavailableContextDetail('changed');
1497
+ }
1498
+ catch {
1499
+ return unavailableContextDetail();
1500
+ }
1501
+ }
1350
1502
  async usage(sid) {
1351
1503
  const s = (await this.get404(sid));
1352
1504
  const inp = Math.trunc(s.input_tokens) || 0;
@@ -1407,6 +1559,36 @@ export class SessionService {
1407
1559
  this.publish('session_new', s.profile, s.session_id);
1408
1560
  return s;
1409
1561
  }
1562
+ /**
1563
+ * TAL-1040: an `ignore_config` / `safe` chat starts through the active profile's Agent owner, which freezes the mode before
1564
+ * any Agent import. That owner reads no profile default, so the launch route is explicit: the requested model, else this
1565
+ * profile's default as a normal chat starts, with that provider's configured endpoint.
1566
+ */
1567
+ async createDiagnostic(body, mode) {
1568
+ const profile = (typeof body.profile === 'string' && body.profile) || this.deps.activeProfile();
1569
+ if (profile !== this.deps.activeProfile())
1570
+ throw new HttpFailure(400, 'A diagnostic chat starts in the active profile');
1571
+ if (!this.owner)
1572
+ throw new HttpFailure(409, 'The Agent cannot start a chat in this mode', { code: 'execution_mode_unavailable', reason: 'gateway_unavailable' });
1573
+ const defaults = await this.store.deps.defaults(profile);
1574
+ let workspace;
1575
+ try {
1576
+ workspace = await this.resolveNewSessionWorkspace(body, null, profile);
1577
+ }
1578
+ catch (error) {
1579
+ throw new HttpFailure(400, error.message);
1580
+ }
1581
+ const [requested, requestedProvider] = this.deps.modelStateFromRequest(body.model, body.model_provider, null);
1582
+ const model = requested ?? defaults.model;
1583
+ const provider = requested ? requestedProvider : requestedProvider ?? defaults.modelProvider ?? null;
1584
+ if (!model)
1585
+ throw new HttpFailure(400, 'Choose a model before starting a diagnostic chat');
1586
+ const toolsets = this.validateToolsetsShape(body.enabled_toolsets);
1587
+ const route = provider ? this.deps.profileLaunchRoute?.(profile, provider) ?? { provider, baseUrl: null } : null;
1588
+ const sid = await this.owner.create(profile, typeof body.request_id === 'string' ? body.request_id : null, { execution_mode: mode, cwd: workspace ?? defaults.workspace, model,
1589
+ ...(route ? { provider: route.provider } : {}), ...(route?.baseUrl ? { base_url: route.baseUrl } : {}), ...(toolsets ? { toolsets } : {}) });
1590
+ return this.detail(sid, {});
1591
+ }
1410
1592
  // ── simple metadata mutations ────────────────────────────────────────────
1411
1593
  /** Python `_persist_generated_session_title`: title + generated/manual flags under the session lock, then insights sync. */
1412
1594
  async persistGeneratedTitle(sid, nextTitle, eventReason) {
@@ -1431,7 +1613,8 @@ export class SessionService {
1431
1613
  return current;
1432
1614
  }
1433
1615
  async rename(sid, rawTitle) {
1434
- const s = (await this.mutationTarget(sid, 'renamed'));
1616
+ const reads = this.sessionGateReads(sid);
1617
+ const s = (await this.mutationTarget(sid, 'renamed', reads));
1435
1618
  await this.withMutationLock(sid, async () => {
1436
1619
  applySessionTitleRename(s, rawTitle);
1437
1620
  await this.store.save(s);
@@ -1449,8 +1632,9 @@ export class SessionService {
1449
1632
  return run;
1450
1633
  }
1451
1634
  async pinUnserialized(sid, pinRequested) {
1452
- await this.rejectViewOnly(sid, 'modified');
1453
- let s = (await this.get404(sid));
1635
+ const reads = this.sessionGateReads(sid);
1636
+ await this.rejectViewOnly(sid, 'modified', reads);
1637
+ let s = (await this.get404(sid, {}, reads));
1454
1638
  s = (await this.store.ensureFull(sid, s));
1455
1639
  if (pinRequested && !s.pinned) {
1456
1640
  const rows = (await allSessions(this.store));
@@ -1470,16 +1654,143 @@ export class SessionService {
1470
1654
  this.publish('session_pin', s.profile, s.session_id);
1471
1655
  return { ok: true, session: (await this.wireRow(s)) };
1472
1656
  }
1657
+ /**
1658
+ * TAL-1254: relock the worktrees of every unarchived worktree chat (or just `only`) for the running sidecar, so the
1659
+ * Agent's worktree sweep reads them as live, and flag each one git no longer has as retained. Runs before a
1660
+ * (re)started sidecar reports ready and when a chat is unarchived. The Agent's sweep runs outside Talaria, so a lock
1661
+ * the sidecar could not confirm is retried until git takes it; meanwhile that tree keeps its flag.
1662
+ */
1663
+ async retainWorktrees(call, only) {
1664
+ const { rows, unread } = only ? { rows: [only.compact()], unread: 0 } : await this.worktreeSessionRows();
1665
+ const targets = rows.filter((row) => !row.archived && typeof row.worktree_path === 'string' && row.worktree_path && typeof row.session_id === 'string');
1666
+ if (!targets.length) {
1667
+ if (unread)
1668
+ this.retryWorktreeRetention();
1669
+ return;
1670
+ }
1671
+ let results;
1672
+ try {
1673
+ ({ results } = await call('worktree.retain', { paths: [...new Set(targets.map((row) => String(row.worktree_path)))] }));
1674
+ }
1675
+ catch (error) {
1676
+ this.retryWorktreeRetention();
1677
+ throw error;
1678
+ }
1679
+ const unconfirmed = targets.filter((row) => !results[String(row.worktree_path)]).length + unread;
1680
+ if (!only && unconfirmed !== this.worktreesUnconfirmed)
1681
+ this.deps.log(`[webui] ${String(unconfirmed)} worktree lock(s) not yet held by the sidecar${unconfirmed ? '; retrying' : ''}`);
1682
+ if (!only)
1683
+ this.worktreesUnconfirmed = unconfirmed;
1684
+ if (unconfirmed)
1685
+ this.retryWorktreeRetention();
1686
+ for (const row of targets) {
1687
+ const state = results[String(row.worktree_path)];
1688
+ const sid = String(row.session_id);
1689
+ if (!state)
1690
+ continue;
1691
+ try {
1692
+ // `extra` follows the transcript in the file, past what a metadata-only read sees.
1693
+ if (row._unread)
1694
+ continue;
1695
+ const s = await this.store.ensureFull(sid, await this.store.get(sid, { metadataOnly: true }));
1696
+ if ((state === 'missing') === (s.extra[WORKTREE_RETAINED_KEY] === true))
1697
+ continue;
1698
+ await this.withMutationLock(sid, async () => {
1699
+ s.extra = Object.fromEntries(Object.entries(s.extra).filter(([key]) => key !== WORKTREE_RETAINED_KEY));
1700
+ if (state === 'missing')
1701
+ s.extra[WORKTREE_RETAINED_KEY] = true;
1702
+ await this.store.save(s, { touchUpdatedAt: false });
1703
+ }, { allowReadOnly: true });
1704
+ this.publish('session_worktree', s.profile, sid);
1705
+ }
1706
+ catch (error) {
1707
+ if (error instanceof SessionNotFound)
1708
+ continue;
1709
+ this.deps.log(`[webui] could not record the worktree state of ${sid}: ${error.message}`);
1710
+ this.retryWorktreeRetention();
1711
+ }
1712
+ }
1713
+ }
1714
+ /**
1715
+ * Every persisted worktree chat, read from its own file: the index only narrows the files to read, since a crash
1716
+ * between a save and its index update can leave a session out of the index or with a stale `archived`. A file that
1717
+ * cannot be read now, or a session directory that cannot be listed, falls back to the index rows (`_unread`, so their
1718
+ * flags are left alone) and counts in `unread`, which keeps retention retrying until every file has been read.
1719
+ */
1720
+ async worktreeSessionRows() {
1721
+ let indexed;
1722
+ try {
1723
+ indexed = new Map((await this.store.readIndexEntries()).map((row) => [str(row.session_id), row]));
1724
+ }
1725
+ catch {
1726
+ indexed = new Map();
1727
+ }
1728
+ let persisted;
1729
+ try {
1730
+ persisted = await this.store.persistedIds({ strict: true });
1731
+ }
1732
+ catch {
1733
+ // The session directory cannot be listed now: retain what the index names and retry until it can.
1734
+ const rows = [...indexed.values()].filter((row) => row.worktree_path).map((row) => ({ ...row, archived: false, _unread: true }));
1735
+ return { rows, unread: Math.max(1, rows.length) };
1736
+ }
1737
+ const candidates = [...persisted].filter((sid) => !indexed.has(sid) || indexed.get(sid)?.worktree_path);
1738
+ let unread = 0;
1739
+ const rows = await Promise.all(candidates.map(async (sid) => {
1740
+ try {
1741
+ return (await this.store.get(sid, { metadataOnly: true })).compact();
1742
+ }
1743
+ catch (error) {
1744
+ if (error instanceof SessionNotFound)
1745
+ return null;
1746
+ unread += 1;
1747
+ const row = indexed.get(sid);
1748
+ return row?.worktree_path ? { ...row, archived: false, _unread: true } : null;
1749
+ }
1750
+ }));
1751
+ return { rows: rows.filter((row) => row !== null), unread };
1752
+ }
1753
+ /** One pending retry at a time; a sidecar that is not ready by then retains everything in its next ready gate instead. */
1754
+ retryWorktreeRetention() {
1755
+ if (this.worktreeRetainRetry)
1756
+ return;
1757
+ this.worktreeRetainRetry = setTimeout(() => {
1758
+ this.worktreeRetainRetry = null;
1759
+ const sidecar = this.deps.sidecar?.();
1760
+ if (sidecar?.status !== 'ready')
1761
+ return;
1762
+ this.retainWorktrees((method, params, opts) => sidecar.call(method, params, opts)).catch((error) => { this.deps.log(`[webui] worktree lock retry failed: ${String(error)}`); });
1763
+ }, WORKTREE_RETAIN_RETRY_MS);
1764
+ this.worktreeRetainRetry.unref();
1765
+ }
1473
1766
  async archive(sid, archived) {
1474
- await this.rejectViewOnly(sid, 'archived');
1475
- let s = (await this.get404(sid));
1767
+ const reads = this.sessionGateReads(sid);
1768
+ await this.rejectViewOnly(sid, 'archived', reads);
1769
+ let s = (await this.get404(sid, {}, reads));
1476
1770
  s = (await this.store.ensureFull(sid, s));
1477
1771
  await this.withMutationLock(sid, async () => {
1772
+ s = await this.store.ensureFull(sid, await this.store.get(sid));
1773
+ const previous = { archived: s.archived, pinned: s.pinned };
1774
+ const wasArchived = (await this.store.loadMetadataOnly(sid))?.archived ?? false;
1478
1775
  s.archived = archived;
1479
1776
  if (archived)
1480
1777
  s.pinned = false;
1481
- await this.store.save(s, { touchUpdatedAt: false });
1778
+ try {
1779
+ await this.store.save(s, { touchUpdatedAt: false });
1780
+ const saved = await this.store.loadMetadataOnly(sid);
1781
+ if (saved?.archived !== archived || saved.pinned !== s.pinned)
1782
+ throw new HttpFailure(409, 'Archive was not saved; reload the chat and retry');
1783
+ }
1784
+ catch (error) {
1785
+ Object.assign(s, previous);
1786
+ throw error;
1787
+ }
1788
+ if (archived && !wasArchived)
1789
+ await this.finalizeSession(sid, s.profile, 'archive', stateDbReadLineage(s) ?? undefined);
1482
1790
  }, { allowReadOnly: true });
1791
+ const sidecar = this.deps.sidecar?.();
1792
+ if (!archived && sidecar)
1793
+ await this.retainWorktrees((method, params, opts) => sidecar.call(method, params, opts), s).catch(() => undefined);
1483
1794
  this.publish('session_archive', s.profile, s.session_id);
1484
1795
  return { ok: true, session: (await this.wireRow(s)), ...worktreeRetainedPayload(s) };
1485
1796
  }
@@ -1507,10 +1818,11 @@ export class SessionService {
1507
1818
  }
1508
1819
  modelConfirmationKey = randomUUID();
1509
1820
  async confirmModelSwitch(s, model, provider, token, retry = false) {
1821
+ const stateRows = await this.stateDbRows(s);
1510
1822
  const profile = s.profile ?? this.deps.activeProfile();
1511
1823
  const home = this.deps.profileHome(profile);
1512
1824
  const context = contextUsage(s).context_used_tokens;
1513
- const messages = this.modelContext(s);
1825
+ const messages = this.modelContext(s, stateRows);
1514
1826
  const before = JSON.stringify([s.model, s.model_provider, context, s.context_length, messages]);
1515
1827
  let decision;
1516
1828
  try {
@@ -1522,7 +1834,8 @@ export class SessionService {
1522
1834
  catch (error) {
1523
1835
  throw new HttpFailure(503, `Cannot check model selection warnings: ${error.message}`);
1524
1836
  }
1525
- if (before !== JSON.stringify([s.model, s.model_provider, contextUsage(s).context_used_tokens, s.context_length, this.modelContext(s)])) {
1837
+ const stateRowsAfter = await this.stateDbRows(s);
1838
+ if (before !== JSON.stringify([s.model, s.model_provider, contextUsage(s).context_used_tokens, s.context_length, this.modelContext(s, stateRowsAfter)])) {
1526
1839
  if (retry)
1527
1840
  throw new HttpFailure(503, 'The session changed while checking model warnings. Try the selection again.');
1528
1841
  return this.confirmModelSwitch(s, model, provider, null, true);
@@ -1556,7 +1869,7 @@ export class SessionService {
1556
1869
  const contextLength = changed ? (this.deps.resolveContextLength ? await this.deps.resolveContextLength(nextModel, provider, s.profile) : this.deps.contextLengthFor(nextModel, provider)) : s.context_length;
1557
1870
  if (changed)
1558
1871
  await this.confirmModelSwitch(s, nextModel ?? '', provider, body.confirm_token);
1559
- this.assertWritable(s);
1872
+ await this.assertWritable(s);
1560
1873
  s.workspace = newWs;
1561
1874
  if (hasModel) {
1562
1875
  s.model = nextModel;
@@ -1564,9 +1877,10 @@ export class SessionService {
1564
1877
  if (changed) {
1565
1878
  s.context_length = contextLength;
1566
1879
  forgetContextFigures(s);
1567
- await this.deps.runtime.evictAgent(sid);
1568
1880
  }
1569
1881
  }
1882
+ if (changed || oldWs !== newWs)
1883
+ await this.deps.runtime.evictAgent(sid);
1570
1884
  await this.store.save(s);
1571
1885
  });
1572
1886
  if (str(oldWs) !== str(newWs))
@@ -1619,7 +1933,7 @@ export class SessionService {
1619
1933
  throw new HttpFailure(400, 'keep_count must be non-negative');
1620
1934
  const refused = await this.withMutationLock(sid, async () => {
1621
1935
  // One state.db read decides and marks the boundary, so a row committed meanwhile is neither counted nor hidden.
1622
- const read = this.stateDbRead(s);
1936
+ const read = await this.stateDbRead(s);
1623
1937
  if (hold) {
1624
1938
  // A failed read cannot say whether a CLI continuation would go too: fail closed. The Agent keeps a state.db for every
1625
1939
  // profile it has run in, so a missing one is never evidence of a session without state.db rows.
@@ -1745,7 +2059,7 @@ export class SessionService {
1745
2059
  return false;
1746
2060
  let tokens;
1747
2061
  try {
1748
- tokens = (await sidecar.call('models.estimate_tokens', { messages: this.modelContext(s) })).tokens;
2062
+ tokens = (await sidecar.call('models.estimate_tokens', { messages: await this.readModelContext(s) })).tokens;
1749
2063
  }
1750
2064
  catch {
1751
2065
  return false;
@@ -1786,7 +2100,7 @@ export class SessionService {
1786
2100
  async compressionTarget(sid) {
1787
2101
  await this.rejectViewOnly(sid, 'compressed');
1788
2102
  const s = (await this.mutationTarget(sid, 'compressed'));
1789
- const stateRows = this.stateDbRows(s);
2103
+ const stateRows = await this.stateDbRows(s);
1790
2104
  const context = this.compressionContext(s, stateRows);
1791
2105
  return { s, history: sanitizeMessagesForApi(context), context, stateRows };
1792
2106
  }
@@ -1830,7 +2144,7 @@ export class SessionService {
1830
2144
  }
1831
2145
  /** Python `_handle_conversation_rounds` (TAL-258): the session's rounds and whether the handoff dock is due. */
1832
2146
  async conversationRounds(sid, since) {
1833
- const rounds = countConversationRounds((await this.handoffTarget(sid)).dbPath, sid, since);
2147
+ const rounds = await stateDbSessionReadAsync(0, 'countConversationRounds', (await this.handoffTarget(sid)).dbPath, sid, since);
1834
2148
  return { ok: true, rounds, threshold: CONVERSATION_ROUND_THRESHOLD, should_show: rounds >= CONVERSATION_ROUND_THRESHOLD };
1835
2149
  }
1836
2150
  /**
@@ -1839,40 +2153,41 @@ export class SessionService {
1839
2153
  * back to a deterministic local summary rather than an error.
1840
2154
  */
1841
2155
  async handoffSummary(sid, since) {
1842
- sid = (await this.rejectViewOnly(sid, 'summarized'));
2156
+ const reads = this.sessionGateReads(sid.trim());
2157
+ sid = (await this.rejectViewOnly(sid, 'summarized', reads));
1843
2158
  const { local, profile, dbPath } = (await this.handoffTarget(sid));
1844
- const rounds = countConversationRounds(dbPath, sid, since);
2159
+ const rounds = await stateDbSessionReadAsync(0, 'countConversationRounds', dbPath, sid, since);
1845
2160
  if (rounds < CONVERSATION_ROUND_THRESHOLD)
1846
2161
  throw new HttpFailure(400, 'Not enough conversation rounds to generate a summary.');
1847
2162
  // A running turn settles the transcript it started from, which would drop a card appended meanwhile.
1848
2163
  if (local?.active_stream_id)
1849
2164
  throw new HttpFailure(409, 'Session is still streaming; wait for the current turn to finish.');
1850
- const all = stateDbSessionMessages(dbPath, sid, { stitch: true });
2165
+ const all = await stateDbSessionReadAsync([], 'stateDbSessionMessages', dbPath, sid, { stitch: true });
1851
2166
  const messages = (since === null ? all : all.filter((m) => { const ts = stateDbTimestampSeconds(m.timestamp); return ts !== null && ts > since; })).slice(-50);
1852
2167
  if (messages.length < 2)
1853
2168
  throw new HttpFailure(400, 'Not enough messages to summarize.');
1854
2169
  const sidecar = this.deps.sidecar?.() ?? null;
1855
2170
  await ensureAgentRuntimeCurrent(sidecar);
1856
- const meta = local ? null : (await this.lookupCliMeta(sid));
1857
- const generated = await this.generateHandoffSummary(sidecar, profile, local, meta, messages);
2171
+ const meta = local ? null : (await reads.meta());
2172
+ const generated = await this.generateHandoffSummary(sidecar, profile, local, meta, messages, [...all, ...(local?.messages ?? []), ...(local?.context_messages ?? [])]);
1858
2173
  const pick = (row, ...keys) => { for (const key of keys) {
1859
2174
  const v = str(row?.[key]).trim();
1860
2175
  if (v)
1861
2176
  return v;
1862
2177
  } return ''; };
1863
2178
  // Python `get_cli_sessions` never listed WebUI's own state.db rows, so they name no channel.
1864
- const cliRow = meta ?? (await this.lookupCliMeta(sid));
2179
+ const cliRow = meta ?? (await reads.meta());
1865
2180
  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;
1866
2181
  await this.persistHandoffMarker(sid, profile, handoffMarker(sid, generated.summary, channel, rounds, generated.fallback, this.deps.now()));
1867
2182
  return { ok: true, summary: generated.summary, message_count: messages.length, rounds, fallback: generated.fallback, ...(generated.warning ? { warning: generated.warning } : {}) };
1868
2183
  }
1869
2184
  /** The main model's summary at 700 tokens, once more at 1400 when it was cut off, else the local fallback. */
1870
- async generateHandoffSummary(sidecar, profile, local, meta, messages) {
2185
+ async generateHandoffSummary(sidecar, profile, local, meta, messages, source) {
1871
2186
  const fallback = (warning) => ({ summary: fallbackHandoffSummary(messages), fallback: true, ...(warning ? { warning: `Summary generation used local fallback: ${warning}` } : {}) });
1872
2187
  if (!sidecar)
1873
2188
  return fallback('the Agent sidecar is not running');
1874
2189
  const [model, provider] = local ? this.deps.modelStateFromRequest(local.model, local.model_provider, local.model_provider) : [str(meta?.model).trim() || null, null];
1875
- const prompt = [{ role: 'system', content: HANDOFF_SYSTEM_PROMPT }, { role: 'user', content: `Conversation transcript:\n${handoffTranscript(messages)}` }];
2190
+ const prompt = withDataUseRequirements([{ role: 'system', content: HANDOFF_SYSTEM_PROMPT }, { role: 'user', content: `Conversation transcript:\n${handoffTranscript(messages)}` }], source);
1876
2191
  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 });
1877
2192
  try {
1878
2193
  let result = await ask(700);
@@ -1898,7 +2213,7 @@ export class SessionService {
1898
2213
  const card = messageHandoffPayload(marker);
1899
2214
  const toStateDb = async () => {
1900
2215
  await this.rejectViewOnly(sid, 'summarized');
1901
- if (sameHandoff(handoffPayload(stateDbTailToolContent(this.stateDbPath(profile), sid)), card))
2216
+ if (sameHandoff(handoffPayload(await stateDbSessionReadAsync(null, 'stateDbTailToolContent', this.stateDbPath(profile), sid)), card))
1902
2217
  return true;
1903
2218
  const sidecar = this.deps.sidecar?.();
1904
2219
  if (!sidecar)
@@ -2003,11 +2318,11 @@ export class SessionService {
2003
2318
  catch {
2004
2319
  throw new HttpFailure(404, 'Session not found');
2005
2320
  }
2321
+ // One state.db read serves the check, the kept display rows, and the boundary; every gate below decides after it.
2322
+ const read = await this.stateDbRead(live);
2006
2323
  const turn = live.active_stream_id;
2007
2324
  if (turn && !waited(turn))
2008
2325
  throw new HttpFailure(409, 'Session stream state changed during compression; please retry.');
2009
- // One state.db read serves the check, the kept display rows, and the boundary.
2010
- const read = this.stateDbRead(live);
2011
2326
  const stateRows = read.rows;
2012
2327
  const tail = turnTail(this.modelContext(live, stateRows).filter((m) => !turn || m._turn_id !== turn), context);
2013
2328
  if (!tail || !turnTail(live.messages, s.messages))
@@ -2090,8 +2405,8 @@ export class SessionService {
2090
2405
  }
2091
2406
  }
2092
2407
  }
2093
- /** Clear and delete share the profile-scoped, best-effort Agent session boundary. */
2094
- async finalizeSession(sid, profile, reason) {
2408
+ /** Profile-scoped, best-effort lifecycle delivery; archive only notifies plugins. */
2409
+ async finalizeSession(sid, profile, reason, lineage) {
2095
2410
  const sidecar = this.deps.sidecar?.();
2096
2411
  if (!sidecar || this.deps.profileDeleting?.(profile))
2097
2412
  return [];
@@ -2099,10 +2414,11 @@ export class SessionService {
2099
2414
  try {
2100
2415
  return (await sidecar.call('chat.finalize_session', {
2101
2416
  session_id: sid, profile_home: this.deps.profileHome(profile ?? this.deps.activeProfile()), reason,
2417
+ ...(lineage ? { lineage } : {}),
2102
2418
  }, { timeoutMs: 12_000 })).messages;
2103
2419
  }
2104
- catch (error) {
2105
- this.deps.log(`[webui] session finalize (${reason}) failed: ${error.message}`);
2420
+ catch {
2421
+ this.deps.log(`[webui] session lifecycle (${reason}) failed`);
2106
2422
  return [];
2107
2423
  }
2108
2424
  finally {
@@ -2114,6 +2430,8 @@ export class SessionService {
2114
2430
  const s = (await this.mutationTarget(sid, 'modified'));
2115
2431
  await this.withMutationLock(sid, async () => {
2116
2432
  const messages = await this.finalizeSession(sid, s.profile, 'clear');
2433
+ // The boundary covers every row this read saw, the session's finalization rows included.
2434
+ const read = await this.stateDbRead(s);
2117
2435
  const hadMessages = s.messages.length > 0;
2118
2436
  truncateSessionAtKeep(s, 0);
2119
2437
  s.messages = messages.map((content) => ({ role: 'local_notice', content, _notice_kind: 'session_finalize', timestamp: this.deps.now() }));
@@ -2139,7 +2457,7 @@ export class SessionService {
2139
2457
  s.pending_user_source = null;
2140
2458
  s.clear_generation = hadMessages ? randomUUID().replace(/-/g, '') : null;
2141
2459
  applySessionTitleRename(s, 'Untitled');
2142
- this.markStateDbSeen(s, undefined, { boundary: true });
2460
+ this.markStateDbSeen(s, read, { boundary: true });
2143
2461
  await this.store.save(s);
2144
2462
  if (hadMessages) {
2145
2463
  try {
@@ -2155,6 +2473,7 @@ export class SessionService {
2155
2473
  await this.rejectViewOnly(sid, 'modified');
2156
2474
  return this.withMutationLock(sid, async () => {
2157
2475
  const s = (await this.mutationTarget(sid, 'modified'));
2476
+ const read = await this.stateDbRead(s);
2158
2477
  const history = s.messages;
2159
2478
  const lastUser = findLastUserIndex(history);
2160
2479
  if (lastUser === null)
@@ -2170,7 +2489,7 @@ export class SessionService {
2170
2489
  return { error: 'The last message has nothing to resend.' };
2171
2490
  const removed = history.length - lastUser;
2172
2491
  shrinkTo(s, lastUser);
2173
- this.markStateDbSeen(s, undefined, { boundary: true });
2492
+ this.markStateDbSeen(s, read, { boundary: true });
2174
2493
  await this.store.save(s);
2175
2494
  return { ok: true, last_user_text: lastUserText, last_user_prompt: lastUserPrompt, last_user_attachments: lastUserAttachments, removed_count: removed };
2176
2495
  });
@@ -2179,6 +2498,7 @@ export class SessionService {
2179
2498
  await this.rejectViewOnly(sid, 'modified');
2180
2499
  return this.withMutationLock(sid, async () => {
2181
2500
  const s = (await this.mutationTarget(sid, 'modified'));
2501
+ const read = await this.stateDbRead(s);
2182
2502
  const history = s.messages;
2183
2503
  const lastUser = findLastUserIndex(history);
2184
2504
  if (lastUser === null)
@@ -2186,7 +2506,7 @@ export class SessionService {
2186
2506
  const removedText = extractText(history[lastUser]?.content);
2187
2507
  const removed = history.length - lastUser;
2188
2508
  shrinkTo(s, lastUser);
2189
- this.markStateDbSeen(s, undefined, { boundary: true });
2509
+ this.markStateDbSeen(s, read, { boundary: true });
2190
2510
  await this.store.save(s);
2191
2511
  const preview = removedText.length > 40 ? `${removedText.slice(0, 40)}...` : removedText;
2192
2512
  return { ok: true, removed_count: removed, removed_preview: preview };
@@ -2239,14 +2559,14 @@ export class SessionService {
2239
2559
  catch { /* ignore */ }
2240
2560
  }
2241
2561
  // Python: `keep_count` indexes the merged display transcript, never the raw sidecar array.
2242
- const sourceMessages = this.mergedTranscript(source);
2562
+ const stateRows = await this.stateDbRows(source);
2563
+ const sourceMessages = this.mergedTranscript(source, source.messages, stateRows);
2243
2564
  const forked = keepCount !== null ? sourceMessages.slice(0, keepCount) : [...sourceMessages];
2244
2565
  const title = customTitle ?? `${source.title || 'Untitled'} (fork)`;
2245
2566
  const forkKeep = keepCount ?? sourceMessages.length;
2246
2567
  // The branch's model context must carry the retained state.db rows too: the new id has no state.db rows of its
2247
2568
  // own, and the runner prefers a non-empty context over the displayed messages.
2248
- const stateRows = this.stateDbRows(source);
2249
- const sourceContext = stateRows.length ? this.mergedTranscript(source, source.context_messages.length ? source.context_messages : source.messages) : source.context_messages;
2569
+ const sourceContext = stateRows.length ? this.mergedTranscript(source, source.context_messages.length ? source.context_messages : source.messages, stateRows) : source.context_messages;
2250
2570
  const forkedContext = copyJson(truncateContextForDisplayKeep(sourceContext, sourceMessages, forkKeep));
2251
2571
  const branch = new Session({
2252
2572
  workspace: source.workspace, model: source.model, model_provider: source.model_provider, profile: source.profile, title, messages: withMirrorSources(forked, source), project_id: source.project_id,
@@ -2263,11 +2583,11 @@ export class SessionService {
2263
2583
  }
2264
2584
  /** Python `read_session_lineage_report` on the active profile's state.db. */
2265
2585
  lineageReport(sid) {
2266
- return stateDbLineageReport(this.stateDbPath(null), sid);
2586
+ return stateDbSessionReadAsync(emptyLineageReport(sid.trim()), 'stateDbLineageReport', this.stateDbPath(null), sid);
2267
2587
  }
2268
2588
  /** TAL-259: recovery reads the active profile's state.db, and a recovered sidecar joins that profile. */
2269
2589
  async recoveryDeps() {
2270
- return { store: this.store, stateDbPath: this.stateDbPath(null), profile: this.deps.activeProfile(), stampingSince: await this.deps.recoveryStampingSince(), isReadOnly: (sid, profile) => Boolean(withOwnerLocks([{ session_id: sid, profile }], this.stateDbSources)[0]?.read_only), log: this.deps.log };
2590
+ return { store: this.store, stateDbPath: this.stateDbPath(null), profile: this.deps.activeProfile(), stampingSince: await this.deps.recoveryStampingSince(), isReadOnly: async (sid, profile) => Boolean((await this.ownerLocked([{ session_id: sid, profile }]))[0]?.read_only), log: this.deps.log };
2271
2591
  }
2272
2592
  async recoveryAudit() {
2273
2593
  return (await auditSessionRecovery(await this.recoveryDeps()));
@@ -2301,6 +2621,7 @@ export class SessionService {
2301
2621
  project_id: source.project_id, profile: source.profile, session_source: 'fork', personality: source.personality, enabled_toolsets: copyJson(source.enabled_toolsets),
2302
2622
  context_length: source.context_length, threshold_tokens: source.threshold_tokens, gateway_routing: copyJson(source.gateway_routing), gateway_routing_history: copyJson(source.gateway_routing_history),
2303
2623
  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,
2624
+ ...(source.extra[WORKTREE_RETAINED_KEY] === true ? { [WORKTREE_RETAINED_KEY]: true } : {}),
2304
2625
  compression_recovery_source_session_id: sid, compression_recovery_action: action,
2305
2626
  // An empty model-facing transcript: the focused follow-up must not replay the exhausted context.
2306
2627
  messages: [], context_messages: [], composer_draft: { text: '', files: [] },
@@ -2360,13 +2681,14 @@ export class SessionService {
2360
2681
  throw new HttpFailure(400, 'session_id is required');
2361
2682
  if (!isSafeSessionId(sid))
2362
2683
  throw new HttpFailure(400, 'Invalid session_id');
2363
- await this.rejectViewOnly(sid, 'deleted');
2684
+ const reads = this.sessionGateReads(sid);
2685
+ await this.rejectViewOnly(sid, 'deleted', reads);
2364
2686
  // Python: a read-only import (persisted sidecar flag, sidebar metadata, or a foreign state.db owner such as a
2365
2687
  // Claude Code session) is never deleted — that would erase the owner's authoritative transcript. An orphan has none.
2366
- const orphaned = (await this.isOrphanedImport(sid));
2367
- if (!orphaned && (await this.isReadOnlyImport(sid)))
2688
+ const orphaned = (await this.isOrphanedImport(sid, reads));
2689
+ if (!orphaned && (await this.isReadOnlyImport(sid, reads)))
2368
2690
  throw new HttpFailure(400, 'Read-only imported sessions cannot be deleted from WebUI');
2369
- if ((await this.isSubagentViewOnly(sid)))
2691
+ if ((await this.isSubagentViewOnly(sid, reads)))
2370
2692
  throw new HttpFailure(400, 'Subagent sessions are view-only and cannot be deleted from WebUI');
2371
2693
  const retained = (await (async () => { try {
2372
2694
  return worktreeRetainedPayload((await this.store.get(sid, { metadataOnly: true })));
@@ -2382,8 +2704,8 @@ export class SessionService {
2382
2704
  eventProfile = null;
2383
2705
  }
2384
2706
  // Python `_is_messaging_session_id`: decided before the JSON is gone, from WebUI metadata or the Agent's row.
2385
- const isMessaging = (await this.isMessagingSession(sid));
2386
- const knownIds = !isMessaging && !orphaned ? stateDbCompressionLineage(this.stateDbPath(eventProfile), sid) : [sid];
2707
+ const isMessaging = (await this.isMessagingSession(sid, reads));
2708
+ const knownIds = !isMessaging && !orphaned ? await stateDbSessionReadAsync([sid], 'stateDbCompressionLineage', this.stateDbPath(eventProfile), sid) : [sid];
2387
2709
  const assertIdle = async () => { for (const id of knownIds)
2388
2710
  if (await this.activeRunBlocking(id))
2389
2711
  throw new HttpFailure(409, 'Session has an active run; stop it before deleting'); };
@@ -2693,7 +3015,7 @@ export class SessionService {
2693
3015
  session = (await this.store.load(sid)) ?? session;
2694
3016
  if (!this.visibleToActiveProfile(session.profile))
2695
3017
  throw new HttpFailure(404, 'Session not found');
2696
- return { session, transcript: this.mergedTranscript(session), stored: true };
3018
+ return { session, transcript: this.mergedTranscript(session, session.messages, await this.stateDbRows(session)), stored: true };
2697
3019
  }
2698
3020
  catch (error) {
2699
3021
  if (error instanceof HttpFailure)
@@ -2819,6 +3141,18 @@ function findLastUserIndex(history) {
2819
3141
  export function sanitizePaths(error) {
2820
3142
  return str(error?.message ?? error).replace(/(?:(?:\/[a-zA-Z0-9_.-]+)+|(?:[A-Z]:\\[^\s]+))/g, '<path>');
2821
3143
  }
3144
+ /** A failed state.db read says nothing (TAL-493). */
3145
+ function failedStateDbRead() {
3146
+ return { rows: [], idCapable: false, ok: false, reason: null, generation: null };
3147
+ }
3148
+ /** TAL-529: after a compression rotation the Agent writes to the continuation, so a read follows the recorded lineage. */
3149
+ function stateDbReadLineage(s) {
3150
+ return s.state_db_lineage?.[0] === s.session_id ? s.state_db_lineage : null;
3151
+ }
3152
+ /** A delegated child's view, which never merges state.db rows. */
3153
+ function isSubagentView(s) {
3154
+ return str(s.source_tag || s.raw_source || s.session_source).trim().toLowerCase() === 'subagent';
3155
+ }
2822
3156
  /** TAL-536: a dead run's own state.db rows: from its prompt to the next prompt (tool results and applied steers stay in it). */
2823
3157
  function runStateDbRows(rows, prompt) {
2824
3158
  const at = rows.findIndex((m) => looksLikeCurrentUserTurn(m, prompt));